Compare commits
10 Commits
fedf3f88e7
...
780d76dced
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
780d76dced | ||
|
|
49f5564cc9 | ||
|
|
0ff8aec8f9 | ||
|
|
597ff7ec90 | ||
|
|
46a3c3e8fc | ||
|
|
4891cd3bbd | ||
|
|
70f2f82df0 | ||
|
|
0d572708c0 | ||
|
|
492c3573d0 | ||
|
|
a1080d3b34 |
@@ -1,15 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
Component: QueryProvider
|
||||||
|
Provides a QueryClientProvider for child components.
|
||||||
|
|
||||||
|
All components that use useQueryClient() or createQuery() must be
|
||||||
|
descendants of this provider.
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/**
|
|
||||||
* Query Provider Component
|
|
||||||
*
|
|
||||||
* All components that use useQueryClient() or createQuery() must be
|
|
||||||
* descendants of this provider.
|
|
||||||
*/
|
|
||||||
import { queryClient } from '$shared/api/queryClient';
|
import { queryClient } from '$shared/api/queryClient';
|
||||||
import { QueryClientProvider } from '@tanstack/svelte-query';
|
import { QueryClientProvider } from '@tanstack/svelte-query';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
/** Slot content for child components */
|
interface Props {
|
||||||
let { children } = $props();
|
children?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { controlManager } from '$features/SetupFont';
|
import { controlManager } from '$features/SetupFont';
|
||||||
import {
|
import {
|
||||||
ContentEditable,
|
ContentEditable,
|
||||||
|
Footnote,
|
||||||
// IconButton,
|
// IconButton,
|
||||||
} from '$shared/ui';
|
} from '$shared/ui';
|
||||||
// import XIcon from '@lucide/svelte/icons/x';
|
// import XIcon from '@lucide/svelte/icons/x';
|
||||||
@@ -44,7 +45,7 @@ let {
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const fontWeight = $derived(controlManager.weight);
|
const fontWeight = $derived(controlManager.weight);
|
||||||
const fontSize = $derived(controlManager.size);
|
const fontSize = $derived(controlManager.renderedSize);
|
||||||
const lineHeight = $derived(controlManager.height);
|
const lineHeight = $derived(controlManager.height);
|
||||||
const letterSpacing = $derived(controlManager.spacing);
|
const letterSpacing = $derived(controlManager.spacing);
|
||||||
|
|
||||||
@@ -66,13 +67,13 @@ function removeSample() {
|
|||||||
>
|
>
|
||||||
<div class="px-4 sm:px-5 md:px-6 py-2.5 sm:py-3 border-b border-gray-200/60 flex items-center justify-between">
|
<div class="px-4 sm:px-5 md:px-6 py-2.5 sm:py-3 border-b border-gray-200/60 flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2 sm:gap-2.5">
|
<div class="flex items-center gap-2 sm:gap-2.5">
|
||||||
<span class="font-mono text-[8px] sm:text-[9px] uppercase tracking-[0.25em] text-gray-500 font-medium">
|
<Footnote>
|
||||||
typeface_{String(index).padStart(3, '0')}
|
typeface_{String(index).padStart(3, '0')}
|
||||||
</span>
|
</Footnote>
|
||||||
<div class="w-px h-2 sm:h-2.5 bg-gray-300/60"></div>
|
<div class="w-px h-2 sm:h-2.5 bg-gray-300/60"></div>
|
||||||
<span class="font-mono text-[9px] sm:text-[10px] tracking-[0.15em] font-bold uppercase text-gray-900">
|
<Footnote class="tracking-[0.15em] font-bold text-gray-900">
|
||||||
{font.name}
|
{font.name}
|
||||||
</span>
|
</Footnote>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -101,20 +102,20 @@ function removeSample() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-4 sm:px-5 md:px-6 py-1.5 sm:py-2 border-t border-gray-200/40 w-full flex flex-row gap-2 sm:gap-4 bg-gray-50/30 mt-auto">
|
<div class="px-4 sm:px-5 md:px-6 py-1.5 sm:py-2 border-t border-gray-200/40 w-full flex flex-row gap-2 sm:gap-4 bg-gray-50/30 mt-auto">
|
||||||
<span class="text-[7px] sm:text-[8px] font-mono text-gray-400 uppercase tracking-wider ml-auto">
|
<Footnote class="text-[7px] sm:text-[8px] tracking-wider ml-auto">
|
||||||
SZ:{fontSize}PX
|
SZ:{fontSize}PX
|
||||||
</span>
|
</Footnote>
|
||||||
<div class="w-px h-2 sm:h-2.5 self-center bg-gray-300/40 hidden sm:block"></div>
|
<div class="w-px h-2 sm:h-2.5 self-center bg-gray-300/60 hidden sm:block"></div>
|
||||||
<span class="text-[7px] sm:text-[8px] font-mono text-gray-400 uppercase tracking-wider">
|
<Footnote class="text-[7px] sm:text-[8px] tracking-wider">
|
||||||
WGT:{fontWeight}
|
WGT:{fontWeight}
|
||||||
</span>
|
</Footnote>
|
||||||
<div class="w-px h-2 sm:h-2.5 self-center bg-gray-300/40 hidden sm:block"></div>
|
<div class="w-px h-2 sm:h-2.5 self-center bg-gray-300/60 hidden sm:block"></div>
|
||||||
<span class="text-[7px] sm:text-[8px] font-mono text-gray-400 uppercase tracking-wider">
|
<Footnote class="text-[7px] sm:text-[8px] tracking-wider">
|
||||||
LH:{lineHeight?.toFixed(2)}
|
LH:{lineHeight?.toFixed(2)}
|
||||||
</span>
|
</Footnote>
|
||||||
<div class="w-px h-2 sm:h-2.5 self-center bg-gray-300/40 hidden sm:block"></div>
|
<div class="w-px h-2 sm:h-2.5 self-center bg-gray-300/60 hidden sm:block"></div>
|
||||||
<span class="text-[7px] sm:text-[8px] font-mono text-gray-400 uppercase tracking-wider">
|
<Footnote class="text-[0.4375rem] sm:text-[0.5rem] tracking-wider">
|
||||||
LTR:{letterSpacing}
|
LTR:{letterSpacing}
|
||||||
</span>
|
</Footnote>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,58 +1,174 @@
|
|||||||
|
import type { ControlId } from '$features/SetupFont/model/state/manager.svelte';
|
||||||
import {
|
import {
|
||||||
|
type ControlDataModel,
|
||||||
type ControlModel,
|
type ControlModel,
|
||||||
|
type PersistentStore,
|
||||||
type TypographyControl,
|
type TypographyControl,
|
||||||
|
createPersistentStore,
|
||||||
createTypographyControl,
|
createTypographyControl,
|
||||||
} from '$shared/lib';
|
} from '$shared/lib';
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
import {
|
||||||
|
DEFAULT_FONT_SIZE,
|
||||||
|
DEFAULT_FONT_WEIGHT,
|
||||||
|
DEFAULT_LETTER_SPACING,
|
||||||
|
DEFAULT_LINE_HEIGHT,
|
||||||
|
} from '../../model';
|
||||||
|
|
||||||
export interface Control {
|
type ControlOnlyFields<T extends string = string> = Omit<ControlModel<T>, keyof ControlDataModel>;
|
||||||
id: string;
|
export interface Control extends ControlOnlyFields<ControlId> {
|
||||||
increaseLabel?: string;
|
|
||||||
decreaseLabel?: string;
|
|
||||||
controlLabel?: string;
|
|
||||||
instance: TypographyControl;
|
instance: TypographyControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TypographyControlManager {
|
export class TypographyControlManager {
|
||||||
#controls = new SvelteMap<string, Control>();
|
#controls = new SvelteMap<string, Control>();
|
||||||
#sizeMultiplier = $state(1);
|
#multiplier = $state(1);
|
||||||
|
#storage: PersistentStore<TypographySettings>;
|
||||||
|
#baseSize = $state(DEFAULT_FONT_SIZE);
|
||||||
|
|
||||||
constructor(configs: ControlModel[]) {
|
constructor(configs: ControlModel<ControlId>[], storage: PersistentStore<TypographySettings>) {
|
||||||
configs.forEach(({ id, increaseLabel, decreaseLabel, controlLabel, ...config }) => {
|
this.#storage = storage;
|
||||||
this.#controls.set(id, {
|
|
||||||
id,
|
// 1. Initial Load
|
||||||
increaseLabel,
|
const saved = storage.value;
|
||||||
decreaseLabel,
|
this.#baseSize = saved.fontSize;
|
||||||
controlLabel,
|
|
||||||
instance: createTypographyControl(config),
|
// 2. Setup Controls
|
||||||
|
configs.forEach(config => {
|
||||||
|
const initialValue = this.#getInitialValue(config.id, saved);
|
||||||
|
|
||||||
|
this.#controls.set(config.id, {
|
||||||
|
...config,
|
||||||
|
instance: createTypographyControl({
|
||||||
|
...config,
|
||||||
|
value: initialValue,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 3. The Sync Effect (UI -> Storage)
|
||||||
|
// We access .value explicitly to ensure Svelte 5 tracks the dependency
|
||||||
|
$effect.root(() => {
|
||||||
|
$effect(() => {
|
||||||
|
// EXPLICIT DEPENDENCIES: Accessing these triggers the effect
|
||||||
|
const fontSize = this.#baseSize;
|
||||||
|
const fontWeight = this.#controls.get('font_weight')?.instance.value ?? DEFAULT_FONT_WEIGHT;
|
||||||
|
const lineHeight = this.#controls.get('line_height')?.instance.value ?? DEFAULT_LINE_HEIGHT;
|
||||||
|
const letterSpacing = this.#controls.get('letter_spacing')?.instance.value ?? DEFAULT_LETTER_SPACING;
|
||||||
|
|
||||||
|
// Syncing back to storage
|
||||||
|
this.#storage.value = {
|
||||||
|
fontSize,
|
||||||
|
fontWeight,
|
||||||
|
lineHeight,
|
||||||
|
letterSpacing,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. The Font Size Proxy Effect
|
||||||
|
// This handles the "Multiplier" logic specifically for the Font Size Control
|
||||||
|
$effect(() => {
|
||||||
|
const ctrl = this.#controls.get('font_size')?.instance;
|
||||||
|
if (!ctrl) return;
|
||||||
|
|
||||||
|
// If the user moves the slider/clicks buttons in the UI:
|
||||||
|
// We update the baseSize (User Intent)
|
||||||
|
const currentDisplayValue = ctrl.value;
|
||||||
|
const calculatedBase = currentDisplayValue / this.#multiplier;
|
||||||
|
|
||||||
|
// Only update if the difference is significant (prevents rounding jitter)
|
||||||
|
if (Math.abs(this.#baseSize - calculatedBase) > 0.01) {
|
||||||
|
this.#baseSize = calculatedBase;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#getInitialValue(id: string, saved: TypographySettings): number {
|
||||||
|
if (id === 'font_size') return saved.fontSize * this.#multiplier;
|
||||||
|
if (id === 'font_weight') return saved.fontWeight;
|
||||||
|
if (id === 'line_height') return saved.lineHeight;
|
||||||
|
if (id === 'letter_spacing') return saved.letterSpacing;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Getters / Setters ---
|
||||||
|
|
||||||
|
get multiplier() {
|
||||||
|
return this.#multiplier;
|
||||||
|
}
|
||||||
|
set multiplier(value: number) {
|
||||||
|
if (this.#multiplier === value) return;
|
||||||
|
this.#multiplier = value;
|
||||||
|
|
||||||
|
// When multiplier changes, we must update the Font Size Control's display value
|
||||||
|
const ctrl = this.#controls.get('font_size')?.instance;
|
||||||
|
if (ctrl) {
|
||||||
|
ctrl.value = this.#baseSize * this.#multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The scaled size for CSS usage */
|
||||||
|
get renderedSize() {
|
||||||
|
return this.#baseSize * this.#multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The base size (User Preference) */
|
||||||
|
get baseSize() {
|
||||||
|
return this.#baseSize;
|
||||||
|
}
|
||||||
|
set baseSize(val: number) {
|
||||||
|
this.#baseSize = val;
|
||||||
|
const ctrl = this.#controls.get('font_size')?.instance;
|
||||||
|
if (ctrl) ctrl.value = val * this.#multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
get controls() {
|
get controls() {
|
||||||
return this.#controls.values();
|
return Array.from(this.#controls.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
get weight() {
|
get weight() {
|
||||||
return this.#controls.get('font_weight')?.instance.value ?? 400;
|
return this.#controls.get('font_weight')?.instance.value ?? DEFAULT_FONT_WEIGHT;
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
const size = this.#controls.get('font_size')?.instance.value;
|
|
||||||
return size === undefined ? undefined : size * this.#sizeMultiplier;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get height() {
|
get height() {
|
||||||
return this.#controls.get('line_height')?.instance.value;
|
return this.#controls.get('line_height')?.instance.value ?? DEFAULT_LINE_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
get spacing() {
|
get spacing() {
|
||||||
return this.#controls.get('letter_spacing')?.instance.value;
|
return this.#controls.get('letter_spacing')?.instance.value ?? DEFAULT_LETTER_SPACING;
|
||||||
}
|
}
|
||||||
|
|
||||||
set multiplier(value: number) {
|
reset() {
|
||||||
this.#sizeMultiplier = value;
|
this.#storage.clear();
|
||||||
|
const defaults = this.#storage.value;
|
||||||
|
|
||||||
|
this.#baseSize = defaults.fontSize;
|
||||||
|
|
||||||
|
// Reset all control instances
|
||||||
|
this.#controls.forEach(c => {
|
||||||
|
if (c.id === 'font_size') {
|
||||||
|
c.instance.value = defaults.fontSize * this.#multiplier;
|
||||||
|
} else {
|
||||||
|
// Map storage key to control id
|
||||||
|
const key = c.id.replace('_', '') as keyof TypographySettings;
|
||||||
|
// Simplified for brevity, you'd map these properly:
|
||||||
|
if (c.id === 'font_weight') c.instance.value = defaults.fontWeight;
|
||||||
|
if (c.id === 'line_height') c.instance.value = defaults.lineHeight;
|
||||||
|
if (c.id === 'letter_spacing') c.instance.value = defaults.letterSpacing;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage schema for typography settings
|
||||||
|
*/
|
||||||
|
export interface TypographySettings {
|
||||||
|
fontSize: number;
|
||||||
|
fontWeight: number;
|
||||||
|
lineHeight: number;
|
||||||
|
letterSpacing: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +177,12 @@ export class TypographyControlManager {
|
|||||||
* @param configs - Array of control configurations.
|
* @param configs - Array of control configurations.
|
||||||
* @returns - Typography control manager instance.
|
* @returns - Typography control manager instance.
|
||||||
*/
|
*/
|
||||||
export function createTypographyControlManager(configs: ControlModel[]) {
|
export function createTypographyControlManager(configs: ControlModel<ControlId>[]) {
|
||||||
return new TypographyControlManager(configs);
|
const storage = createPersistentStore<TypographySettings>('glyphdiff:typography', {
|
||||||
|
fontSize: DEFAULT_FONT_SIZE,
|
||||||
|
fontWeight: DEFAULT_FONT_WEIGHT,
|
||||||
|
lineHeight: DEFAULT_LINE_HEIGHT,
|
||||||
|
letterSpacing: DEFAULT_LETTER_SPACING,
|
||||||
|
});
|
||||||
|
return new TypographyControlManager(configs, storage);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
export { createTypographyControlManager } from './controlManager/controlManager.svelte';
|
export {
|
||||||
|
createTypographyControlManager,
|
||||||
|
type TypographyControlManager,
|
||||||
|
} from './controlManager/controlManager.svelte';
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ export {
|
|||||||
MIN_LINE_HEIGHT,
|
MIN_LINE_HEIGHT,
|
||||||
} from './const/const';
|
} from './const/const';
|
||||||
|
|
||||||
|
export { type TypographyControlManager } from '../lib';
|
||||||
export { controlManager } from './state/manager.svelte';
|
export { controlManager } from './state/manager.svelte';
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import {
|
|||||||
MIN_LINE_HEIGHT,
|
MIN_LINE_HEIGHT,
|
||||||
} from '../const/const';
|
} from '../const/const';
|
||||||
|
|
||||||
const controlData: ControlModel[] = [
|
export type ControlId = 'font_size' | 'font_weight' | 'line_height' | 'letter_spacing';
|
||||||
|
|
||||||
|
const controlData: ControlModel<ControlId>[] = [
|
||||||
{
|
{
|
||||||
id: 'font_size',
|
id: 'font_size',
|
||||||
value: DEFAULT_FONT_SIZE,
|
value: DEFAULT_FONT_SIZE,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ $effect(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="py-2 px-10 flex flex-row items-center gap-2">
|
<div class="sm:py-2 sm:px-10 flex flex-row items-center gap-2">
|
||||||
<div class="flex flex-row gap-3">
|
<div class="flex flex-row gap-3">
|
||||||
{#each controlManager.controls as control (control.id)}
|
{#each controlManager.controls as control (control.id)}
|
||||||
<ComboControl
|
<ComboControl
|
||||||
@@ -39,6 +39,7 @@ $effect(() => {
|
|||||||
increaseLabel={control.increaseLabel}
|
increaseLabel={control.increaseLabel}
|
||||||
decreaseLabel={control.decreaseLabel}
|
decreaseLabel={control.decreaseLabel}
|
||||||
controlLabel={control.controlLabel}
|
controlLabel={control.controlLabel}
|
||||||
|
reduced={responsive.isMobile}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { scrollBreadcrumbsStore } from '$entities/Breadcrumb';
|
import { scrollBreadcrumbsStore } from '$entities/Breadcrumb';
|
||||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||||
import { Section } from '$shared/ui';
|
import {
|
||||||
|
Logo,
|
||||||
|
Section,
|
||||||
|
} from '$shared/ui';
|
||||||
import ComparisonSlider from '$widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte';
|
import ComparisonSlider from '$widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte';
|
||||||
import { FontSearch } from '$widgets/FontSearch';
|
import { FontSearch } from '$widgets/FontSearch';
|
||||||
import { SampleList } from '$widgets/SampleList';
|
import { SampleList } from '$widgets/SampleList';
|
||||||
@@ -52,9 +55,7 @@ function handleTitleStatusChanged(index: number, isPast: boolean, title?: Snippe
|
|||||||
Project_Codename
|
Project_Codename
|
||||||
</span>
|
</span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
<h2 class={cn('font-[Barlow] font-thin text-5xl sm:text-6xl md:text-7xl lg:text-8xl text-justify [text-align-last:justify] [text-justify:inter-character]')}>
|
<Logo />
|
||||||
GLYPHDIFF
|
|
||||||
</h2>
|
|
||||||
</Section>
|
</Section>
|
||||||
<!--
|
<!--
|
||||||
<Section class="my-12 gap-8" index={1} onTitleStatusChange={handleTitleStatusChanged}>
|
<Section class="my-12 gap-8" index={1} onTitleStatusChange={handleTitleStatusChanged}>
|
||||||
|
|||||||
@@ -49,3 +49,5 @@ export function createPersistentStore<T>(key: string, defaultValue: T) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PersistentStore<T> = ReturnType<typeof createPersistentStore<T>>;
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ export interface ControlDataModel {
|
|||||||
step: number;
|
step: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ControlModel extends ControlDataModel {
|
export interface ControlModel<T extends string = string> extends ControlDataModel {
|
||||||
/**
|
/**
|
||||||
* Control identifier
|
* Control identifier
|
||||||
*/
|
*/
|
||||||
id: string;
|
id: T;
|
||||||
/**
|
/**
|
||||||
* Area label for increase button
|
* Area label for increase button
|
||||||
*/
|
*/
|
||||||
@@ -59,10 +59,10 @@ export function createTypographyControl<T extends ControlDataModel>(
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
set value(newValue) {
|
set value(newValue) {
|
||||||
value = roundToStepPrecision(
|
const rounded = roundToStepPrecision(clampNumber(newValue, min, max), step);
|
||||||
clampNumber(newValue, min, max),
|
if (value !== rounded) {
|
||||||
step,
|
value = rounded;
|
||||||
);
|
}
|
||||||
},
|
},
|
||||||
get max() {
|
get max() {
|
||||||
return max;
|
return max;
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ export {
|
|||||||
type LineData,
|
type LineData,
|
||||||
} from './createCharacterComparison/createCharacterComparison.svelte';
|
} from './createCharacterComparison/createCharacterComparison.svelte';
|
||||||
|
|
||||||
export { createPersistentStore } from './createPersistentStore/createPersistentStore.svelte';
|
export {
|
||||||
|
createPersistentStore,
|
||||||
|
type PersistentStore,
|
||||||
|
} from './createPersistentStore/createPersistentStore.svelte';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createResponsiveManager,
|
createResponsiveManager,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export {
|
|||||||
type Filter,
|
type Filter,
|
||||||
type FilterModel,
|
type FilterModel,
|
||||||
type LineData,
|
type LineData,
|
||||||
|
type PersistentStore,
|
||||||
type Property,
|
type Property,
|
||||||
type ResponsiveManager,
|
type ResponsiveManager,
|
||||||
responsiveManager,
|
responsiveManager,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import PlusIcon from '@lucide/svelte/icons/plus';
|
|||||||
import type { ChangeEventHandler } from 'svelte/elements';
|
import type { ChangeEventHandler } from 'svelte/elements';
|
||||||
import IconButton from '../IconButton/IconButton.svelte';
|
import IconButton from '../IconButton/IconButton.svelte';
|
||||||
|
|
||||||
interface ComboControlProps {
|
interface Props {
|
||||||
/**
|
/**
|
||||||
* Text for increase button aria-label
|
* Text for increase button aria-label
|
||||||
*/
|
*/
|
||||||
@@ -43,6 +43,10 @@ interface ComboControlProps {
|
|||||||
* Control instance
|
* Control instance
|
||||||
*/
|
*/
|
||||||
control: TypographyControl;
|
control: TypographyControl;
|
||||||
|
/**
|
||||||
|
* Reduced amount of controls
|
||||||
|
*/
|
||||||
|
reduced?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -50,7 +54,8 @@ const {
|
|||||||
decreaseLabel,
|
decreaseLabel,
|
||||||
increaseLabel,
|
increaseLabel,
|
||||||
controlLabel,
|
controlLabel,
|
||||||
}: ComboControlProps = $props();
|
reduced = false,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
// Local state for the slider to prevent infinite loops
|
// Local state for the slider to prevent infinite loops
|
||||||
// svelte-ignore state_referenced_locally - $state captures initial value, $effect syncs updates
|
// svelte-ignore state_referenced_locally - $state captures initial value, $effect syncs updates
|
||||||
@@ -80,6 +85,7 @@ const handleSliderChange = (newValue: number) => {
|
|||||||
<TooltipRoot>
|
<TooltipRoot>
|
||||||
<ButtonGroupRoot class="bg-transparent border-none shadow-none">
|
<ButtonGroupRoot class="bg-transparent border-none shadow-none">
|
||||||
<TooltipTrigger class="flex items-center">
|
<TooltipTrigger class="flex items-center">
|
||||||
|
{#if !reduced}
|
||||||
<IconButton
|
<IconButton
|
||||||
onclick={control.decrease}
|
onclick={control.decrease}
|
||||||
disabled={control.isAtMin}
|
disabled={control.isAtMin}
|
||||||
@@ -90,6 +96,7 @@ const handleSliderChange = (newValue: number) => {
|
|||||||
<MinusIcon class={className} />
|
<MinusIcon class={className} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
{/if}
|
||||||
<PopoverRoot>
|
<PopoverRoot>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
@@ -127,6 +134,7 @@ const handleSliderChange = (newValue: number) => {
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</PopoverRoot>
|
</PopoverRoot>
|
||||||
|
|
||||||
|
{#if !reduced}
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={increaseLabel}
|
aria-label={increaseLabel}
|
||||||
onclick={control.increase}
|
onclick={control.increase}
|
||||||
@@ -137,6 +145,7 @@ const handleSliderChange = (newValue: number) => {
|
|||||||
<PlusIcon class={className} />
|
<PlusIcon class={className} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
{/if}
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
</ButtonGroupRoot>
|
</ButtonGroupRoot>
|
||||||
{#if controlLabel}
|
{#if controlLabel}
|
||||||
|
|||||||
31
src/shared/ui/Footnote/Footnote.stories.svelte
Normal file
31
src/shared/ui/Footnote/Footnote.stories.svelte
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Footnote from './Footnote.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Shared/Footnote',
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Styles footnote text',
|
||||||
|
},
|
||||||
|
story: { inline: false }, // Render stories in iframe for state isolation
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Default">
|
||||||
|
<Footnote>
|
||||||
|
Footnote
|
||||||
|
</Footnote>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<Story name="With custom render">
|
||||||
|
<Footnote>
|
||||||
|
{#snippet render({ class: className })}
|
||||||
|
<span class={className}>Footnote</span>
|
||||||
|
{/snippet}
|
||||||
|
</Footnote>
|
||||||
|
</Story>
|
||||||
30
src/shared/ui/Footnote/Footnote.svelte
Normal file
30
src/shared/ui/Footnote/Footnote.svelte
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!--
|
||||||
|
Component: Footnote
|
||||||
|
Provides classes for styling footnotes
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: Snippet;
|
||||||
|
class?: string;
|
||||||
|
/**
|
||||||
|
* Custom render function for full control
|
||||||
|
*/
|
||||||
|
render?: Snippet<[{ class: string }]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children, class: className, render }: Props = $props();
|
||||||
|
|
||||||
|
const baseClasses = 'font-mono text-[0.5625rem] sm:text-[0.625rem] uppercase tracking-[0.2em] text-gray-500 opacity-60';
|
||||||
|
const combinedClasses = cn(baseClasses, className);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if render}
|
||||||
|
{@render render({ class: combinedClasses })}
|
||||||
|
{:else if children}
|
||||||
|
<span class={combinedClasses}>
|
||||||
|
{@render children()}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
21
src/shared/ui/Logo/Logo.stories.svelte
Normal file
21
src/shared/ui/Logo/Logo.stories.svelte
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Logo from './Logo.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Shared/Logo',
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Projec Logo',
|
||||||
|
},
|
||||||
|
story: { inline: false }, // Render stories in iframe for state isolation
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Default">
|
||||||
|
<Logo />
|
||||||
|
</Story>
|
||||||
19
src/shared/ui/Logo/Logo.svelte
Normal file
19
src/shared/ui/Logo/Logo.svelte
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
Component: Logo
|
||||||
|
Project logo with apropriate styles
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className }: Props = $props();
|
||||||
|
|
||||||
|
const baseClasses =
|
||||||
|
'font-[Barlow] font-thin text-5xl sm:text-6xl md:text-7xl lg:text-8xl text-justify [text-align-last:justify] [text-justify:inter-character]';
|
||||||
|
</script>
|
||||||
|
<h2 class={cn(baseClasses, className)}>
|
||||||
|
GLYPHDIFF
|
||||||
|
</h2>
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
type FlyParams,
|
type FlyParams,
|
||||||
fly,
|
fly,
|
||||||
} from 'svelte/transition';
|
} from 'svelte/transition';
|
||||||
|
import { Footnote } from '..';
|
||||||
|
|
||||||
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
|
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
|
||||||
/**
|
/**
|
||||||
@@ -91,17 +92,21 @@ $effect(() => {
|
|||||||
out:fly={flyParams}
|
out:fly={flyParams}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-2 sm:gap-3" bind:this={titleContainer}>
|
<div class="flex flex-col gap-2 sm:gap-3" bind:this={titleContainer}>
|
||||||
<div class="flex items-center gap-2 sm:gap-3 opacity-60">
|
<div class="flex items-center gap-2 sm:gap-3">
|
||||||
{#if icon}
|
{#if icon}
|
||||||
{@render icon({ className: 'size-3 sm:size-4 stroke-gray-900 stroke-1' })}
|
{@render icon({ className: 'size-3 sm:size-4 stroke-gray-900 stroke-1 opacity-60' })}
|
||||||
<div class="w-px h-2.5 sm:h-3 bg-gray-400/50"></div>
|
<div class="w-px h-2.5 sm:h-3 bg-gray-300/60"></div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if description}
|
{#if description}
|
||||||
{@render description({ className: 'font-mono text-[9px] sm:text-[10px] uppercase tracking-[0.2em] text-gray-600' })}
|
<Footnote>
|
||||||
|
{#snippet render({ class: className })}
|
||||||
|
{@render description({ className })}
|
||||||
|
{/snippet}
|
||||||
|
</Footnote>
|
||||||
{:else if typeof index === 'number'}
|
{:else if typeof index === 'number'}
|
||||||
<span class="font-mono text-[9px] sm:text-[10px] uppercase tracking-[0.2em] text-gray-600">
|
<Footnote>
|
||||||
Component_{String(index).padStart(3, '0')}
|
Component_{String(index).padStart(3, '0')}
|
||||||
</span>
|
</Footnote>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ export { default as ComboControl } from './ComboControl/ComboControl.svelte';
|
|||||||
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 ExpandableWrapper } from './ExpandableWrapper/ExpandableWrapper.svelte';
|
export { default as ExpandableWrapper } from './ExpandableWrapper/ExpandableWrapper.svelte';
|
||||||
|
export { default as Footnote } from './Footnote/Footnote.svelte';
|
||||||
export { default as IconButton } from './IconButton/IconButton.svelte';
|
export { default as IconButton } from './IconButton/IconButton.svelte';
|
||||||
export { default as Loader } from './Loader/Loader.svelte';
|
export { default as Loader } from './Loader/Loader.svelte';
|
||||||
|
export { default as Logo } from './Logo/Logo.svelte';
|
||||||
export { default as SearchBar } from './SearchBar/SearchBar.svelte';
|
export { default as SearchBar } from './SearchBar/SearchBar.svelte';
|
||||||
export { default as Section } from './Section/Section.svelte';
|
export { default as Section } from './Section/Section.svelte';
|
||||||
export { default as Skeleton } from './Skeleton/Skeleton.svelte';
|
export { default as Skeleton } from './Skeleton/Skeleton.svelte';
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { springySlideFade } from '$shared/lib';
|
import { springySlideFade } from '$shared/lib';
|
||||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||||
import {
|
import {
|
||||||
|
Footnote,
|
||||||
IconButton,
|
IconButton,
|
||||||
SearchBar,
|
SearchBar,
|
||||||
} from '$shared/ui';
|
} from '$shared/ui';
|
||||||
@@ -107,14 +108,12 @@ function toggleFilters() {
|
|||||||
shadow-[0_1px_3px_rgba(0,0,0,0.04)]
|
shadow-[0_1px_3px_rgba(0,0,0,0.04)]
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 sm:gap-2.5 mb-3 sm:mb-4 opacity-70">
|
<div class="flex items-center gap-2 sm:gap-2.5 mb-3 sm:mb-4">
|
||||||
<div class="w-1 h-1 rounded-full bg-gray-900"></div>
|
<div class="w-1 h-1 rounded-full bg-gray-900 opacity-70"></div>
|
||||||
<div class="w-px h-2.5 bg-gray-400/50"></div>
|
<div class="w-px h-2.5 bg-gray-300/60"></div>
|
||||||
<span
|
<Footnote>
|
||||||
class="font-mono text-[9px] sm:text-[10px] uppercase tracking-[0.2em] text-gray-500 font-medium"
|
|
||||||
>
|
|
||||||
filter_params
|
filter_params
|
||||||
</span>
|
</Footnote>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ const [send, receive] = crossfade({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="w-auto fixed bottom-4 sm:bottom-5 left-4 right-4 sm:left-auto sm:right-auto sm:inset-x-0 max-screen z-10 flex justify-center"
|
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"
|
||||||
in:receive={{ key: 'panel' }}
|
in:receive={{ key: 'panel' }}
|
||||||
out:send={{ key: 'panel' }}
|
out:send={{ key: 'panel' }}
|
||||||
>
|
>
|
||||||
<ItemRoot
|
<ItemRoot
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="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 />
|
<SetupFontMenu />
|
||||||
|
|||||||
Reference in New Issue
Block a user