From 2bcded583d5fa0fdf500b3a76ab0e737174b3069 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 1 Jan 2026 13:12:57 +0300 Subject: [PATCH] feature: add necessary shadcn components for CategoryFilter and Sidebar --- src/shared/hooks/is-mobile.svelte.ts | 9 ++ src/shared/types/collection.ts | 21 ++++ src/shared/types/common.ts | 37 ++++++ src/shared/ui/button/button.svelte | 85 ++++++++++++++ src/shared/ui/button/index.ts | 17 +++ src/shared/ui/command/command-dialog.svelte | 42 +++++++ src/shared/ui/command/command-empty.svelte | 17 +++ src/shared/ui/command/command-group.svelte | 32 ++++++ src/shared/ui/command/command-input.svelte | 26 +++++ src/shared/ui/command/command-item.svelte | 20 ++++ .../ui/command/command-link-item.svelte | 20 ++++ src/shared/ui/command/command-list.svelte | 17 +++ src/shared/ui/command/command-loading.svelte | 7 ++ .../ui/command/command-separator.svelte | 17 +++ src/shared/ui/command/command-shortcut.svelte | 20 ++++ src/shared/ui/command/command.svelte | 28 +++++ src/shared/ui/command/index.ts | 37 ++++++ src/shared/ui/dialog/dialog-close.svelte | 7 ++ src/shared/ui/dialog/dialog-content.svelte | 45 ++++++++ .../ui/dialog/dialog-description.svelte | 17 +++ src/shared/ui/dialog/dialog-footer.svelte | 20 ++++ src/shared/ui/dialog/dialog-header.svelte | 20 ++++ src/shared/ui/dialog/dialog-overlay.svelte | 20 ++++ src/shared/ui/dialog/dialog-portal.svelte | 7 ++ src/shared/ui/dialog/dialog-title.svelte | 17 +++ src/shared/ui/dialog/dialog-trigger.svelte | 7 ++ src/shared/ui/dialog/dialog.svelte | 7 ++ src/shared/ui/dialog/index.ts | 34 ++++++ src/shared/ui/input/index.ts | 7 ++ src/shared/ui/input/input.svelte | 52 +++++++++ src/shared/ui/popover/index.ts | 19 +++ src/shared/ui/popover/popover-close.svelte | 7 ++ src/shared/ui/popover/popover-content.svelte | 31 +++++ src/shared/ui/popover/popover-portal.svelte | 7 ++ src/shared/ui/popover/popover-trigger.svelte | 17 +++ src/shared/ui/popover/popover.svelte | 7 ++ src/shared/ui/separator/index.ts | 7 ++ src/shared/ui/separator/separator.svelte | 21 ++++ src/shared/ui/sheet/index.ts | 34 ++++++ src/shared/ui/sheet/sheet-close.svelte | 7 ++ src/shared/ui/sheet/sheet-content.svelte | 64 +++++++++++ src/shared/ui/sheet/sheet-description.svelte | 17 +++ src/shared/ui/sheet/sheet-footer.svelte | 20 ++++ src/shared/ui/sheet/sheet-header.svelte | 20 ++++ src/shared/ui/sheet/sheet-overlay.svelte | 20 ++++ src/shared/ui/sheet/sheet-portal.svelte | 7 ++ src/shared/ui/sheet/sheet-title.svelte | 17 +++ src/shared/ui/sheet/sheet-trigger.svelte | 7 ++ src/shared/ui/sheet/sheet.svelte | 7 ++ src/shared/ui/sidebar/constants.ts | 6 + src/shared/ui/sidebar/context.svelte.ts | 81 +++++++++++++ src/shared/ui/sidebar/index.ts | 75 ++++++++++++ src/shared/ui/sidebar/sidebar-content.svelte | 24 ++++ src/shared/ui/sidebar/sidebar-footer.svelte | 21 ++++ .../ui/sidebar/sidebar-group-action.svelte | 36 ++++++ .../ui/sidebar/sidebar-group-content.svelte | 21 ++++ .../ui/sidebar/sidebar-group-label.svelte | 34 ++++++ src/shared/ui/sidebar/sidebar-group.svelte | 21 ++++ src/shared/ui/sidebar/sidebar-header.svelte | 21 ++++ src/shared/ui/sidebar/sidebar-input.svelte | 21 ++++ src/shared/ui/sidebar/sidebar-inset.svelte | 24 ++++ .../ui/sidebar/sidebar-menu-action.svelte | 43 +++++++ .../ui/sidebar/sidebar-menu-badge.svelte | 29 +++++ .../ui/sidebar/sidebar-menu-button.svelte | 108 ++++++++++++++++++ .../ui/sidebar/sidebar-menu-item.svelte | 21 ++++ .../ui/sidebar/sidebar-menu-skeleton.svelte | 36 ++++++ .../ui/sidebar/sidebar-menu-sub-button.svelte | 43 +++++++ .../ui/sidebar/sidebar-menu-sub-item.svelte | 21 ++++ src/shared/ui/sidebar/sidebar-menu-sub.svelte | 25 ++++ src/shared/ui/sidebar/sidebar-menu.svelte | 21 ++++ src/shared/ui/sidebar/sidebar-provider.svelte | 54 +++++++++ src/shared/ui/sidebar/sidebar-rail.svelte | 36 ++++++ .../ui/sidebar/sidebar-separator.svelte | 19 +++ src/shared/ui/sidebar/sidebar-trigger.svelte | 35 ++++++ src/shared/ui/sidebar/sidebar.svelte | 105 +++++++++++++++++ src/shared/ui/skeleton/index.ts | 7 ++ src/shared/ui/skeleton/skeleton.svelte | 18 +++ src/shared/ui/tooltip/index.ts | 19 +++ src/shared/ui/tooltip/tooltip-content.svelte | 53 +++++++++ src/shared/ui/tooltip/tooltip-portal.svelte | 7 ++ src/shared/ui/tooltip/tooltip-provider.svelte | 7 ++ src/shared/ui/tooltip/tooltip-trigger.svelte | 7 ++ src/shared/ui/tooltip/tooltip.svelte | 7 ++ src/shared/utils/shadcn-utils.ts | 13 +++ 84 files changed, 2167 insertions(+) create mode 100644 src/shared/hooks/is-mobile.svelte.ts create mode 100644 src/shared/types/collection.ts create mode 100644 src/shared/types/common.ts create mode 100644 src/shared/ui/button/button.svelte create mode 100644 src/shared/ui/button/index.ts create mode 100644 src/shared/ui/command/command-dialog.svelte create mode 100644 src/shared/ui/command/command-empty.svelte create mode 100644 src/shared/ui/command/command-group.svelte create mode 100644 src/shared/ui/command/command-input.svelte create mode 100644 src/shared/ui/command/command-item.svelte create mode 100644 src/shared/ui/command/command-link-item.svelte create mode 100644 src/shared/ui/command/command-list.svelte create mode 100644 src/shared/ui/command/command-loading.svelte create mode 100644 src/shared/ui/command/command-separator.svelte create mode 100644 src/shared/ui/command/command-shortcut.svelte create mode 100644 src/shared/ui/command/command.svelte create mode 100644 src/shared/ui/command/index.ts create mode 100644 src/shared/ui/dialog/dialog-close.svelte create mode 100644 src/shared/ui/dialog/dialog-content.svelte create mode 100644 src/shared/ui/dialog/dialog-description.svelte create mode 100644 src/shared/ui/dialog/dialog-footer.svelte create mode 100644 src/shared/ui/dialog/dialog-header.svelte create mode 100644 src/shared/ui/dialog/dialog-overlay.svelte create mode 100644 src/shared/ui/dialog/dialog-portal.svelte create mode 100644 src/shared/ui/dialog/dialog-title.svelte create mode 100644 src/shared/ui/dialog/dialog-trigger.svelte create mode 100644 src/shared/ui/dialog/dialog.svelte create mode 100644 src/shared/ui/dialog/index.ts create mode 100644 src/shared/ui/input/index.ts create mode 100644 src/shared/ui/input/input.svelte create mode 100644 src/shared/ui/popover/index.ts create mode 100644 src/shared/ui/popover/popover-close.svelte create mode 100644 src/shared/ui/popover/popover-content.svelte create mode 100644 src/shared/ui/popover/popover-portal.svelte create mode 100644 src/shared/ui/popover/popover-trigger.svelte create mode 100644 src/shared/ui/popover/popover.svelte create mode 100644 src/shared/ui/separator/index.ts create mode 100644 src/shared/ui/separator/separator.svelte create mode 100644 src/shared/ui/sheet/index.ts create mode 100644 src/shared/ui/sheet/sheet-close.svelte create mode 100644 src/shared/ui/sheet/sheet-content.svelte create mode 100644 src/shared/ui/sheet/sheet-description.svelte create mode 100644 src/shared/ui/sheet/sheet-footer.svelte create mode 100644 src/shared/ui/sheet/sheet-header.svelte create mode 100644 src/shared/ui/sheet/sheet-overlay.svelte create mode 100644 src/shared/ui/sheet/sheet-portal.svelte create mode 100644 src/shared/ui/sheet/sheet-title.svelte create mode 100644 src/shared/ui/sheet/sheet-trigger.svelte create mode 100644 src/shared/ui/sheet/sheet.svelte create mode 100644 src/shared/ui/sidebar/constants.ts create mode 100644 src/shared/ui/sidebar/context.svelte.ts create mode 100644 src/shared/ui/sidebar/index.ts create mode 100644 src/shared/ui/sidebar/sidebar-content.svelte create mode 100644 src/shared/ui/sidebar/sidebar-footer.svelte create mode 100644 src/shared/ui/sidebar/sidebar-group-action.svelte create mode 100644 src/shared/ui/sidebar/sidebar-group-content.svelte create mode 100644 src/shared/ui/sidebar/sidebar-group-label.svelte create mode 100644 src/shared/ui/sidebar/sidebar-group.svelte create mode 100644 src/shared/ui/sidebar/sidebar-header.svelte create mode 100644 src/shared/ui/sidebar/sidebar-input.svelte create mode 100644 src/shared/ui/sidebar/sidebar-inset.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-action.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-badge.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-button.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-item.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-skeleton.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-sub-button.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-sub-item.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu-sub.svelte create mode 100644 src/shared/ui/sidebar/sidebar-menu.svelte create mode 100644 src/shared/ui/sidebar/sidebar-provider.svelte create mode 100644 src/shared/ui/sidebar/sidebar-rail.svelte create mode 100644 src/shared/ui/sidebar/sidebar-separator.svelte create mode 100644 src/shared/ui/sidebar/sidebar-trigger.svelte create mode 100644 src/shared/ui/sidebar/sidebar.svelte create mode 100644 src/shared/ui/skeleton/index.ts create mode 100644 src/shared/ui/skeleton/skeleton.svelte create mode 100644 src/shared/ui/tooltip/index.ts create mode 100644 src/shared/ui/tooltip/tooltip-content.svelte create mode 100644 src/shared/ui/tooltip/tooltip-portal.svelte create mode 100644 src/shared/ui/tooltip/tooltip-provider.svelte create mode 100644 src/shared/ui/tooltip/tooltip-trigger.svelte create mode 100644 src/shared/ui/tooltip/tooltip.svelte create mode 100644 src/shared/utils/shadcn-utils.ts diff --git a/src/shared/hooks/is-mobile.svelte.ts b/src/shared/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000..2dfb0eb --- /dev/null +++ b/src/shared/hooks/is-mobile.svelte.ts @@ -0,0 +1,9 @@ +import { MediaQuery } from 'svelte/reactivity'; + +const DEFAULT_MOBILE_BREAKPOINT = 768; + +export class IsMobile extends MediaQuery { + constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) { + super(`max-width: ${breakpoint - 1}px`); + } +} diff --git a/src/shared/types/collection.ts b/src/shared/types/collection.ts new file mode 100644 index 0000000..79d4960 --- /dev/null +++ b/src/shared/types/collection.ts @@ -0,0 +1,21 @@ +/** + * 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 = Record & { + /** + * 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; +}; diff --git a/src/shared/types/common.ts b/src/shared/types/common.ts new file mode 100644 index 0000000..2c1f8c8 --- /dev/null +++ b/src/shared/types/common.ts @@ -0,0 +1,37 @@ +/** + * 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 { + /** + * Data + */ + data: T; + /** + * Status + */ + status: number; + /** + * Status text + */ + statusText: string; +} + +export type ApiResponse = ApiErrorResponse | ApiSuccessResponse; diff --git a/src/shared/ui/button/button.svelte b/src/shared/ui/button/button.svelte new file mode 100644 index 0000000..d1a962e --- /dev/null +++ b/src/shared/ui/button/button.svelte @@ -0,0 +1,85 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/src/shared/ui/button/index.ts b/src/shared/ui/button/index.ts new file mode 100644 index 0000000..b9e1882 --- /dev/null +++ b/src/shared/ui/button/index.ts @@ -0,0 +1,17 @@ +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, +}; diff --git a/src/shared/ui/command/command-dialog.svelte b/src/shared/ui/command/command-dialog.svelte new file mode 100644 index 0000000..47b5c08 --- /dev/null +++ b/src/shared/ui/command/command-dialog.svelte @@ -0,0 +1,42 @@ + + + + + {title} + {description} + + + + + diff --git a/src/shared/ui/command/command-empty.svelte b/src/shared/ui/command/command-empty.svelte new file mode 100644 index 0000000..b435270 --- /dev/null +++ b/src/shared/ui/command/command-empty.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/command/command-group.svelte b/src/shared/ui/command/command-group.svelte new file mode 100644 index 0000000..0be293c --- /dev/null +++ b/src/shared/ui/command/command-group.svelte @@ -0,0 +1,32 @@ + + + + {#if heading} + + {heading} + + {/if} + + diff --git a/src/shared/ui/command/command-input.svelte b/src/shared/ui/command/command-input.svelte new file mode 100644 index 0000000..4f4e13b --- /dev/null +++ b/src/shared/ui/command/command-input.svelte @@ -0,0 +1,26 @@ + + +
+ + +
diff --git a/src/shared/ui/command/command-item.svelte b/src/shared/ui/command/command-item.svelte new file mode 100644 index 0000000..ca6f82f --- /dev/null +++ b/src/shared/ui/command/command-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/shared/ui/command/command-link-item.svelte b/src/shared/ui/command/command-link-item.svelte new file mode 100644 index 0000000..20e3232 --- /dev/null +++ b/src/shared/ui/command/command-link-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/shared/ui/command/command-list.svelte b/src/shared/ui/command/command-list.svelte new file mode 100644 index 0000000..62740b7 --- /dev/null +++ b/src/shared/ui/command/command-list.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/command/command-loading.svelte b/src/shared/ui/command/command-loading.svelte new file mode 100644 index 0000000..fafe30d --- /dev/null +++ b/src/shared/ui/command/command-loading.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/command/command-separator.svelte b/src/shared/ui/command/command-separator.svelte new file mode 100644 index 0000000..6ff4e99 --- /dev/null +++ b/src/shared/ui/command/command-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/command/command-shortcut.svelte b/src/shared/ui/command/command-shortcut.svelte new file mode 100644 index 0000000..23376a5 --- /dev/null +++ b/src/shared/ui/command/command-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/shared/ui/command/command.svelte b/src/shared/ui/command/command.svelte new file mode 100644 index 0000000..7fdaa09 --- /dev/null +++ b/src/shared/ui/command/command.svelte @@ -0,0 +1,28 @@ + + + diff --git a/src/shared/ui/command/index.ts b/src/shared/ui/command/index.ts new file mode 100644 index 0000000..28c62c5 --- /dev/null +++ b/src/shared/ui/command/index.ts @@ -0,0 +1,37 @@ +import Dialog from './command-dialog.svelte'; +import Empty from './command-empty.svelte'; +import Group from './command-group.svelte'; +import Input from './command-input.svelte'; +import Item from './command-item.svelte'; +import LinkItem from './command-link-item.svelte'; +import List from './command-list.svelte'; +import Loading from './command-loading.svelte'; +import Separator from './command-separator.svelte'; +import Shortcut from './command-shortcut.svelte'; +import Root from './command.svelte'; + +export { + Dialog, + Dialog as CommandDialog, + Empty, + Empty as CommandEmpty, + Group, + Group as CommandGroup, + Input, + Input as CommandInput, + Item, + Item as CommandItem, + LinkItem, + LinkItem as CommandLinkItem, + List, + List as CommandList, + Loading, + Loading as CommandLoading, + Root, + // + Root as Command, + Separator, + Separator as CommandSeparator, + Shortcut, + Shortcut as CommandShortcut, +}; diff --git a/src/shared/ui/dialog/dialog-close.svelte b/src/shared/ui/dialog/dialog-close.svelte new file mode 100644 index 0000000..fcc21a3 --- /dev/null +++ b/src/shared/ui/dialog/dialog-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/dialog/dialog-content.svelte b/src/shared/ui/dialog/dialog-content.svelte new file mode 100644 index 0000000..013e377 --- /dev/null +++ b/src/shared/ui/dialog/dialog-content.svelte @@ -0,0 +1,45 @@ + + + + + + {@render children?.()} + {#if showCloseButton} + + + Close + + {/if} + + diff --git a/src/shared/ui/dialog/dialog-description.svelte b/src/shared/ui/dialog/dialog-description.svelte new file mode 100644 index 0000000..f8c55a6 --- /dev/null +++ b/src/shared/ui/dialog/dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/dialog/dialog-footer.svelte b/src/shared/ui/dialog/dialog-footer.svelte new file mode 100644 index 0000000..f4e8507 --- /dev/null +++ b/src/shared/ui/dialog/dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/dialog/dialog-header.svelte b/src/shared/ui/dialog/dialog-header.svelte new file mode 100644 index 0000000..2a9c5e3 --- /dev/null +++ b/src/shared/ui/dialog/dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/dialog/dialog-overlay.svelte b/src/shared/ui/dialog/dialog-overlay.svelte new file mode 100644 index 0000000..562a34b --- /dev/null +++ b/src/shared/ui/dialog/dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/shared/ui/dialog/dialog-portal.svelte b/src/shared/ui/dialog/dialog-portal.svelte new file mode 100644 index 0000000..2aa7e62 --- /dev/null +++ b/src/shared/ui/dialog/dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/dialog/dialog-title.svelte b/src/shared/ui/dialog/dialog-title.svelte new file mode 100644 index 0000000..b47352d --- /dev/null +++ b/src/shared/ui/dialog/dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/dialog/dialog-trigger.svelte b/src/shared/ui/dialog/dialog-trigger.svelte new file mode 100644 index 0000000..54402b0 --- /dev/null +++ b/src/shared/ui/dialog/dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/dialog/dialog.svelte b/src/shared/ui/dialog/dialog.svelte new file mode 100644 index 0000000..f5d105c --- /dev/null +++ b/src/shared/ui/dialog/dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/dialog/index.ts b/src/shared/ui/dialog/index.ts new file mode 100644 index 0000000..f10e275 --- /dev/null +++ b/src/shared/ui/dialog/index.ts @@ -0,0 +1,34 @@ +import Close from './dialog-close.svelte'; +import Content from './dialog-content.svelte'; +import Description from './dialog-description.svelte'; +import Footer from './dialog-footer.svelte'; +import Header from './dialog-header.svelte'; +import Overlay from './dialog-overlay.svelte'; +import Portal from './dialog-portal.svelte'; +import Title from './dialog-title.svelte'; +import Trigger from './dialog-trigger.svelte'; +import Root from './dialog.svelte'; + +export { + Close, + Close as DialogClose, + Content, + Content as DialogContent, + Description, + Description as DialogDescription, + Footer, + Footer as DialogFooter, + Header, + Header as DialogHeader, + Overlay, + Overlay as DialogOverlay, + Portal, + Portal as DialogPortal, + Root, + // + Root as Dialog, + Title, + Title as DialogTitle, + Trigger, + Trigger as DialogTrigger, +}; diff --git a/src/shared/ui/input/index.ts b/src/shared/ui/input/index.ts new file mode 100644 index 0000000..8c0f4f7 --- /dev/null +++ b/src/shared/ui/input/index.ts @@ -0,0 +1,7 @@ +import Root from './input.svelte'; + +export { + Root, + // + Root as Input, +}; diff --git a/src/shared/ui/input/input.svelte b/src/shared/ui/input/input.svelte new file mode 100644 index 0000000..9a530ee --- /dev/null +++ b/src/shared/ui/input/input.svelte @@ -0,0 +1,52 @@ + + +{#if type === 'file'} + +{:else} + +{/if} diff --git a/src/shared/ui/popover/index.ts b/src/shared/ui/popover/index.ts new file mode 100644 index 0000000..e5456bb --- /dev/null +++ b/src/shared/ui/popover/index.ts @@ -0,0 +1,19 @@ +import Close from './popover-close.svelte'; +import Content from './popover-content.svelte'; +import Portal from './popover-portal.svelte'; +import Trigger from './popover-trigger.svelte'; +import Root from './popover.svelte'; + +export { + Close, + Close as PopoverClose, + Content, + Content as PopoverContent, + Portal, + Portal as PopoverPortal, + Root, + // + Root as Popover, + Trigger, + Trigger as PopoverTrigger, +}; diff --git a/src/shared/ui/popover/popover-close.svelte b/src/shared/ui/popover/popover-close.svelte new file mode 100644 index 0000000..945ba11 --- /dev/null +++ b/src/shared/ui/popover/popover-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/popover/popover-content.svelte b/src/shared/ui/popover/popover-content.svelte new file mode 100644 index 0000000..e0b8b87 --- /dev/null +++ b/src/shared/ui/popover/popover-content.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/src/shared/ui/popover/popover-portal.svelte b/src/shared/ui/popover/popover-portal.svelte new file mode 100644 index 0000000..833dcac --- /dev/null +++ b/src/shared/ui/popover/popover-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/popover/popover-trigger.svelte b/src/shared/ui/popover/popover-trigger.svelte new file mode 100644 index 0000000..497e5b0 --- /dev/null +++ b/src/shared/ui/popover/popover-trigger.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/popover/popover.svelte b/src/shared/ui/popover/popover.svelte new file mode 100644 index 0000000..6834fa7 --- /dev/null +++ b/src/shared/ui/popover/popover.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/separator/index.ts b/src/shared/ui/separator/index.ts new file mode 100644 index 0000000..25e42eb --- /dev/null +++ b/src/shared/ui/separator/index.ts @@ -0,0 +1,7 @@ +import Root from './separator.svelte'; + +export { + Root, + // + Root as Separator, +}; diff --git a/src/shared/ui/separator/separator.svelte b/src/shared/ui/separator/separator.svelte new file mode 100644 index 0000000..f70bbb0 --- /dev/null +++ b/src/shared/ui/separator/separator.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/shared/ui/sheet/index.ts b/src/shared/ui/sheet/index.ts new file mode 100644 index 0000000..c2da041 --- /dev/null +++ b/src/shared/ui/sheet/index.ts @@ -0,0 +1,34 @@ +import Close from './sheet-close.svelte'; +import Content from './sheet-content.svelte'; +import Description from './sheet-description.svelte'; +import Footer from './sheet-footer.svelte'; +import Header from './sheet-header.svelte'; +import Overlay from './sheet-overlay.svelte'; +import Portal from './sheet-portal.svelte'; +import Title from './sheet-title.svelte'; +import Trigger from './sheet-trigger.svelte'; +import Root from './sheet.svelte'; + +export { + Close, + Close as SheetClose, + Content, + Content as SheetContent, + Description, + Description as SheetDescription, + Footer, + Footer as SheetFooter, + Header, + Header as SheetHeader, + Overlay, + Overlay as SheetOverlay, + Portal, + Portal as SheetPortal, + Root, + // + Root as Sheet, + Title, + Title as SheetTitle, + Trigger, + Trigger as SheetTrigger, +}; diff --git a/src/shared/ui/sheet/sheet-close.svelte b/src/shared/ui/sheet/sheet-close.svelte new file mode 100644 index 0000000..076cd5e --- /dev/null +++ b/src/shared/ui/sheet/sheet-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/sheet/sheet-content.svelte b/src/shared/ui/sheet/sheet-content.svelte new file mode 100644 index 0000000..3dd1e0e --- /dev/null +++ b/src/shared/ui/sheet/sheet-content.svelte @@ -0,0 +1,64 @@ + + + + + + + + {@render children?.()} + + + Close + + + diff --git a/src/shared/ui/sheet/sheet-description.svelte b/src/shared/ui/sheet/sheet-description.svelte new file mode 100644 index 0000000..9265138 --- /dev/null +++ b/src/shared/ui/sheet/sheet-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/sheet/sheet-footer.svelte b/src/shared/ui/sheet/sheet-footer.svelte new file mode 100644 index 0000000..a30ab12 --- /dev/null +++ b/src/shared/ui/sheet/sheet-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sheet/sheet-header.svelte b/src/shared/ui/sheet/sheet-header.svelte new file mode 100644 index 0000000..a46a807 --- /dev/null +++ b/src/shared/ui/sheet/sheet-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sheet/sheet-overlay.svelte b/src/shared/ui/sheet/sheet-overlay.svelte new file mode 100644 index 0000000..3bcd5fb --- /dev/null +++ b/src/shared/ui/sheet/sheet-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/shared/ui/sheet/sheet-portal.svelte b/src/shared/ui/sheet/sheet-portal.svelte new file mode 100644 index 0000000..361356a --- /dev/null +++ b/src/shared/ui/sheet/sheet-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/sheet/sheet-title.svelte b/src/shared/ui/sheet/sheet-title.svelte new file mode 100644 index 0000000..bb648af --- /dev/null +++ b/src/shared/ui/sheet/sheet-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/shared/ui/sheet/sheet-trigger.svelte b/src/shared/ui/sheet/sheet-trigger.svelte new file mode 100644 index 0000000..5b091b7 --- /dev/null +++ b/src/shared/ui/sheet/sheet-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/sheet/sheet.svelte b/src/shared/ui/sheet/sheet.svelte new file mode 100644 index 0000000..18d493f --- /dev/null +++ b/src/shared/ui/sheet/sheet.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/sidebar/constants.ts b/src/shared/ui/sidebar/constants.ts new file mode 100644 index 0000000..2d3bbfb --- /dev/null +++ b/src/shared/ui/sidebar/constants.ts @@ -0,0 +1,6 @@ +export const SIDEBAR_COOKIE_NAME = 'sidebar:state'; +export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +export const SIDEBAR_WIDTH = '16rem'; +export const SIDEBAR_WIDTH_MOBILE = '18rem'; +export const SIDEBAR_WIDTH_ICON = '3rem'; +export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; diff --git a/src/shared/ui/sidebar/context.svelte.ts b/src/shared/ui/sidebar/context.svelte.ts new file mode 100644 index 0000000..4b592d3 --- /dev/null +++ b/src/shared/ui/sidebar/context.svelte.ts @@ -0,0 +1,81 @@ +import { IsMobile } from '$shared/hooks/is-mobile.svelte.js'; +import { getContext, setContext } from 'svelte'; +import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js'; + +type Getter = () => T; + +export type SidebarStateProps = { + /** + * A getter function that returns the current open state of the sidebar. + * We use a getter function here to support `bind:open` on the `Sidebar.Provider` + * component. + */ + open: Getter; + + /** + * A function that sets the open state of the sidebar. To support `bind:open`, we need + * a source of truth for changing the open state to ensure it will be synced throughout + * the sub-components and any `bind:` references. + */ + setOpen: (open: boolean) => void; +}; + +class SidebarState { + readonly props: SidebarStateProps; + open = $derived.by(() => this.props.open()); + openMobile = $state(false); + setOpen: SidebarStateProps['setOpen']; + #isMobile: IsMobile; + state = $derived.by(() => (this.open ? 'expanded' : 'collapsed')); + + constructor(props: SidebarStateProps) { + this.setOpen = props.setOpen; + this.#isMobile = new IsMobile(); + this.props = props; + } + + // Convenience getter for checking if the sidebar is mobile + // without this, we would need to use `sidebar.isMobile.current` everywhere + get isMobile() { + return this.#isMobile.current; + } + + // Event handler to apply to the `` + handleShortcutKeydown = (e: KeyboardEvent) => { + if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + this.toggle(); + } + }; + + setOpenMobile = (value: boolean) => { + this.openMobile = value; + }; + + toggle = () => { + return this.#isMobile.current + ? (this.openMobile = !this.openMobile) + : this.setOpen(!this.open); + }; +} + +const SYMBOL_KEY = 'scn-sidebar'; + +/** + * Instantiates a new `SidebarState` instance and sets it in the context. + * + * @param props The constructor props for the `SidebarState` class. + * @returns The `SidebarState` instance. + */ +export function setSidebar(props: SidebarStateProps): SidebarState { + return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props)); +} + +/** + * Retrieves the `SidebarState` instance from the context. This is a class instance, + * so you cannot destructure it. + * @returns The `SidebarState` instance. + */ +export function useSidebar(): SidebarState { + return getContext(Symbol.for(SYMBOL_KEY)); +} diff --git a/src/shared/ui/sidebar/index.ts b/src/shared/ui/sidebar/index.ts new file mode 100644 index 0000000..cbafa36 --- /dev/null +++ b/src/shared/ui/sidebar/index.ts @@ -0,0 +1,75 @@ +import { useSidebar } from './context.svelte.js'; +import Content from './sidebar-content.svelte'; +import Footer from './sidebar-footer.svelte'; +import GroupAction from './sidebar-group-action.svelte'; +import GroupContent from './sidebar-group-content.svelte'; +import GroupLabel from './sidebar-group-label.svelte'; +import Group from './sidebar-group.svelte'; +import Header from './sidebar-header.svelte'; +import Input from './sidebar-input.svelte'; +import Inset from './sidebar-inset.svelte'; +import MenuAction from './sidebar-menu-action.svelte'; +import MenuBadge from './sidebar-menu-badge.svelte'; +import MenuButton from './sidebar-menu-button.svelte'; +import MenuItem from './sidebar-menu-item.svelte'; +import MenuSkeleton from './sidebar-menu-skeleton.svelte'; +import MenuSubButton from './sidebar-menu-sub-button.svelte'; +import MenuSubItem from './sidebar-menu-sub-item.svelte'; +import MenuSub from './sidebar-menu-sub.svelte'; +import Menu from './sidebar-menu.svelte'; +import Provider from './sidebar-provider.svelte'; +import Rail from './sidebar-rail.svelte'; +import Separator from './sidebar-separator.svelte'; +import Trigger from './sidebar-trigger.svelte'; +import Root from './sidebar.svelte'; + +export { + Content, + Content as SidebarContent, + Footer, + Footer as SidebarFooter, + Group, + Group as SidebarGroup, + GroupAction, + GroupAction as SidebarGroupAction, + GroupContent, + GroupContent as SidebarGroupContent, + GroupLabel, + GroupLabel as SidebarGroupLabel, + Header, + Header as SidebarHeader, + Input, + Input as SidebarInput, + Inset, + Inset as SidebarInset, + Menu, + Menu as SidebarMenu, + MenuAction, + MenuAction as SidebarMenuAction, + MenuBadge, + MenuBadge as SidebarMenuBadge, + MenuButton, + MenuButton as SidebarMenuButton, + MenuItem, + MenuItem as SidebarMenuItem, + MenuSkeleton, + MenuSkeleton as SidebarMenuSkeleton, + MenuSub, + MenuSub as SidebarMenuSub, + MenuSubButton, + MenuSubButton as SidebarMenuSubButton, + MenuSubItem, + MenuSubItem as SidebarMenuSubItem, + Provider, + Provider as SidebarProvider, + Rail, + Rail as SidebarRail, + Root, + // + Root as Sidebar, + Separator, + Separator as SidebarSeparator, + Trigger, + Trigger as SidebarTrigger, + useSidebar, +}; diff --git a/src/shared/ui/sidebar/sidebar-content.svelte b/src/shared/ui/sidebar/sidebar-content.svelte new file mode 100644 index 0000000..9de2092 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-content.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sidebar/sidebar-footer.svelte b/src/shared/ui/sidebar/sidebar-footer.svelte new file mode 100644 index 0000000..5b271e4 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-footer.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sidebar/sidebar-group-action.svelte b/src/shared/ui/sidebar/sidebar-group-action.svelte new file mode 100644 index 0000000..012677b --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-group-action.svelte @@ -0,0 +1,36 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + +{/if} diff --git a/src/shared/ui/sidebar/sidebar-group-content.svelte b/src/shared/ui/sidebar/sidebar-group-content.svelte new file mode 100644 index 0000000..029f07f --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-group-content.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sidebar/sidebar-group-label.svelte b/src/shared/ui/sidebar/sidebar-group-label.svelte new file mode 100644 index 0000000..fc3ec11 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-group-label.svelte @@ -0,0 +1,34 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
+ {@render children?.()} +
+{/if} diff --git a/src/shared/ui/sidebar/sidebar-group.svelte b/src/shared/ui/sidebar/sidebar-group.svelte new file mode 100644 index 0000000..69d2d01 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-group.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sidebar/sidebar-header.svelte b/src/shared/ui/sidebar/sidebar-header.svelte new file mode 100644 index 0000000..88f998a --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-header.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sidebar/sidebar-input.svelte b/src/shared/ui/sidebar/sidebar-input.svelte new file mode 100644 index 0000000..7c7e9d0 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-input.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/shared/ui/sidebar/sidebar-inset.svelte b/src/shared/ui/sidebar/sidebar-inset.svelte new file mode 100644 index 0000000..12bb87c --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-inset.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sidebar/sidebar-menu-action.svelte b/src/shared/ui/sidebar/sidebar-menu-action.svelte new file mode 100644 index 0000000..584fa17 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-action.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + +{/if} diff --git a/src/shared/ui/sidebar/sidebar-menu-badge.svelte b/src/shared/ui/sidebar/sidebar-menu-badge.svelte new file mode 100644 index 0000000..2408f69 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-badge.svelte @@ -0,0 +1,29 @@ + + +
+ {@render children?.()} +
diff --git a/src/shared/ui/sidebar/sidebar-menu-button.svelte b/src/shared/ui/sidebar/sidebar-menu-button.svelte new file mode 100644 index 0000000..d2baba3 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-button.svelte @@ -0,0 +1,108 @@ + + + + +{#snippet Button({ props }: { props?: Record })} + {@const mergedProps = mergeProps(buttonProps, props)} + {#if child} + {@render child({ props: mergedProps })} + {:else} + + {/if} +{/snippet} + +{#if !tooltipContent} + {@render Button({})} +{:else} + + + {#snippet child({ props })} + {@render Button({ props })} + {/snippet} + + + +{/if} diff --git a/src/shared/ui/sidebar/sidebar-menu-item.svelte b/src/shared/ui/sidebar/sidebar-menu-item.svelte new file mode 100644 index 0000000..967b2fb --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-item.svelte @@ -0,0 +1,21 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/shared/ui/sidebar/sidebar-menu-skeleton.svelte b/src/shared/ui/sidebar/sidebar-menu-skeleton.svelte new file mode 100644 index 0000000..4a118f9 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-skeleton.svelte @@ -0,0 +1,36 @@ + + +
    + {#if showIcon} + + {/if} + + {@render children?.()} +
    diff --git a/src/shared/ui/sidebar/sidebar-menu-sub-button.svelte b/src/shared/ui/sidebar/sidebar-menu-sub-button.svelte new file mode 100644 index 0000000..6b80a84 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-sub-button.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + + {@render children?.()} + +{/if} diff --git a/src/shared/ui/sidebar/sidebar-menu-sub-item.svelte b/src/shared/ui/sidebar/sidebar-menu-sub-item.svelte new file mode 100644 index 0000000..467122f --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-sub-item.svelte @@ -0,0 +1,21 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/shared/ui/sidebar/sidebar-menu-sub.svelte b/src/shared/ui/sidebar/sidebar-menu-sub.svelte new file mode 100644 index 0000000..a0cb453 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu-sub.svelte @@ -0,0 +1,25 @@ + + +
      + {@render children?.()} +
    diff --git a/src/shared/ui/sidebar/sidebar-menu.svelte b/src/shared/ui/sidebar/sidebar-menu.svelte new file mode 100644 index 0000000..1308df6 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-menu.svelte @@ -0,0 +1,21 @@ + + +
      + {@render children?.()} +
    diff --git a/src/shared/ui/sidebar/sidebar-provider.svelte b/src/shared/ui/sidebar/sidebar-provider.svelte new file mode 100644 index 0000000..fd708c4 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-provider.svelte @@ -0,0 +1,54 @@ + + + + + +
    + {@render children?.()} +
    +
    diff --git a/src/shared/ui/sidebar/sidebar-rail.svelte b/src/shared/ui/sidebar/sidebar-rail.svelte new file mode 100644 index 0000000..4014668 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-rail.svelte @@ -0,0 +1,36 @@ + + + diff --git a/src/shared/ui/sidebar/sidebar-separator.svelte b/src/shared/ui/sidebar/sidebar-separator.svelte new file mode 100644 index 0000000..5b29c0a --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-separator.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/shared/ui/sidebar/sidebar-trigger.svelte b/src/shared/ui/sidebar/sidebar-trigger.svelte new file mode 100644 index 0000000..a0ac126 --- /dev/null +++ b/src/shared/ui/sidebar/sidebar-trigger.svelte @@ -0,0 +1,35 @@ + + + diff --git a/src/shared/ui/sidebar/sidebar.svelte b/src/shared/ui/sidebar/sidebar.svelte new file mode 100644 index 0000000..11e241e --- /dev/null +++ b/src/shared/ui/sidebar/sidebar.svelte @@ -0,0 +1,105 @@ + + +{#if collapsible === 'none'} +
    + {@render children?.()} +
    +{:else if sidebar.isMobile} + sidebar.openMobile, v => sidebar.setOpenMobile(v)} + {...restProps} + > + + + Sidebar + Displays the mobile sidebar. + +
    + {@render children?.()} +
    +
    +
    +{:else} + +{/if} diff --git a/src/shared/ui/skeleton/index.ts b/src/shared/ui/skeleton/index.ts new file mode 100644 index 0000000..8b4fb82 --- /dev/null +++ b/src/shared/ui/skeleton/index.ts @@ -0,0 +1,7 @@ +import Root from './skeleton.svelte'; + +export { + Root, + // + Root as Skeleton, +}; diff --git a/src/shared/ui/skeleton/skeleton.svelte b/src/shared/ui/skeleton/skeleton.svelte new file mode 100644 index 0000000..53d4de0 --- /dev/null +++ b/src/shared/ui/skeleton/skeleton.svelte @@ -0,0 +1,18 @@ + + +
    +
    diff --git a/src/shared/ui/tooltip/index.ts b/src/shared/ui/tooltip/index.ts new file mode 100644 index 0000000..247b714 --- /dev/null +++ b/src/shared/ui/tooltip/index.ts @@ -0,0 +1,19 @@ +import Content from './tooltip-content.svelte'; +import Portal from './tooltip-portal.svelte'; +import Provider from './tooltip-provider.svelte'; +import Trigger from './tooltip-trigger.svelte'; +import Root from './tooltip.svelte'; + +export { + Content, + Content as TooltipContent, + Portal, + Portal as TooltipPortal, + Provider, + Provider as TooltipProvider, + Root, + // + Root as Tooltip, + Trigger, + Trigger as TooltipTrigger, +}; diff --git a/src/shared/ui/tooltip/tooltip-content.svelte b/src/shared/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..a10d2f5 --- /dev/null +++ b/src/shared/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,53 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
    +
    + {/snippet} +
    +
    +
    diff --git a/src/shared/ui/tooltip/tooltip-portal.svelte b/src/shared/ui/tooltip/tooltip-portal.svelte new file mode 100644 index 0000000..0a846ec --- /dev/null +++ b/src/shared/ui/tooltip/tooltip-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/tooltip/tooltip-provider.svelte b/src/shared/ui/tooltip/tooltip-provider.svelte new file mode 100644 index 0000000..4ba9734 --- /dev/null +++ b/src/shared/ui/tooltip/tooltip-provider.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/tooltip/tooltip-trigger.svelte b/src/shared/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000..5ece1b5 --- /dev/null +++ b/src/shared/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/ui/tooltip/tooltip.svelte b/src/shared/ui/tooltip/tooltip.svelte new file mode 100644 index 0000000..74dbe52 --- /dev/null +++ b/src/shared/ui/tooltip/tooltip.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/shared/utils/shadcn-utils.ts b/src/shared/utils/shadcn-utils.ts new file mode 100644 index 0000000..f5acaf9 --- /dev/null +++ b/src/shared/utils/shadcn-utils.ts @@ -0,0 +1,13 @@ +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 extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null };