Compare commits
6 Commits
359617212d
...
662d4ac626
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
662d4ac626 | ||
|
|
4d7ae6c1c6 | ||
|
|
cb0e89b257 | ||
|
|
204aa75959 | ||
|
|
b72ec8afdf | ||
|
|
fa08986d60 |
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export { default as SetupFontMenu } from './SetupFontMenu.svelte';
|
|
||||||
export { default as TypographyMenu } from './TypographyMenu.svelte';
|
export { default as TypographyMenu } from './TypographyMenu.svelte';
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user