Compare commits
3 Commits
14d7f0976c
...
4ba02b5933
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ba02b5933 | ||
|
|
3a2cc1c76b | ||
|
|
be267d43d8 |
20
dprint.json
20
dprint.json
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://dprint.dev/schemas/v0.json",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"includes": ["**/*.{ts,tsx,js,jsx,svelte,json,md}"],
|
"includes": ["**/*.{ts,tsx,js,jsx,svelte,json,md}"],
|
||||||
"excludes": [
|
"excludes": [
|
||||||
@@ -22,7 +23,17 @@
|
|||||||
"quoteStyle": "preferSingle",
|
"quoteStyle": "preferSingle",
|
||||||
"trailingCommas": "onlyMultiLine",
|
"trailingCommas": "onlyMultiLine",
|
||||||
"arrowFunction.useParentheses": "preferNone",
|
"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": {
|
"json": {
|
||||||
"indentWidth": 2,
|
"indentWidth": 2,
|
||||||
@@ -37,6 +48,11 @@
|
|||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"quotes": "double",
|
"quotes": "double",
|
||||||
"scriptIndent": false,
|
"scriptIndent": false,
|
||||||
"styleIndent": false
|
"styleIndent": false,
|
||||||
|
|
||||||
|
// Svelte-specific formatting
|
||||||
|
"vBindStyle": "short",
|
||||||
|
"vOnStyle": "short",
|
||||||
|
"formatComments": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import {
|
||||||
|
expect,
|
||||||
|
test,
|
||||||
|
} from '@playwright/test';
|
||||||
|
|
||||||
test('home page has expected h1', async ({ page }) => {
|
test('home page has expected h1', async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
|
|||||||
6
src/app/types/ambient.d.ts
vendored
6
src/app/types/ambient.d.ts
vendored
@@ -1,5 +1,9 @@
|
|||||||
declare module '*.svelte' {
|
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';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
interface Component {
|
interface Component {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
export type { FontCategory, FontProvider, FontSubset } from './model/font';
|
export type {
|
||||||
|
FontCategory,
|
||||||
|
FontProvider,
|
||||||
|
FontSubset,
|
||||||
|
} from './model/font';
|
||||||
export type {
|
export type {
|
||||||
FontshareApiModel,
|
FontshareApiModel,
|
||||||
FontshareDesigner,
|
FontshareDesigner,
|
||||||
@@ -10,4 +14,9 @@ export type {
|
|||||||
FontshareTag,
|
FontshareTag,
|
||||||
FontshareWeight,
|
FontshareWeight,
|
||||||
} from './model/fontshare_fonts';
|
} from './model/fontshare_fonts';
|
||||||
export type { FontFiles, FontItem, FontVariant, GoogleFontsApiModel } from './model/google_fonts';
|
export type {
|
||||||
|
FontFiles,
|
||||||
|
FontItem,
|
||||||
|
FontVariant,
|
||||||
|
GoogleFontsApiModel,
|
||||||
|
} from './model/google_fonts';
|
||||||
|
|||||||
@@ -2,4 +2,8 @@ import { FONT_CATEGORIES } from './model/state';
|
|||||||
import { categoryFilterStore } from './store/categoryFilterStore';
|
import { categoryFilterStore } from './store/categoryFilterStore';
|
||||||
import CategoryFilter from './ui/CategoryFilter.svelte';
|
import CategoryFilter from './ui/CategoryFilter.svelte';
|
||||||
|
|
||||||
export { CategoryFilter, categoryFilterStore, FONT_CATEGORIES };
|
export {
|
||||||
|
CategoryFilter,
|
||||||
|
categoryFilterStore,
|
||||||
|
FONT_CATEGORIES,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import { tv, type VariantProps } from 'tailwind-variants';
|
import {
|
||||||
|
type VariantProps,
|
||||||
|
tv,
|
||||||
|
} from 'tailwind-variants';
|
||||||
|
|
||||||
export const badgeVariants = tv({
|
export const badgeVariants = tv({
|
||||||
base:
|
base:
|
||||||
@@ -24,7 +27,10 @@ export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<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';
|
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
export { default as Badge } from './badge.svelte';
|
export { default as Badge } from './badge.svelte';
|
||||||
export { type BadgeVariant, badgeVariants } from './badge.svelte';
|
export {
|
||||||
|
type BadgeVariant,
|
||||||
|
badgeVariants,
|
||||||
|
} from './badge.svelte';
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
|
import {
|
||||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
type WithElementRef,
|
||||||
import { tv, type VariantProps } from 'tailwind-variants';
|
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({
|
export const buttonVariants = tv({
|
||||||
base:
|
base:
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 CheckIcon from '@lucide/svelte/icons/check';
|
||||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||||
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 XIcon from '@lucide/svelte/icons/x';
|
||||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from '$shared/shadcn/utils/shadcn-utils.js';
|
import {
|
||||||
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
|
type WithElementRef,
|
||||||
|
cn,
|
||||||
|
} from '$shared/shadcn/utils/shadcn-utils.js';
|
||||||
|
import type {
|
||||||
|
HTMLInputAttributes,
|
||||||
|
HTMLInputTypeAttribute,
|
||||||
|
} from 'svelte/elements';
|
||||||
|
|
||||||
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
|
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import { tv, type VariantProps } from 'tailwind-variants';
|
import {
|
||||||
|
type VariantProps,
|
||||||
|
tv,
|
||||||
|
} from 'tailwind-variants';
|
||||||
export const sheetVariants = tv({
|
export const sheetVariants = tv({
|
||||||
base:
|
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',
|
'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>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 XIcon from '@lucide/svelte/icons/x';
|
||||||
import { Dialog as SheetPrimitive } from 'bits-ui';
|
import { Dialog as SheetPrimitive } from 'bits-ui';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { IsMobile } from '$shared/shadcn/hooks/is-mobile.svelte.js';
|
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';
|
import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js';
|
||||||
|
|
||||||
type Getter<T> = () => T;
|
type Getter<T> = () => T;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 { Snippet } from 'svelte';
|
||||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 { Snippet } from 'svelte';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 { Snippet } from 'svelte';
|
||||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import { tv, type VariantProps } from 'tailwind-variants';
|
import {
|
||||||
|
type VariantProps,
|
||||||
|
tv,
|
||||||
|
} from 'tailwind-variants';
|
||||||
|
|
||||||
export const sidebarMenuButtonVariants = tv({
|
export const sidebarMenuButtonVariants = tv({
|
||||||
base:
|
base:
|
||||||
@@ -31,12 +34,15 @@ export type SidebarMenuButtonSize = VariantProps<typeof sidebarMenuButtonVariant
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Tooltip from '$shared/shadcn/ui/tooltip/index.js';
|
import * as Tooltip from '$shared/shadcn/ui/tooltip/index.js';
|
||||||
import {
|
import {
|
||||||
cn,
|
|
||||||
type WithElementRef,
|
type WithElementRef,
|
||||||
type WithoutChildrenOrChild,
|
type WithoutChildrenOrChild,
|
||||||
|
cn,
|
||||||
} from '$shared/shadcn/utils/shadcn-utils.js';
|
} from '$shared/shadcn/utils/shadcn-utils.js';
|
||||||
import { mergeProps } from 'bits-ui';
|
import { mergeProps } from 'bits-ui';
|
||||||
import type { ComponentProps, Snippet } from 'svelte';
|
import type {
|
||||||
|
ComponentProps,
|
||||||
|
Snippet,
|
||||||
|
} from 'svelte';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
import { useSidebar } from './context.svelte.js';
|
import { useSidebar } from './context.svelte.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Skeleton } from '$shared/shadcn/ui/skeleton/index.js';
|
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';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 { Snippet } from 'svelte';
|
||||||
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Tooltip from '$shared/shadcn/ui/tooltip/index.js';
|
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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
import {
|
import {
|
||||||
SIDEBAR_COOKIE_MAX_AGE,
|
SIDEBAR_COOKIE_MAX_AGE,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
import { useSidebar } from './context.svelte.js';
|
import { useSidebar } from './context.svelte.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Sheet from '$shared/shadcn/ui/sheet/index.js';
|
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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
import { SIDEBAR_WIDTH_MOBILE } from './constants.js';
|
import { SIDEBAR_WIDTH_MOBILE } from './constants.js';
|
||||||
import { useSidebar } from './context.svelte.js';
|
import { useSidebar } from './context.svelte.js';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
cn,
|
|
||||||
type WithElementRef,
|
type WithElementRef,
|
||||||
type WithoutChildren,
|
type WithoutChildren,
|
||||||
|
cn,
|
||||||
} from '$shared/shadcn/utils/shadcn-utils.js';
|
} from '$shared/shadcn/utils/shadcn-utils.js';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { type ClassValue, clsx } from 'clsx';
|
import {
|
||||||
|
type ClassValue,
|
||||||
|
clsx,
|
||||||
|
} from 'clsx';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 {
|
export interface Category {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,17 +10,37 @@ import { onMount } from 'svelte';
|
|||||||
import { cubicOut } from 'svelte/easing';
|
import { cubicOut } from 'svelte/easing';
|
||||||
import { slide } from 'svelte/transition';
|
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 {
|
interface CategoryFilterProps {
|
||||||
|
/** Display name for this filter group (e.g., "Categories", "Tags") */
|
||||||
filterName: string;
|
filterName: string;
|
||||||
|
/** Array of categories with their selection states */
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
|
/** Callback when a category checkbox is toggled */
|
||||||
onCategoryToggle: (id: string) => void;
|
onCategoryToggle: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { filterName, categories, onCategoryToggle }: CategoryFilterProps = $props();
|
const { filterName, categories, onCategoryToggle }: CategoryFilterProps = $props();
|
||||||
|
|
||||||
|
// Toggle state - defaults to open for better discoverability
|
||||||
let isOpen = $state(true);
|
let isOpen = $state(true);
|
||||||
|
// Accessibility preference to disable animations
|
||||||
let prefersReducedMotion = $state(false);
|
let prefersReducedMotion = $state(false);
|
||||||
|
|
||||||
|
// Check reduced motion preference on mount (window access required)
|
||||||
|
// Event listener allows responding to system preference changes
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
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({
|
const slideConfig = $derived({
|
||||||
duration: prefersReducedMotion ? 0 : 250,
|
duration: prefersReducedMotion ? 0 : 250,
|
||||||
easing: cubicOut,
|
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 selectedCount = $derived(categories.filter(c => c.selected).length);
|
||||||
const hasSelection = $derived(selectedCount > 0);
|
const hasSelection = $derived(selectedCount > 0);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Collapsible card wrapper with subtle hover state for affordance -->
|
||||||
<Collapsible.Root
|
<Collapsible.Root
|
||||||
bind:open={isOpen}
|
bind:open={isOpen}
|
||||||
class="w-full rounded-lg border bg-card transition-colors hover:bg-accent/5"
|
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">
|
<div class="flex items-center justify-between px-4 py-2">
|
||||||
<Collapsible.Trigger
|
<Collapsible.Trigger
|
||||||
class={buttonVariants({
|
class={buttonVariants({
|
||||||
@@ -61,12 +84,14 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
>
|
>
|
||||||
<h4 class="text-sm font-semibold">{filterName}</h4>
|
<h4 class="text-sm font-semibold">{filterName}</h4>
|
||||||
|
|
||||||
|
<!-- Chevron rotates based on open state for visual feedback -->
|
||||||
<div
|
<div
|
||||||
class="shrink-0 transition-transform duration-200 ease-out"
|
class="shrink-0 transition-transform duration-200 ease-out"
|
||||||
style:transform={isOpen ? 'rotate(0deg)' : 'rotate(-90deg)'}
|
style:transform={isOpen ? 'rotate(0deg)' : 'rotate(-90deg)'}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon class="h-4 w-4" />
|
<ChevronDownIcon class="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Badge only appears when items are selected to avoid clutter -->
|
||||||
{#if hasSelection}
|
{#if hasSelection}
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -78,6 +103,7 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
</Collapsible.Trigger>
|
</Collapsible.Trigger>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Expandable content with slide animation -->
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div
|
<div
|
||||||
transition:slide|local={slideConfig}
|
transition:slide|local={slideConfig}
|
||||||
@@ -85,6 +111,8 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
>
|
>
|
||||||
<div class="px-4 py-3">
|
<div class="px-4 py-3">
|
||||||
<div class="flex flex-col gap-2.5">
|
<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)}
|
{#each categories as category (category.id)}
|
||||||
<Label
|
<Label
|
||||||
for={category.id}
|
for={category.id}
|
||||||
@@ -95,6 +123,9 @@ const hasSelection = $derived(selectedCount > 0);
|
|||||||
active:scale-[0.98] active:transition-transform active:duration-75
|
active:scale-[0.98] active:transition-transform active:duration-75
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<!--
|
||||||
|
Checkbox handles toggle, styled for accessibility with focus rings
|
||||||
|
-->
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={category.id}
|
id={category.id}
|
||||||
checked={category.selected}
|
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
|
focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<!-- Label text changes weight/color based on selection state -->
|
||||||
<span
|
<span
|
||||||
class="
|
class="
|
||||||
text-sm select-none transition-all duration-150 ease-out
|
text-sm select-none transition-all duration-150 ease-out
|
||||||
|
|||||||
Reference in New Issue
Block a user