From 34b64420bf9b9ec208c049a02093d869c1225486 Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Mon, 16 Jun 2025 07:01:15 -0500 Subject: [PATCH] togglable sidebar --- convex.json | 2 +- package.json | 1 - pnpm-lock.yaml | 54 ----- src/lib/actions/shortcut.svelte.ts | 211 +++++++++++++++++++ src/lib/components/ui/button/button.svelte | 11 +- src/lib/components/ui/kbd/index.ts | 7 + src/lib/components/ui/kbd/kbd.svelte | 53 +++++ src/lib/components/ui/sidebar/sidebar.svelte | 3 + src/lib/hooks/is-mac.svelte.ts | 10 + src/routes/account/+layout.svelte | 19 +- 10 files changed, 309 insertions(+), 62 deletions(-) create mode 100644 src/lib/actions/shortcut.svelte.ts create mode 100644 src/lib/components/ui/kbd/index.ts create mode 100644 src/lib/components/ui/kbd/kbd.svelte create mode 100644 src/lib/hooks/is-mac.svelte.ts diff --git a/convex.json b/convex.json index d13b702..14af2a8 100644 --- a/convex.json +++ b/convex.json @@ -1,3 +1,3 @@ { - "functions": "src/lib/backend/convex" + "functions": "src/lib/backend/convex" } diff --git a/package.json b/package.json index 12b1975..dc7acaa 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "@tailwindcss/vite": "^4.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/svelte": "^5.2.4", - "bits-ui": "^2.6.2", "clsx": "^2.1.1", "concurrently": "^9.1.2", "convex": "^1.24.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34f5499..b6fb3f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,9 +48,6 @@ importers: '@testing-library/svelte': specifier: ^5.2.4 version: 5.2.8(svelte@5.34.1)(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1))(vitest@3.2.3(@types/node@24.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)) - bits-ui: - specifier: ^2.6.2 - version: 2.6.2(@internationalized/date@3.8.2)(svelte@5.34.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -596,9 +593,6 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} - '@internationalized/date@3.8.2': - resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -814,9 +808,6 @@ packages: svelte: ^5.0.0 vite: ^6.0.0 - '@swc/helpers@0.5.17': - resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} - '@tailwindcss/node@4.1.10': resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==} @@ -1104,13 +1095,6 @@ packages: better-call@1.0.9: resolution: {integrity: sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==} - bits-ui@2.6.2: - resolution: {integrity: sha512-OlPSUAT+ENhtRarPjABljca1cCljyoAqOZKfgjCB8PxQii2fL0AKnzObhnEdhZKwYdpXczEtNOYqUUNYwliaWA==} - engines: {node: '>=20', pnpm: '>=8.7.0'} - peerDependencies: - '@internationalized/date': ^3.8.1 - svelte: ^5.33.0 - brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -2144,12 +2128,6 @@ packages: peerDependencies: svelte: ^5.0.0 - svelte-toolbelt@0.9.1: - resolution: {integrity: sha512-wBX6MtYw/kpht80j5zLpxJyR9soLizXPIAIWEVd9llAi17SR44ZdG291bldjB7r/K5duC0opDFcuhk2cA1hb8g==} - engines: {node: '>=18', pnpm: '>=8.7.0'} - peerDependencies: - svelte: ^5.30.2 - svelte@5.34.1: resolution: {integrity: sha512-jWNnN2hZFNtnzKPptCcJHBWrD9CtbHPDwIRIODufOYaWkR0kLmAIlM384lMt4ucwuIRX4hCJwD2D8ZtEcGJQ0Q==} engines: {node: '>=18'} @@ -2157,9 +2135,6 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - tailwind-merge@3.0.2: resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==} @@ -2789,10 +2764,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@internationalized/date@3.8.2': - dependencies: - '@swc/helpers': 0.5.17 - '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -2998,10 +2969,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@swc/helpers@0.5.17': - dependencies: - tslib: 2.8.1 - '@tailwindcss/node@4.1.10': dependencies: '@ampproject/remapping': 2.3.0 @@ -3324,18 +3291,6 @@ snapshots: set-cookie-parser: 2.7.1 uncrypto: 0.1.3 - bits-ui@2.6.2(@internationalized/date@3.8.2)(svelte@5.34.1): - dependencies: - '@floating-ui/core': 1.7.1 - '@floating-ui/dom': 1.7.1 - '@internationalized/date': 3.8.2 - css.escape: 1.5.1 - esm-env: 1.2.2 - runed: 0.28.0(svelte@5.34.1) - svelte: 5.34.1 - svelte-toolbelt: 0.9.1(svelte@5.34.1) - tabbable: 6.2.0 - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -4294,13 +4249,6 @@ snapshots: style-to-object: 1.0.9 svelte: 5.34.1 - svelte-toolbelt@0.9.1(svelte@5.34.1): - dependencies: - clsx: 2.1.1 - runed: 0.28.0(svelte@5.34.1) - style-to-object: 1.0.9 - svelte: 5.34.1 - svelte@5.34.1: dependencies: '@ampproject/remapping': 2.3.0 @@ -4320,8 +4268,6 @@ snapshots: symbol-tree@3.2.4: {} - tabbable@6.2.0: {} - tailwind-merge@3.0.2: {} tailwind-merge@3.3.1: {} diff --git a/src/lib/actions/shortcut.svelte.ts b/src/lib/actions/shortcut.svelte.ts new file mode 100644 index 0000000..50c06a5 --- /dev/null +++ b/src/lib/actions/shortcut.svelte.ts @@ -0,0 +1,211 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import { createAttachmentKey } from 'svelte/attachments'; + +export type Options = { + /** Event to use to detect the shortcut @default 'keydown' */ + event?: 'keydown' | 'keyup' | 'keypress'; + /** Function to be called when the shortcut is pressed */ + callback: (e: KeyboardEvent) => void; + /** Should the `Shift` key be pressed */ + shift?: boolean; + /** Should the `Ctrl` / `Command` key be pressed */ + ctrl?: boolean; + /** Should the `Alt` key be pressed */ + alt?: boolean; + /** Which key should be pressed */ + key: Key; + /** Control whether or not the shortcut prevents default behavior @default true */ + preventDefault?: boolean; + /** Control whether or not the shortcut stops propagation @default false */ + stopPropagation?: boolean; +}; + +/** Allows you to configure one or more shortcuts based on the key events of an element. + * + * ## Usage + * ```svelte + * + * + * ``` + */ +export const shortcut = (node: HTMLElement, options: Options[] | Options) => { + const handleKeydown = (e: KeyboardEvent, options: Options) => { + if (options.ctrl && !e.ctrlKey && !e.metaKey) return; + + if (options.alt && !e.altKey) return; + + if (options.shift && !e.shiftKey) return; + + if (e.key.toLocaleLowerCase() !== options.key.toLocaleLowerCase()) return; + + if (options.preventDefault === undefined || options.preventDefault) { + e.preventDefault(); + } + + if (options.stopPropagation) { + e.stopPropagation(); + } + + options.callback(e); + }; + + $effect(() => { + let optionsArr: Options[] = []; + if (Array.isArray(options)) { + optionsArr = options; + } else { + optionsArr = [options]; + } + + for (const opt of optionsArr) { + node.addEventListener(opt.event ?? 'keydown', (e) => handleKeydown(e, opt)); + } + + return () => { + for (const opt of optionsArr) { + node.removeEventListener(opt.event ?? 'keydown', (e) => handleKeydown(e, opt)); + } + }; + }); +}; + +/** Allows you to configure one or more shortcuts based on the key events of an element. + * + * ## Usage + * ```svelte + * + * + * ``` + */ +export function attachShortcut(opts: Options[] | Options) { + return { + [createAttachmentKey()]: (node: HTMLElement) => shortcut(node, opts), + }; +} + +export type Key = + | 'backspace' + | 'tab' + | 'enter' + | 'shift(left)' + | 'shift(right)' + | 'ctrl(left)' + | 'ctrl(right)' + | 'alt(left)' + | 'alt(right)' + | 'pause/break' + | 'caps lock' + | 'escape' + | 'space' + | 'page up' + | 'page down' + | 'end' + | 'home' + | 'left arrow' + | 'up arrow' + | 'right arrow' + | 'down arrow' + | 'print screen' + | 'insert' + | 'delete' + | '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9' + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + | 'h' + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 't' + | 'u' + | 'v' + | 'w' + | 'x' + | 'y' + | 'z' + | 'left window key' + | 'right window key' + | 'select key (Context Menu)' + | 'numpad 0' + | 'numpad 1' + | 'numpad 2' + | 'numpad 3' + | 'numpad 4' + | 'numpad 5' + | 'numpad 6' + | 'numpad 7' + | 'numpad 8' + | 'numpad 9' + | 'multiply' + | 'add' + | 'subtract' + | 'decimal point' + | 'divide' + | 'f1' + | 'f2' + | 'f3' + | 'f4' + | 'f5' + | 'f6' + | 'f7' + | 'f8' + | 'f9' + | 'f10' + | 'f11' + | 'f12' + | 'num lock' + | 'scroll lock' + | 'audio volume mute' + | 'audio volume down' + | 'audio volume up' + | 'media player' + | 'launch application 1' + | 'launch application 2' + | 'semi-colon' + | 'equal sign' + | 'comma' + | 'dash' + | 'period' + | 'forward slash' + | 'Backquote/Grave accent' + | 'open bracket' + | 'back slash' + | 'close bracket' + | 'single quote'; diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte index a8db535..a9b71fe 100644 --- a/src/lib/components/ui/button/button.svelte +++ b/src/lib/components/ui/button/button.svelte @@ -3,7 +3,6 @@ --> + + + + + {@render children?.()} + diff --git a/src/lib/components/ui/sidebar/sidebar.svelte b/src/lib/components/ui/sidebar/sidebar.svelte index fd845a7..7cbd77c 100644 --- a/src/lib/components/ui/sidebar/sidebar.svelte +++ b/src/lib/components/ui/sidebar/sidebar.svelte @@ -2,12 +2,15 @@ import { cn } from '$lib/utils/utils'; import type { HTMLAttributes } from 'svelte/elements'; import { useSidebar } from './sidebar.svelte.js'; + import { shortcut } from '$lib/actions/shortcut.svelte.js'; let { children, ...rest }: HTMLAttributes = $props(); const sidebar = useSidebar(); + +
@@ -46,7 +50,7 @@
-
+