Compare commits

...

3 Commits

Author SHA1 Message Date
Ilia Mashkov
4ba02b5933 fix: new dprint import format settings
Some checks failed
Lint / Lint Code (push) Failing after 7m9s
Test / Svelte Checks (push) Failing after 7m20s
2026-01-02 17:01:59 +03:00
Ilia Mashkov
3a2cc1c76b feat(dprint): setup import/export order 2026-01-02 17:00:58 +03:00
Ilia Mashkov
be267d43d8 feat(CheckboxFilter): add comprehencive documentation 2026-01-02 17:00:34 +03:00
41 changed files with 241 additions and 51 deletions

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://dprint.dev/schemas/v0.json",
"incremental": true,
"includes": ["**/*.{ts,tsx,js,jsx,svelte,json,md}"],
"excludes": [
@@ -22,7 +23,17 @@
"quoteStyle": "preferSingle",
"trailingCommas": "onlyMultiLine",
"arrowFunction.useParentheses": "preferNone",
"importDeclaration.sortNamedImports": "caseInsensitive"
// Import sorting configuration
"module.sortImportDeclarations": "caseSensitive",
"module.sortExportDeclarations": "caseSensitive",
"importDeclaration.sortNamedImports": "caseSensitive",
// Additional import formatting options
"importDeclaration.forceMultiLine": "whenMultiple",
"importDeclaration.forceSingleLine": false,
"exportDeclaration.forceMultiLine": "whenMultiple",
"exportDeclaration.forceSingleLine": false
},
"json": {
"indentWidth": 2,
@@ -37,6 +48,11 @@
"useTabs": false,
"quotes": "double",
"scriptIndent": false,
"styleIndent": false
"styleIndent": false,
// Svelte-specific formatting
"vBindStyle": "short",
"vOnStyle": "short",
"formatComments": true
}
}

View File

@@ -1,4 +1,7 @@
import { expect, test } from '@playwright/test';
import {
expect,
test,
} from '@playwright/test';
test('home page has expected h1', async ({ page }) => {
await page.goto('/');

View File

@@ -1,5 +1,9 @@
declare module '*.svelte' {
import type { ComponentProps as SvelteComponentProps, ComponentType, Snippet } from 'svelte';
import type {
ComponentProps as SvelteComponentProps,
ComponentType,
Snippet,
} from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
interface Component {

View File

@@ -1,4 +1,8 @@
export type { FontCategory, FontProvider, FontSubset } from './model/font';
export type {
FontCategory,
FontProvider,
FontSubset,
} from './model/font';
export type {
FontshareApiModel,
FontshareDesigner,
@@ -10,4 +14,9 @@ export type {
FontshareTag,
FontshareWeight,
} from './model/fontshare_fonts';
export type { FontFiles, FontItem, FontVariant, GoogleFontsApiModel } from './model/google_fonts';
export type {
FontFiles,
FontItem,
FontVariant,
GoogleFontsApiModel,
} from './model/google_fonts';

View File

@@ -2,4 +2,8 @@ import { FONT_CATEGORIES } from './model/state';
import { categoryFilterStore } from './store/categoryFilterStore';
import CategoryFilter from './ui/CategoryFilter.svelte';
export { CategoryFilter, categoryFilterStore, FONT_CATEGORIES };
export {
CategoryFilter,
categoryFilterStore,
FONT_CATEGORIES,
};

View File

@@ -1,5 +1,8 @@
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
import {
type VariantProps,
tv,
} from 'tailwind-variants';
export const badgeVariants = tv({
base:
@@ -24,7 +27,10 @@ export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
</script>
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAnchorAttributes } from 'svelte/elements';
let {

View File

@@ -1,2 +1,5 @@
export { default as Badge } from './badge.svelte';
export { type BadgeVariant, badgeVariants } from './badge.svelte';
export {
type BadgeVariant,
badgeVariants,
} from './badge.svelte';

View File

@@ -1,7 +1,16 @@
<script lang="ts" module>
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { tv, type VariantProps } from 'tailwind-variants';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type {
HTMLAnchorAttributes,
HTMLButtonAttributes,
} from 'svelte/elements';
import {
type VariantProps,
tv,
} from 'tailwind-variants';
export const buttonVariants = tv({
base:

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithoutChildrenOrChild } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithoutChildrenOrChild,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import CheckIcon from '@lucide/svelte/icons/check';
import MinusIcon from '@lucide/svelte/icons/minus';
import { Checkbox as CheckboxPrimitive } from 'bits-ui';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithoutChildrenOrChild } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithoutChildrenOrChild,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import XIcon from '@lucide/svelte/icons/x';
import { Dialog as DialogPrimitive } from 'bits-ui';
import type { Snippet } from 'svelte';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,6 +1,12 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type {
HTMLInputAttributes,
HTMLInputTypeAttribute,
} from 'svelte/elements';
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;

View File

@@ -1,5 +1,8 @@
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
import {
type VariantProps,
tv,
} from 'tailwind-variants';
export const sheetVariants = tv({
base:
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
@@ -23,7 +26,10 @@ export type Side = VariantProps<typeof sheetVariants>['side'];
</script>
<script lang="ts">
import { cn, type WithoutChildrenOrChild } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithoutChildrenOrChild,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import XIcon from '@lucide/svelte/icons/x';
import { Dialog as SheetPrimitive } from 'bits-ui';
import type { Snippet } from 'svelte';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
import { IsMobile } from '$shared/shadcn/hooks/is-mobile.svelte.js';
import { getContext, setContext } from 'svelte';
import {
getContext,
setContext,
} from 'svelte';
import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js';
type Getter<T> = () => T;

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
import {
type VariantProps,
tv,
} from 'tailwind-variants';
export const sidebarMenuButtonVariants = tv({
base:
@@ -31,12 +34,15 @@ export type SidebarMenuButtonSize = VariantProps<typeof sidebarMenuButtonVariant
<script lang="ts">
import * as Tooltip from '$shared/shadcn/ui/tooltip/index.js';
import {
cn,
type WithElementRef,
type WithoutChildrenOrChild,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import { mergeProps } from 'bits-ui';
import type { ComponentProps, Snippet } from 'svelte';
import type {
ComponentProps,
Snippet,
} from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import { useSidebar } from './context.svelte.js';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,6 +1,9 @@
<script lang="ts">
import { Skeleton } from '$shared/shadcn/ui/skeleton/index.js';
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { Snippet } from 'svelte';
import type { HTMLAnchorAttributes } from 'svelte/elements';

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {

View File

@@ -1,6 +1,9 @@
<script lang="ts">
import * as Tooltip from '$shared/shadcn/ui/tooltip/index.js';
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import {
SIDEBAR_COOKIE_MAX_AGE,

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import { useSidebar } from './context.svelte.js';

View File

@@ -1,6 +1,9 @@
<script lang="ts">
import * as Sheet from '$shared/shadcn/ui/sheet/index.js';
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
import {
type WithElementRef,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';
import { SIDEBAR_WIDTH_MOBILE } from './constants.js';
import { useSidebar } from './context.svelte.js';

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import {
cn,
type WithElementRef,
type WithoutChildren,
cn,
} from '$shared/shadcn/utils/shadcn-utils.js';
import type { HTMLAttributes } from 'svelte/elements';

View File

@@ -1,4 +1,7 @@
import { type ClassValue, clsx } from 'clsx';
import {
type ClassValue,
clsx,
} from 'clsx';
import { twMerge } from 'tailwind-merge';
/**

View File

@@ -1,4 +1,9 @@
import { derived, type Readable, type Writable, writable } from 'svelte/store';
import {
type Readable,
type Writable,
derived,
writable,
} from 'svelte/store';
export interface Category {
/**

View File

@@ -10,17 +10,37 @@ import { onMount } from 'svelte';
import { cubicOut } from 'svelte/easing';
import { slide } from 'svelte/transition';
/**
* CheckboxFilter Component
*
* A collapsible category filter with checkboxes. Displays selected count as a badge
* and supports reduced motion for accessibility. Used in sidebar filtering UIs.
*
* Design choices:
* - Open by default for immediate visibility and interaction
* - Badge shown only when filters are active to reduce visual noise
* - Transitions use cubicOut for natural deceleration
* - Local transition prevents animation when component first renders
*/
interface CategoryFilterProps {
/** Display name for this filter group (e.g., "Categories", "Tags") */
filterName: string;
/** Array of categories with their selection states */
categories: Category[];
/** Callback when a category checkbox is toggled */
onCategoryToggle: (id: string) => void;
}
const { filterName, categories, onCategoryToggle }: CategoryFilterProps = $props();
// Toggle state - defaults to open for better discoverability
let isOpen = $state(true);
// Accessibility preference to disable animations
let prefersReducedMotion = $state(false);
// Check reduced motion preference on mount (window access required)
// Event listener allows responding to system preference changes
onMount(() => {
if (typeof window !== 'undefined') {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
@@ -35,21 +55,24 @@ onMount(() => {
}
});
// Optimized animation configuration
// Animation config respects user preferences - zero duration if reduced motion enabled
// Local modifier prevents animation on initial render, only animates user interactions
const slideConfig = $derived({
duration: prefersReducedMotion ? 0 : 250,
easing: cubicOut,
});
// Count selected categories for badge
// Derived for reactive updates when categories change - avoids recomputing on every render
const selectedCount = $derived(categories.filter(c => c.selected).length);
const hasSelection = $derived(selectedCount > 0);
</script>
<!-- Collapsible card wrapper with subtle hover state for affordance -->
<Collapsible.Root
bind:open={isOpen}
class="w-full rounded-lg border bg-card transition-colors hover:bg-accent/5"
>
<!-- Trigger row: title, expand indicator, and optional count badge -->
<div class="flex items-center justify-between px-4 py-2">
<Collapsible.Trigger
class={buttonVariants({
@@ -61,12 +84,14 @@ const hasSelection = $derived(selectedCount > 0);
>
<h4 class="text-sm font-semibold">{filterName}</h4>
<!-- Chevron rotates based on open state for visual feedback -->
<div
class="shrink-0 transition-transform duration-200 ease-out"
style:transform={isOpen ? 'rotate(0deg)' : 'rotate(-90deg)'}
>
<ChevronDownIcon class="h-4 w-4" />
</div>
<!-- Badge only appears when items are selected to avoid clutter -->
{#if hasSelection}
<Badge
variant="secondary"
@@ -78,6 +103,7 @@ const hasSelection = $derived(selectedCount > 0);
</Collapsible.Trigger>
</div>
<!-- Expandable content with slide animation -->
{#if isOpen}
<div
transition:slide|local={slideConfig}
@@ -85,6 +111,8 @@ const hasSelection = $derived(selectedCount > 0);
>
<div class="px-4 py-3">
<div class="flex flex-col gap-2.5">
<!-- Each item: checkbox + label with interactive hover/focus states -->
<!-- Keyed by category.id for efficient DOM updates -->
{#each categories as category (category.id)}
<Label
for={category.id}
@@ -95,6 +123,9 @@ const hasSelection = $derived(selectedCount > 0);
active:scale-[0.98] active:transition-transform active:duration-75
"
>
<!--
Checkbox handles toggle, styled for accessibility with focus rings
-->
<Checkbox
id={category.id}
checked={category.selected}
@@ -106,6 +137,7 @@ const hasSelection = $derived(selectedCount > 0);
focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
"
/>
<!-- Label text changes weight/color based on selection state -->
<span
class="
text-sm select-none transition-all duration-150 ease-out