togglable sidebar
This commit is contained in:
parent
1aaae9b8f8
commit
34b64420bf
10 changed files with 309 additions and 62 deletions
|
|
@ -28,7 +28,6 @@
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/svelte": "^5.2.4",
|
"@testing-library/svelte": "^5.2.4",
|
||||||
"bits-ui": "^2.6.2",
|
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"convex": "^1.24.8",
|
"convex": "^1.24.8",
|
||||||
|
|
|
||||||
54
pnpm-lock.yaml
generated
54
pnpm-lock.yaml
generated
|
|
@ -48,9 +48,6 @@ importers:
|
||||||
'@testing-library/svelte':
|
'@testing-library/svelte':
|
||||||
specifier: ^5.2.4
|
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))
|
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:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
|
@ -596,9 +593,6 @@ packages:
|
||||||
'@iconify/utils@2.3.0':
|
'@iconify/utils@2.3.0':
|
||||||
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
|
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
|
||||||
|
|
||||||
'@internationalized/date@3.8.2':
|
|
||||||
resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==}
|
|
||||||
|
|
||||||
'@isaacs/fs-minipass@4.0.1':
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
@ -814,9 +808,6 @@ packages:
|
||||||
svelte: ^5.0.0
|
svelte: ^5.0.0
|
||||||
vite: ^6.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':
|
'@tailwindcss/node@4.1.10':
|
||||||
resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==}
|
resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==}
|
||||||
|
|
||||||
|
|
@ -1104,13 +1095,6 @@ packages:
|
||||||
better-call@1.0.9:
|
better-call@1.0.9:
|
||||||
resolution: {integrity: sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==}
|
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:
|
brace-expansion@1.1.12:
|
||||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||||
|
|
||||||
|
|
@ -2144,12 +2128,6 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: ^5.0.0
|
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:
|
svelte@5.34.1:
|
||||||
resolution: {integrity: sha512-jWNnN2hZFNtnzKPptCcJHBWrD9CtbHPDwIRIODufOYaWkR0kLmAIlM384lMt4ucwuIRX4hCJwD2D8ZtEcGJQ0Q==}
|
resolution: {integrity: sha512-jWNnN2hZFNtnzKPptCcJHBWrD9CtbHPDwIRIODufOYaWkR0kLmAIlM384lMt4ucwuIRX4hCJwD2D8ZtEcGJQ0Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
@ -2157,9 +2135,6 @@ packages:
|
||||||
symbol-tree@3.2.4:
|
symbol-tree@3.2.4:
|
||||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||||
|
|
||||||
tabbable@6.2.0:
|
|
||||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
|
||||||
|
|
||||||
tailwind-merge@3.0.2:
|
tailwind-merge@3.0.2:
|
||||||
resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
|
resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
|
||||||
|
|
||||||
|
|
@ -2789,10 +2764,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@internationalized/date@3.8.2':
|
|
||||||
dependencies:
|
|
||||||
'@swc/helpers': 0.5.17
|
|
||||||
|
|
||||||
'@isaacs/fs-minipass@4.0.1':
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 7.1.2
|
minipass: 7.1.2
|
||||||
|
|
@ -2998,10 +2969,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@swc/helpers@0.5.17':
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@tailwindcss/node@4.1.10':
|
'@tailwindcss/node@4.1.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
|
|
@ -3324,18 +3291,6 @@ snapshots:
|
||||||
set-cookie-parser: 2.7.1
|
set-cookie-parser: 2.7.1
|
||||||
uncrypto: 0.1.3
|
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:
|
brace-expansion@1.1.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
|
|
@ -4294,13 +4249,6 @@ snapshots:
|
||||||
style-to-object: 1.0.9
|
style-to-object: 1.0.9
|
||||||
svelte: 5.34.1
|
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:
|
svelte@5.34.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
|
|
@ -4320,8 +4268,6 @@ snapshots:
|
||||||
|
|
||||||
symbol-tree@3.2.4: {}
|
symbol-tree@3.2.4: {}
|
||||||
|
|
||||||
tabbable@6.2.0: {}
|
|
||||||
|
|
||||||
tailwind-merge@3.0.2: {}
|
tailwind-merge@3.0.2: {}
|
||||||
|
|
||||||
tailwind-merge@3.3.1: {}
|
tailwind-merge@3.3.1: {}
|
||||||
|
|
|
||||||
211
src/lib/actions/shortcut.svelte.ts
Normal file
211
src/lib/actions/shortcut.svelte.ts
Normal file
|
|
@ -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
|
||||||
|
* <!-- Ctrl + K Shortcut -->
|
||||||
|
* <svelte:window use:shortcut={
|
||||||
|
* {
|
||||||
|
* ctrl: true,
|
||||||
|
* key: 'k',
|
||||||
|
* callback: commandMenu.toggle
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* <!-- Ctrl + K Shortcut -->
|
||||||
|
* <svelte:window
|
||||||
|
* {...attachShortcut({
|
||||||
|
* ctrl: true,
|
||||||
|
* key: 'k',
|
||||||
|
* callback: commandMenu.toggle
|
||||||
|
* })}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
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';
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import type { WithChildren, WithoutChildren } from 'bits-ui';
|
|
||||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
||||||
import { type VariantProps, tv } from 'tailwind-variants';
|
import { type VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@
|
||||||
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
|
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
|
||||||
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
|
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
|
||||||
|
|
||||||
export type ButtonPropsWithoutHTML = WithChildren<{
|
export type ButtonPropsWithoutHTML = {
|
||||||
ref?: HTMLElement | null;
|
ref?: HTMLElement | null;
|
||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
|
|
@ -46,17 +45,18 @@
|
||||||
currentTarget: EventTarget & HTMLButtonElement;
|
currentTarget: EventTarget & HTMLButtonElement;
|
||||||
}
|
}
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
}>;
|
children?: Snippet<[]>;
|
||||||
|
};
|
||||||
|
|
||||||
export type AnchorElementProps = ButtonPropsWithoutHTML &
|
export type AnchorElementProps = ButtonPropsWithoutHTML &
|
||||||
WithoutChildren<Omit<HTMLAnchorAttributes, 'href' | 'type'>> & {
|
Omit<HTMLAnchorAttributes, 'href' | 'type' | 'children'> & {
|
||||||
href: HTMLAnchorAttributes['href'];
|
href: HTMLAnchorAttributes['href'];
|
||||||
type?: never;
|
type?: never;
|
||||||
disabled?: HTMLButtonAttributes['disabled'];
|
disabled?: HTMLButtonAttributes['disabled'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ButtonElementProps = ButtonPropsWithoutHTML &
|
export type ButtonElementProps = ButtonPropsWithoutHTML &
|
||||||
WithoutChildren<Omit<HTMLButtonAttributes, 'type' | 'href'>> & {
|
Omit<HTMLButtonAttributes, 'type' | 'href' | 'children'> & {
|
||||||
type?: HTMLButtonAttributes['type'];
|
type?: HTMLButtonAttributes['type'];
|
||||||
href?: never;
|
href?: never;
|
||||||
disabled?: HTMLButtonAttributes['disabled'];
|
disabled?: HTMLButtonAttributes['disabled'];
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn } from '$lib/utils/utils.js';
|
import { cn } from '$lib/utils/utils.js';
|
||||||
import LoaderCircleIcon from '~icons/lucide/loader-circle';
|
import LoaderCircleIcon from '~icons/lucide/loader-circle';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
|
|
|
||||||
7
src/lib/components/ui/kbd/index.ts
Normal file
7
src/lib/components/ui/kbd/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
Installed from @ieedan/shadcn-svelte-extras
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Kbd from './kbd.svelte';
|
||||||
|
|
||||||
|
export { Kbd };
|
||||||
53
src/lib/components/ui/kbd/kbd.svelte
Normal file
53
src/lib/components/ui/kbd/kbd.svelte
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<!--
|
||||||
|
Installed from @ieedan/shadcn-svelte-extras
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts" module>
|
||||||
|
import { tv, type VariantProps } from 'tailwind-variants';
|
||||||
|
|
||||||
|
const style = tv({
|
||||||
|
base: 'inline-flex place-items-center justify-center gap-1 rounded-md p-0.5',
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
outline: 'border-border bg-background text-muted-foreground border',
|
||||||
|
secondary: 'bg-secondary text-muted-foreground',
|
||||||
|
primary: 'bg-primary text-primary-foreground',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
sm: 'min-w-6 gap-1.5 p-0.5 px-1 text-sm',
|
||||||
|
default: 'min-w-8 gap-1.5 p-1 px-2',
|
||||||
|
lg: 'min-w-9 gap-2 p-1 px-3 text-lg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Size = VariantProps<typeof style>['size'];
|
||||||
|
type Variant = VariantProps<typeof style>['variant'];
|
||||||
|
|
||||||
|
export type KbdPropsWithoutHTML = {
|
||||||
|
ref?: HTMLElement | null;
|
||||||
|
class?: string;
|
||||||
|
size?: Size;
|
||||||
|
variant?: Variant;
|
||||||
|
children?: Snippet<[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KbdProps = KbdPropsWithoutHTML;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/utils/utils';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
size = 'sm',
|
||||||
|
variant = 'outline',
|
||||||
|
children,
|
||||||
|
}: KbdProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<kbd bind:this={ref} class={cn(style({ size, variant }), className)}>
|
||||||
|
{@render children?.()}
|
||||||
|
</kbd>
|
||||||
|
|
@ -2,12 +2,15 @@
|
||||||
import { cn } from '$lib/utils/utils';
|
import { cn } from '$lib/utils/utils';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
import { useSidebar } from './sidebar.svelte.js';
|
import { useSidebar } from './sidebar.svelte.js';
|
||||||
|
import { shortcut } from '$lib/actions/shortcut.svelte.js';
|
||||||
|
|
||||||
let { children, ...rest }: HTMLAttributes<HTMLDivElement> = $props();
|
let { children, ...rest }: HTMLAttributes<HTMLDivElement> = $props();
|
||||||
|
|
||||||
const sidebar = useSidebar();
|
const sidebar = useSidebar();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window use:shortcut={{ key: 'b', ctrl: true, callback: sidebar.toggle }} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
{...rest}
|
{...rest}
|
||||||
class={cn('[--sidebar-width:0px] md:grid md:grid-cols-[var(--sidebar-width)_1fr]', {
|
class={cn('[--sidebar-width:0px] md:grid md:grid-cols-[var(--sidebar-width)_1fr]', {
|
||||||
|
|
|
||||||
10
src/lib/hooks/is-mac.svelte.ts
Normal file
10
src/lib/hooks/is-mac.svelte.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
Installed from @ieedan/shadcn-svelte-extras
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
/** Attempts to determine if a user is on a Mac using `navigator.userAgent`. */
|
||||||
|
export class IsMac {
|
||||||
|
readonly current = $derived(browser ? navigator.userAgent.includes('Mac') : false);
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
import { LightSwitch } from '$lib/components/ui/light-switch';
|
import { LightSwitch } from '$lib/components/ui/light-switch';
|
||||||
import ArrowLeftIcon from '~icons/lucide/arrow-left';
|
import ArrowLeftIcon from '~icons/lucide/arrow-left';
|
||||||
import { Avatar } from 'melt/components';
|
import { Avatar } from 'melt/components';
|
||||||
|
import { Kbd } from '$lib/components/ui/kbd/index.js';
|
||||||
|
import { IsMac } from '$lib/hooks/is-mac.svelte.js';
|
||||||
|
|
||||||
let { data, children } = $props();
|
let { data, children } = $props();
|
||||||
|
|
||||||
|
|
@ -33,6 +35,8 @@
|
||||||
|
|
||||||
await goto('/login');
|
await goto('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMac = new IsMac();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container mx-auto max-w-[1200px] space-y-8 pt-6 pb-24">
|
<div class="container mx-auto max-w-[1200px] space-y-8 pt-6 pb-24">
|
||||||
|
|
@ -46,7 +50,7 @@
|
||||||
<Button variant="ghost" onClickPromise={signOut}>Sign out</Button>
|
<Button variant="ghost" onClickPromise={signOut}>Sign out</Button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="px-4 md:grid md:grid-cols-[280px_1fr]">
|
<div class="px-4 md:grid md:grid-cols-[255px_1fr]">
|
||||||
<div class="hidden md:col-start-1 md:block">
|
<div class="hidden md:col-start-1 md:block">
|
||||||
<div class="flex flex-col place-items-center gap-2">
|
<div class="flex flex-col place-items-center gap-2">
|
||||||
<Avatar src={data.session.user.image ?? undefined}>
|
<Avatar src={data.session.user.image ?? undefined}>
|
||||||
|
|
@ -64,6 +68,19 @@
|
||||||
<p class="text-center text-2xl font-bold">{data.session.user.name}</p>
|
<p class="text-center text-2xl font-bold">{data.session.user.name}</p>
|
||||||
<span class="text-muted-foreground text-center text-sm">{data.session.user.email}</span>
|
<span class="text-muted-foreground text-center text-sm">{data.session.user.email}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-4 flex w-full flex-col gap-2">
|
||||||
|
<span class="text-sm font-medium">Keyboard Shortcuts</span>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div class="flex place-items-center justify-between">
|
||||||
|
<span>Toggle Sidebar </span>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Kbd>{isMac.current ? '⌘' : 'Ctrl'}</Kbd>
|
||||||
|
<Kbd>B</Kbd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-12 md:col-start-2">
|
<div class="pl-12 md:col-start-2">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue