chore: migrate to FSD architecture

This commit is contained in:
Ilia Mashkov
2026-01-01 13:09:54 +03:00
parent 8b6a1c468e
commit aa951260a0
11 changed files with 12 additions and 719 deletions

View File

@@ -1,85 +0,0 @@
<script lang="ts" module>
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { tv, type VariantProps } from 'tailwind-variants';
export const buttonVariants = tv({
base:
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs',
destructive:
'bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs',
outline:
'bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
});
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps =
& WithElementRef<HTMLButtonAttributes>
& WithElementRef<HTMLAnchorAttributes>
& {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>
<script lang="ts">
let {
class: className,
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = 'button',
disabled,
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
{type}
{disabled}
{...restProps}
>
{@render children?.()}
</button>
{/if}

View File

@@ -1,17 +0,0 @@
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants,
} from './button.svelte';
export {
type ButtonProps,
type ButtonProps as Props,
type ButtonSize,
type ButtonVariant,
buttonVariants,
Root,
//
Root as Button,
};

View File

@@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@@ -1,21 +0,0 @@
/**
* Generic collection API response model
* Use this for APIs that return collections of items
*
* @template T - The type of items in the collection array
* @template K - The key used to access the collection array in the response
*/
export type CollectionApiModel<T, K extends string = 'items'> = Record<K, T[]> & {
/**
* Number of items returned in the current page/response
*/
count: number;
/**
* Total number of items available across all pages
*/
count_total: number;
/**
* Indicates if there are more items available beyond this page
*/
has_more: boolean;
};

View File

@@ -1,37 +0,0 @@
/**
* Model of response with error
*/
export interface ApiErrorResponse {
/**
* Error text
*/
error: string;
/**
* Status
*/
status: number;
/**
* Status text
*/
statusText: string;
}
/**
* Model of response with success
*/
export interface ApiSuccessResponse<T> {
/**
* Data
*/
data: T;
/**
* Status
*/
status: number;
/**
* Status text
*/
statusText: string;
}
export type ApiResponse<T> = ApiErrorResponse | ApiSuccessResponse<T>;

View File

@@ -1,439 +0,0 @@
import type { CollectionApiModel } from './collection';
/**
* Model of Fontshare API response
* @see https://fontshare.com
*/
export type FontshareApiModel = CollectionApiModel<FontshareFont>;
/**
* Individual font metadata from Fontshare API
*/
export interface FontshareFont {
/**
* Unique identifier for the font
* UUID v4 format (e.g., "20e9fcdc-1e41-4559-a43d-1ede0adc8896")
*/
id: string;
/**
* Display name of the font family
* Examples: "Satoshi", "General Sans", "Clash Display"
*/
name: string;
/**
* Native/localized name of the font (if available)
* Often null for Latin-script fonts
*/
native_name: string | null;
/**
* URL-friendly identifier for the font
* Used in URLs: e.g., "satoshi", "general-sans", "clash-display"
*/
slug: string;
/**
* Font category classification
* Examples: "Sans", "Serif", "Display", "Script"
*/
category: string;
/**
* Script/writing system supported by the font
* Examples: "latin", "arabic", "devanagari"
*/
script: string;
/**
* Font publisher/foundry information
*/
publisher: FontsharePublisher;
/**
* Array of designers who created this font
* Multiple designers may have collaborated on a single font
*/
designers: FontshareDesigner[];
/**
* Related font families (if any)
* Often null, as fonts are typically independent
*/
related_families: string | null;
/**
* Whether to display publisher as the designer instead of individual designers
*/
display_publisher_as_designer: boolean;
/**
* Whether trial downloads are enabled for this font
*/
trials_enabled: boolean;
/**
* Whether to show Latin-specific metrics
*/
show_latin_metrics: boolean;
/**
* Type of license for this font
* Examples: "itf_ffl" (ITF Free Font License)
*/
license_type: string;
/**
* Comma-separated list of languages supported by this font
* Example: "Afar, Afrikaans, Albanian, Aranese, Aromanian, Aymara, ..."
*/
languages: string;
/**
* ISO 8601 timestamp when the font was added to Fontshare
* Format: "2021-03-12T20:49:05Z"
*/
inserted_at: string;
/**
* HTML-formatted story/description about the font
* Contains marketing text, design philosophy, and usage recommendations
*/
story: string;
/**
* Version of the font family
* Format: "1.0", "1.2", etc.
*/
version: string;
/**
* Total number of times this font has been viewed
*/
views: number;
/**
* Number of views in the recent time period
*/
views_recent: number;
/**
* Whether this font is marked as "hot"/trending
*/
is_hot: boolean;
/**
* Whether this font is marked as new
*/
is_new: boolean;
/**
* Whether this font is in the shortlisted collection
*/
is_shortlisted: boolean | null;
/**
* Whether this font is marked as top/popular
*/
is_top: boolean;
/**
* Variable font axes (for variable fonts)
* Empty array [] for static fonts
*/
axes: FontshareAxis[];
/**
* Tags/categories for this font
* Examples: ["Magazines", "Branding", "Logos", "Posters"]
*/
font_tags: FontshareTag[];
/**
* OpenType features available in this font
*/
features: FontshareFeature[];
/**
* Array of available font styles/variants
* Each style represents a different font file (weight, italic, variable)
*/
styles: FontshareStyle[];
}
/**
* Publisher/foundry information
*/
export interface FontsharePublisher {
/**
* Description/bio of the publisher
* Example: "Indian Type Foundry (ITF) creates retail and custom multilingual fonts..."
*/
bio: string;
/**
* Publisher email (if available)
*/
email: string | null;
/**
* Unique publisher identifier
* UUID format
*/
id: string;
/**
* Publisher links (social media, website, etc.)
*/
links: FontshareLink[];
/**
* Publisher name
* Example: "Indian Type Foundry"
*/
name: string;
}
/**
* Designer information
*/
export interface FontshareDesigner {
/**
* Designer bio/description
*/
bio: string;
/**
* Designer links (Twitter, website, etc.)
*/
links: FontshareLink[];
/**
* Designer name
*/
name: string;
}
/**
* Link information
*/
export interface FontshareLink {
/**
* Name of the link platform/site
* Examples: "Twitter", "GitHub", "Website"
*/
name: string;
/**
* URL of the link (may be null)
*/
url: string | null;
}
/**
* Font tag/category
*/
export interface FontshareTag {
/**
* Tag name
* Examples: "Magazines", "Branding", "Logos", "Posters"
*/
name: string;
}
/**
* OpenType feature
*/
export interface FontshareFeature {
/**
* Feature name (descriptive name or null)
* Examples: "Alternate t", "All Alternates", or null
*/
name: string | null;
/**
* Whether this feature is on by default
*/
on_by_default: boolean;
/**
* OpenType feature tag (4-character code)
* Examples: "ss01", "frac", "liga", "aalt", "case"
*/
tag: string;
}
/**
* Variable font axis (for variable fonts)
* Defines the range and properties of a variable font axis (e.g., weight)
*/
export interface FontshareAxis {
/**
* Name of the axis
* Example: "wght" (weight axis)
*/
name: string;
/**
* CSS property name for the axis
* Example: "wght"
*/
property: string;
/**
* Default value for the axis
* Example: 420.0, 650.0, 700.0
*/
range_default: number;
/**
* Minimum value for the axis
* Example: 300.0, 100.0, 200.0
*/
range_left: number;
/**
* Maximum value for the axis
* Example: 900.0, 700.0, 800.0
*/
range_right: number;
}
/**
* Individual font style/variant
* Each style represents a single downloadable font file
*/
export interface FontshareStyle {
/**
* Unique identifier for this style
* UUID format
*/
id: string;
/**
* Whether this is the default style for the font family
* Typically, one style per font is marked as default
*/
default: boolean;
/**
* CDN URL to the font file
* Protocol-relative URL: "//cdn.fontshare.com/wf/..."
* Note: URL starts with "//" (protocol-relative), may need protocol prepended
*/
file: string;
/**
* Whether this style is italic
* false for upright, true for italic styles
*/
is_italic: boolean;
/**
* Whether this is a variable font
* Variable fonts have adjustable axes (weight, slant, etc.)
*/
is_variable: boolean;
/**
* Typography properties for this style
* Contains measurements like cap height, x-height, ascenders/descenders
* May be empty object {} for some styles
*/
properties: FontshareStyleProperties | Record<string, never>;
/**
* Weight information for this style
*/
weight: FontshareWeight;
}
/**
* Typography/measurement properties for a font style
*/
export interface FontshareStyleProperties {
/**
* Distance from baseline to the top of ascenders
* Example: 1010, 990, 1000
*/
ascending_leading: number | null;
/**
* Height of uppercase letters (cap height)
* Example: 710, 680, 750
*/
cap_height: number | null;
/**
* Distance from baseline to the bottom of descenders (negative value)
* Example: -203, -186, -220
*/
descending_leading: number | null;
/**
* Body height of the font
* Often null in Fontshare data
*/
body_height: number | null;
/**
* Maximum character width in the font
* Example: 1739, 1739, 1739
*/
max_char_width: number | null;
/**
* Height of lowercase x-height
* Example: 480, 494, 523
*/
x_height: number | null;
/**
* Maximum Y coordinate (top of ascenders)
* Example: 1010, 990, 1026
*/
y_max: number | null;
/**
* Minimum Y coordinate (bottom of descenders)
* Example: -240, -250, -280
*/
y_min: number | null;
}
/**
* Weight information for a font style
*/
export interface FontshareWeight {
/**
* Display label for the weight
* Examples: "Light", "Regular", "Bold", "Variable", "Variable Italic"
*/
label: string;
/**
* Internal name for the weight
* Examples: "Light", "Regular", "Bold", "Variable", "VariableItalic"
*/
name: string;
/**
* Native/localized name for the weight (if available)
* Often null for Latin-script fonts
*/
native_name: string | null;
/**
* Numeric weight value
* Examples: 300, 400, 700, 0 (for variable fonts), 1, 2
* Note: This matches the `weight` property
*/
number: number;
/**
* Numeric weight value (duplicate of `number`)
* Appears to be redundant with `number` field
*/
weight: number;
}

View File

@@ -1,104 +0,0 @@
/**
* Model of google fonts api response
*/
export interface GoogleFontsApiModel {
/**
* Array of font items returned by the Google Fonts API
* Contains all font families matching the requested query parameters
*/
items: FontItem[];
}
export interface FontItem {
/**
* Font family name (e.g., "Roboto", "Open Sans", "Lato")
* This is the name used in CSS font-family declarations
*/
family: string;
/**
* Font category classification (e.g., "sans-serif", "serif", "display", "handwriting", "monospace")
* Useful for grouping and filtering fonts by style
*/
category: string;
/**
* Available font variants for this font family
* Array of strings representing available weights and styles
* Examples: ["regular", "italic", "100", "200", "300", "400", "500", "600", "700", "800", "900", "100italic", "900italic"]
* The keys in the `files` object correspond to these variant values
*/
variants: FontVariant[];
/**
* Supported character subsets for this font
* Examples: ["latin", "latin-ext", "cyrillic", "greek", "arabic", "devanagari", "vietnamese", "hebrew", "thai", etc.]
* Determines which character sets are included in the font files
*/
subsets: string[];
/**
* Font version identifier
* Format: "v" followed by version number (e.g., "v31", "v20", "v1")
* Used to track font updates and cache busting
*/
version: string;
/**
* Last modification date of the font
* Format: ISO 8601 date string (e.g., "2024-01-15", "2023-12-01")
* Indicates when the font was last updated by the font foundry
*/
lastModified: string;
/**
* Mapping of font variants to their downloadable URLs
* Keys correspond to values in the `variants` array
* Examples:
* - "regular" → "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Me4W..."
* - "700" → "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlf..."
* - "700italic" → "https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TzA..."
*/
files: FontFiles;
/**
* URL to the font menu preview image
* Typically a PNG showing the font family name in the font
* Example: "https://fonts.gstatic.com/l/font?kit=KFOmCnqEu92Fr1Me4W...&s=i2"
*/
menu: string;
}
/**
* Standard font weights that can appear in Google Fonts API
*/
export type FontWeight = '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
/**
* Italic variant format: e.g., "100italic", "400italic", "700italic"
*/
export type FontWeightItalic = `${FontWeight}italic`;
/**
* All possible font variants in Google Fonts API
* - Numeric weights: "400", "700", etc.
* - Italic variants: "400italic", "700italic", etc.
* - Legacy names: "regular", "italic", "bold", "bolditalic"
*/
export type FontVariant =
| FontWeight
| FontWeightItalic
| 'regular'
| 'italic'
| 'bold'
| 'bolditalic';
/**
* Google Fonts API file mapping
* Dynamic keys that match the variants array
*
* Examples:
* - { "regular": "...", "italic": "...", "700": "...", "700italic": "..." }
* - { "400": "...", "400italic": "...", "900": "..." }
*/
export type FontFiles = Partial<Record<FontVariant, string>>;

View File

@@ -1,13 +0,0 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T;
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -20,7 +20,12 @@
/* Path Aliases */ /* Path Aliases */
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"$lib/*": ["./src/lib/*"] "$lib/*": ["./src/lib/*"],
"$shared/*": ["./src/shared/*"],
"$entities/*": ["./src/entities/*"],
"$features/*": ["./src/features/*"],
"$routes/*": ["./src/routes/*"],
"$services/*": ["./src/services/*"]
} }
}, },
"include": [ "include": [
@@ -32,6 +37,6 @@
"exclude": [ "exclude": [
"node_modules", "node_modules",
"dist", "dist",
"./src/lib/components/ui" "./src/shared/ui"
] ]
} }

View File

@@ -7,6 +7,11 @@ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
$lib: '/src/lib', $lib: '/src/lib',
$shared: '/src/shared',
$entities: '/src/entities',
$features: '/src/features',
$routes: '/src/routes',
$services: '/src/services',
}, },
}, },
build: { build: {