Compare commits

...

6 Commits

12 changed files with 155 additions and 94 deletions

View File

@@ -11,7 +11,6 @@
* - Footer area (currently empty, reserved for future use) * - Footer area (currently empty, reserved for future use)
*/ */
import { BreadcrumbHeader } from '$entities/Breadcrumb'; import { BreadcrumbHeader } from '$entities/Breadcrumb';
import { TypographyMenu } from '$features/SetupFont';
import favicon from '$shared/assets/favicon.svg'; import favicon from '$shared/assets/favicon.svg';
import { ResponsiveProvider } from '$shared/lib'; import { ResponsiveProvider } from '$shared/lib';
import { ScrollArea } from '$shared/shadcn/ui/scroll-area'; import { ScrollArea } from '$shared/shadcn/ui/scroll-area';
@@ -52,7 +51,6 @@ let { children }: Props = $props();
<!-- <ScrollArea class="h-screen w-screen"> --> <!-- <ScrollArea class="h-screen w-screen"> -->
<main class="flex-1 h-full w-full max-w-6xl mx-auto px-0 pt-0 pb-10 sm:px-6 sm:pt-8 sm:pb-12 md:px-8 md:pt-10 md:pb-16 lg:px-10 lg:pt-12 lg:pb-20 xl:px-16 relative overflow-x-hidden"> <main class="flex-1 h-full w-full max-w-6xl mx-auto px-0 pt-0 pb-10 sm:px-6 sm:pt-8 sm:pb-12 md:px-8 md:pt-10 md:pb-16 lg:px-10 lg:pt-12 lg:pb-20 xl:px-16 relative overflow-x-hidden">
<TooltipProvider> <TooltipProvider>
<TypographyMenu />
{@render children?.()} {@render children?.()}
</TooltipProvider> </TooltipProvider>
</main> </main>

View File

@@ -1,7 +1,4 @@
export { export { TypographyMenu } from './ui';
SetupFontMenu,
TypographyMenu,
} from './ui';
export { export {
type ControlId, type ControlId,
@@ -20,6 +17,9 @@ export {
MIN_FONT_SIZE, MIN_FONT_SIZE,
MIN_FONT_WEIGHT, MIN_FONT_WEIGHT,
MIN_LINE_HEIGHT, MIN_LINE_HEIGHT,
MULTIPLIER_L,
MULTIPLIER_M,
MULTIPLIER_S,
} from './model'; } from './model';
export { export {

View File

@@ -79,3 +79,10 @@ export const DEFAULT_TYPOGRAPHY_CONTROLS_DATA: ControlModel<ControlId>[] = [
controlLabel: 'Letter Spacing', controlLabel: 'Letter Spacing',
}, },
]; ];
/**
* Font size multipliers
*/
export const MULTIPLIER_S = 0.5;
export const MULTIPLIER_M = 0.75;
export const MULTIPLIER_L = 1;

View File

@@ -13,6 +13,9 @@ export {
MIN_FONT_SIZE, MIN_FONT_SIZE,
MIN_FONT_WEIGHT, MIN_FONT_WEIGHT,
MIN_LINE_HEIGHT, MIN_LINE_HEIGHT,
MULTIPLIER_L,
MULTIPLIER_M,
MULTIPLIER_S,
} from './const/const'; } from './const/const';
export { export {

View File

@@ -1,46 +0,0 @@
<!--
Component: SetupFontMenu
Contains controls for setting up font properties.
-->
<script lang="ts">
import type { ResponsiveManager } from '$shared/lib';
import { ComboControl } from '$shared/ui';
import { getContext } from 'svelte';
import { controlManager } from '../model';
const responsive = getContext<ResponsiveManager>('responsive');
$effect(() => {
if (!responsive) {
return;
}
switch (true) {
case responsive.isMobile:
controlManager.multiplier = 0.5;
break;
case responsive.isTablet:
controlManager.multiplier = 0.75;
break;
case responsive.isDesktop:
controlManager.multiplier = 1;
break;
default:
controlManager.multiplier = 1;
break;
}
});
</script>
<div class="sm:py-2 sm:px-10 flex flex-row items-center gap-2">
<div class="flex flex-row gap-3">
{#each controlManager.controls as control (control.id)}
<ComboControl
control={control.instance}
increaseLabel={control.increaseLabel}
decreaseLabel={control.decreaseLabel}
controlLabel={control.controlLabel}
reduced={responsive.isMobile}
/>
{/each}
</div>
</div>

View File

@@ -1,41 +1,121 @@
<!-- <!--
Component: TypographyMenu Component: TypographyMenu
Provides a menu for selecting and configuring typography settings Provides a menu for selecting and configuring typography settings
- On mobile the menu is displayed as a drawer
--> -->
<script lang="ts"> <script lang="ts">
import { SetupFontMenu } from '$features/SetupFont'; import type { ResponsiveManager } from '$shared/lib';
import { import {
Content as ItemContent, Content as ItemContent,
Root as ItemRoot, Root as ItemRoot,
} from '$shared/shadcn/ui/item'; } from '$shared/shadcn/ui/item';
import { cn } from '$shared/shadcn/utils/shadcn-utils';
import {
ComboControl,
ComboControlV2,
Drawer,
IconButton,
} from '$shared/ui';
import SlidersIcon from '@lucide/svelte/icons/sliders-vertical';
import { getContext } from 'svelte';
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
import { crossfade } from 'svelte/transition'; import { crossfade } from 'svelte/transition';
import {
MULTIPLIER_L,
MULTIPLIER_M,
MULTIPLIER_S,
controlManager,
} from '../model';
interface Props {
class?: string;
}
const { class: className }: Props = $props();
const responsive = getContext<ResponsiveManager>('responsive');
const [send, receive] = crossfade({ const [send, receive] = crossfade({
duration: 400, duration: 300,
easing: cubicOut, easing: cubicOut,
fallback(node, params) { fallback(node, params) {
// If it can't find a pair, it falls back to a simple fade/slide // If it can't find a pair, it falls back to a simple fade/slide
return { return {
duration: 400, duration: 300,
css: t => `opacity: ${t}; transform: translateY(${(1 - t) * 10}px);`, css: t => `opacity: ${t}; transform: translateY(${(1 - t) * 10}px);`,
}; };
}, },
}); });
/**
* Sets the common font size multiplier based on the current responsive state.
*/
$effect(() => {
if (!responsive) {
return;
}
switch (true) {
case responsive.isMobile:
controlManager.multiplier = MULTIPLIER_S;
break;
case responsive.isTablet:
controlManager.multiplier = MULTIPLIER_M;
break;
case responsive.isDesktop:
controlManager.multiplier = MULTIPLIER_L;
break;
default:
controlManager.multiplier = MULTIPLIER_L;
break;
}
});
</script> </script>
<div <div
class="w-auto fixed bottom-4 sm:bottom-5 left-4 right-4 sm:left-1/2 sm:right-[unset] sm:-translate-x-1/2 sm:inset-x-0 max-screen z-10 flex justify-center" class={cn('w-auto max-screen z-10 flex justify-center', className)}
in:receive={{ key: 'panel' }} in:receive={{ key: 'panel' }}
out:send={{ key: 'panel' }} out:send={{ key: 'panel' }}
> >
{#if responsive.isMobile}
<Drawer>
{#snippet trigger({ onClick })}
<IconButton onclick={onClick}>
{#snippet icon({ className })}
<SlidersIcon class={className} />
{/snippet}
</IconButton>
{/snippet}
{#snippet content()}
<div class="flex flex-col gap-6 px-2 py-4">
{#each controlManager.controls as control (control.id)}
<ComboControlV2
control={control.instance}
orientation="horizontal"
/>
{/each}
</div>
{/snippet}
</Drawer>
{:else}
<ItemRoot <ItemRoot
variant="outline" variant="outline"
class="w-full sm:w-auto max-w-full sm:max-w-max p-2 sm:p-2.5 rounded-xl sm:rounded-2xl backdrop-blur-lg" class="w-full sm:w-auto max-w-full sm:max-w-max p-2 sm:p-2.5 rounded-xl sm:rounded-2xl backdrop-blur-lg"
> >
<ItemContent class="flex flex-row justify-center items-center max-w-full sm:max-w-max"> <ItemContent class="flex flex-row justify-center items-center max-w-full sm:max-w-max">
<SetupFontMenu /> <div class="sm:py-2 sm:px-10 flex flex-row items-center gap-2">
<div class="flex flex-row gap-3">
{#each controlManager.controls as control (control.id)}
<ComboControl
control={control.instance}
increaseLabel={control.increaseLabel}
decreaseLabel={control.decreaseLabel}
controlLabel={control.controlLabel}
reduced={responsive.isMobile}
/>
{/each}
</div>
</div>
</ItemContent> </ItemContent>
</ItemRoot> </ItemRoot>
{/if}
</div> </div>

View File

@@ -1,2 +1 @@
export { default as SetupFontMenu } from './SetupFontMenu.svelte';
export { default as TypographyMenu } from './TypographyMenu.svelte'; export { default as TypographyMenu } from './TypographyMenu.svelte';

View File

@@ -24,10 +24,6 @@ const { Story } = defineMeta({
control: 'text', control: 'text',
description: 'Placeholder text for the input', description: 'Placeholder text for the input',
}, },
label: {
control: 'text',
description: 'Optional label displayed above the input',
},
}, },
}); });
</script> </script>

View File

@@ -20,10 +20,6 @@ interface Props {
* Placeholder text for the input * Placeholder text for the input
*/ */
placeholder?: string; placeholder?: string;
/**
* Optional label displayed above the input
*/
label?: string;
} }
let { let {

View File

@@ -1,6 +1,5 @@
export { default as CheckboxFilter } from './CheckboxFilter/CheckboxFilter.svelte'; export { default as CheckboxFilter } from './CheckboxFilter/CheckboxFilter.svelte';
export { default as ComboControl } from './ComboControl/ComboControl.svelte'; export { default as ComboControl } from './ComboControl/ComboControl.svelte';
// ComboControlV2 might vary, assuming pattern holds or I'll fix later if build fails
export { default as ComboControlV2 } from './ComboControlV2/ComboControlV2.svelte'; export { default as ComboControlV2 } from './ComboControlV2/ComboControlV2.svelte';
export { default as ContentEditable } from './ContentEditable/ContentEditable.svelte'; export { default as ContentEditable } from './ContentEditable/ContentEditable.svelte';
export { default as Drawer } from './Drawer/Drawer.svelte'; export { default as Drawer } from './Drawer/Drawer.svelte';

View File

@@ -72,7 +72,6 @@ function toggleFilters() {
id="font-search" id="font-search"
class="w-full" class="w-full"
placeholder="search_typefaces..." placeholder="search_typefaces..."
label="query_input"
bind:value={filterManager.queryValue} bind:value={filterManager.queryValue}
/> />

View File

@@ -1,7 +1,8 @@
<!-- <!--
Component: SampleList Component: SampleList
Renders a list of fonts in a virtualized list to improve performance. Renders a list of fonts in a virtualized list to improve performance.
Includes pagination with auto-loading when scrolling near the bottom. - Includes pagination with auto-loading when scrolling near the bottom.
- Provides a typography menu for font setup.
--> -->
<script lang="ts"> <script lang="ts">
import { import {
@@ -10,9 +11,19 @@ import {
unifiedFontStore, unifiedFontStore,
} from '$entities/Font'; } from '$entities/Font';
import { FontSampler } from '$features/DisplayFont'; import { FontSampler } from '$features/DisplayFont';
import { controlManager } from '$features/SetupFont'; import {
TypographyMenu,
controlManager,
} from '$features/SetupFont';
let text = $state('The quick brown fox jumps over the lazy dog...'); let text = $state('The quick brown fox jumps over the lazy dog...');
let wrapper = $state<HTMLDivElement | null>(null);
// Binds to the actual window height
let innerHeight = $state(0);
// Is the component above the middle of the viewport?
let isAboveMiddle = $state(false);
const isLoading = $derived(unifiedFontStore.isFetching || unifiedFontStore.isLoading);
/** /**
* Load more fonts by moving to the next page * Load more fonts by moving to the next page
@@ -51,10 +62,24 @@ const displayRange = $derived.by(() => {
return `Showing ${loadedCount} of ${total} fonts`; return `Showing ${loadedCount} of ${total} fonts`;
}); });
const isLoading = $derived(unifiedFontStore.isFetching || unifiedFontStore.isLoading); function checkPosition() {
if (!wrapper) return;
const rect = wrapper.getBoundingClientRect();
const viewportMiddle = innerHeight / 2;
isAboveMiddle = rect.top < viewportMiddle;
}
</script> </script>
<FontVirtualList <svelte:window
bind:innerHeight
onscroll={checkPosition}
onresize={checkPosition}
/>
<div bind:this={wrapper}>
<FontVirtualList
items={unifiedFontStore.fonts} items={unifiedFontStore.fonts}
total={unifiedFontStore.pagination.total} total={unifiedFontStore.pagination.total}
onNearBottom={handleNearBottom} onNearBottom={handleNearBottom}
@@ -62,7 +87,7 @@ const isLoading = $derived(unifiedFontStore.isFetching || unifiedFontStore.isLoa
useWindowScroll={true} useWindowScroll={true}
weight={controlManager.weight} weight={controlManager.weight}
{isLoading} {isLoading}
> >
{#snippet children({ {#snippet children({
item: font, item: font,
isFullyVisible, isFullyVisible,
@@ -74,4 +99,9 @@ const isLoading = $derived(unifiedFontStore.isFetching || unifiedFontStore.isLoa
<FontSampler {font} bind:text {index} /> <FontSampler {font} bind:text {index} />
</FontListItem> </FontListItem>
{/snippet} {/snippet}
</FontVirtualList> </FontVirtualList>
{#if isAboveMiddle}
<TypographyMenu class="fixed bottom-4 sm:bottom-5 right-4 sm:left-1/2 sm:right-[unset] sm:-translate-x-1/2" />
{/if}
</div>