@dorsk/tsumikit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/autoresize.d.ts +11 -0
- package/dist/autoresize.js +24 -0
- package/dist/components/atoms/Badge.svelte +72 -0
- package/dist/components/atoms/Badge.svelte.d.ts +12 -0
- package/dist/components/atoms/Button.svelte +156 -0
- package/dist/components/atoms/Button.svelte.d.ts +13 -0
- package/dist/components/atoms/Card.svelte +46 -0
- package/dist/components/atoms/Card.svelte.d.ts +11 -0
- package/dist/components/atoms/Checkbox.svelte +99 -0
- package/dist/components/atoms/Checkbox.svelte.d.ts +10 -0
- package/dist/components/atoms/Chip.svelte +53 -0
- package/dist/components/atoms/Chip.svelte.d.ts +11 -0
- package/dist/components/atoms/Heading.svelte +66 -0
- package/dist/components/atoms/Heading.svelte.d.ts +13 -0
- package/dist/components/atoms/Icon.svelte +151 -0
- package/dist/components/atoms/Icon.svelte.d.ts +18 -0
- package/dist/components/atoms/Input.svelte +42 -0
- package/dist/components/atoms/Input.svelte.d.ts +10 -0
- package/dist/components/atoms/Link.svelte +31 -0
- package/dist/components/atoms/Link.svelte.d.ts +10 -0
- package/dist/components/atoms/Progress.svelte +59 -0
- package/dist/components/atoms/Progress.svelte.d.ts +9 -0
- package/dist/components/atoms/Select.svelte +95 -0
- package/dist/components/atoms/Select.svelte.d.ts +11 -0
- package/dist/components/atoms/Slider.svelte +136 -0
- package/dist/components/atoms/Slider.svelte.d.ts +14 -0
- package/dist/components/atoms/Switch.svelte +64 -0
- package/dist/components/atoms/Switch.svelte.d.ts +8 -0
- package/dist/components/atoms/Text.svelte +127 -0
- package/dist/components/atoms/Text.svelte.d.ts +16 -0
- package/dist/components/atoms/Textarea.svelte +62 -0
- package/dist/components/atoms/Textarea.svelte.d.ts +11 -0
- package/dist/components/layouts/AppShell.svelte +304 -0
- package/dist/components/layouts/AppShell.svelte.d.ts +21 -0
- package/dist/components/layouts/AutoGrid.svelte +36 -0
- package/dist/components/layouts/AutoGrid.svelte.d.ts +12 -0
- package/dist/components/layouts/Cluster.svelte +45 -0
- package/dist/components/layouts/Cluster.svelte.d.ts +14 -0
- package/dist/components/layouts/Container.svelte +40 -0
- package/dist/components/layouts/Container.svelte.d.ts +13 -0
- package/dist/components/layouts/NavItem.svelte +95 -0
- package/dist/components/layouts/NavItem.svelte.d.ts +14 -0
- package/dist/components/layouts/Stack.svelte +44 -0
- package/dist/components/layouts/Stack.svelte.d.ts +13 -0
- package/dist/components/molecules/Accordion.svelte +94 -0
- package/dist/components/molecules/Accordion.svelte.d.ts +16 -0
- package/dist/components/molecules/CodeBlock.svelte +119 -0
- package/dist/components/molecules/CodeBlock.svelte.d.ts +17 -0
- package/dist/components/molecules/CopyButton.svelte +80 -0
- package/dist/components/molecules/CopyButton.svelte.d.ts +13 -0
- package/dist/components/molecules/Dropzone.svelte +140 -0
- package/dist/components/molecules/Dropzone.svelte.d.ts +13 -0
- package/dist/components/molecules/Field.svelte +57 -0
- package/dist/components/molecules/Field.svelte.d.ts +12 -0
- package/dist/components/molecules/FileButton.svelte +68 -0
- package/dist/components/molecules/FileButton.svelte.d.ts +14 -0
- package/dist/components/molecules/FontScalePicker.svelte +21 -0
- package/dist/components/molecules/FontScalePicker.svelte.d.ts +6 -0
- package/dist/components/molecules/IconButton.svelte +36 -0
- package/dist/components/molecules/IconButton.svelte.d.ts +13 -0
- package/dist/components/molecules/Menu.svelte +120 -0
- package/dist/components/molecules/Menu.svelte.d.ts +17 -0
- package/dist/components/molecules/Modal.svelte +263 -0
- package/dist/components/molecules/Modal.svelte.d.ts +13 -0
- package/dist/components/molecules/OptionButton.svelte +76 -0
- package/dist/components/molecules/OptionButton.svelte.d.ts +10 -0
- package/dist/components/molecules/Popover.svelte +125 -0
- package/dist/components/molecules/Popover.svelte.d.ts +18 -0
- package/dist/components/molecules/RadioGroup.svelte +110 -0
- package/dist/components/molecules/RadioGroup.svelte.d.ts +16 -0
- package/dist/components/molecules/SelectButton.svelte +52 -0
- package/dist/components/molecules/SelectButton.svelte.d.ts +15 -0
- package/dist/components/molecules/Tabs.svelte +119 -0
- package/dist/components/molecules/Tabs.svelte.d.ts +15 -0
- package/dist/components/molecules/ThemePicker.svelte +22 -0
- package/dist/components/molecules/ThemePicker.svelte.d.ts +6 -0
- package/dist/components/molecules/Toaster.svelte +73 -0
- package/dist/components/molecules/Toaster.svelte.d.ts +18 -0
- package/dist/components/molecules/Toggle.svelte +68 -0
- package/dist/components/molecules/Toggle.svelte.d.ts +11 -0
- package/dist/components/molecules/Tooltip.svelte +106 -0
- package/dist/components/molecules/Tooltip.svelte.d.ts +10 -0
- package/dist/components/organisms/DataTable.svelte +145 -0
- package/dist/components/organisms/DataTable.svelte.d.ts +43 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +4 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +56 -0
- package/dist/stores/fontscale.svelte.d.ts +15 -0
- package/dist/stores/fontscale.svelte.js +49 -0
- package/dist/stores/theme.svelte.d.ts +96 -0
- package/dist/stores/theme.svelte.js +71 -0
- package/dist/stores/toast.svelte.d.ts +19 -0
- package/dist/stores/toast.svelte.js +26 -0
- package/dist/styles/app.css +522 -0
- package/dist/styles/variables.css +651 -0
- package/package.json +71 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface Column<T> {
|
|
3
|
+
/** Key into the row, or an arbitrary id when paired with a cell snippet. */
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
width?: string;
|
|
7
|
+
align?: 'left' | 'center' | 'right';
|
|
8
|
+
/** Pull a display value from the row (defaults to row[key]). */
|
|
9
|
+
get?: (row: T) => unknown;
|
|
10
|
+
}
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts" generics="T">
|
|
14
|
+
// Generic, accessible data table. Columns are typed against the row type; a
|
|
15
|
+
// column renders row[key] by default, a `get` accessor, or a custom
|
|
16
|
+
// `cellSnippets[key]` snippet for full control (badges, actions…). Sticky
|
|
17
|
+
// header, empty state, optional row click (rows become buttons with the
|
|
18
|
+
// right keyboard semantics). Horizontal scroll is contained so it never
|
|
19
|
+
// breaks the page layout on mobile.
|
|
20
|
+
import type { Snippet } from 'svelte';
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
columns,
|
|
24
|
+
rows,
|
|
25
|
+
rowKey,
|
|
26
|
+
onrowclick,
|
|
27
|
+
cellSnippets = {},
|
|
28
|
+
empty = 'No data.',
|
|
29
|
+
stickyHeader = false
|
|
30
|
+
}: {
|
|
31
|
+
columns: Column<T>[];
|
|
32
|
+
rows: T[];
|
|
33
|
+
/** Stable key for each row (for keyed iteration). */
|
|
34
|
+
rowKey: (row: T) => string | number;
|
|
35
|
+
onrowclick?: (row: T) => void;
|
|
36
|
+
cellSnippets?: Record<string, Snippet<[T]>>;
|
|
37
|
+
empty?: string;
|
|
38
|
+
stickyHeader?: boolean;
|
|
39
|
+
} = $props();
|
|
40
|
+
|
|
41
|
+
function display(col: Column<T>, row: T): unknown {
|
|
42
|
+
if (col.get) return col.get(row);
|
|
43
|
+
return (row as Record<string, unknown>)[col.key];
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<div class="dt-scroll">
|
|
48
|
+
<table class="dt" class:sticky={stickyHeader}>
|
|
49
|
+
<thead>
|
|
50
|
+
<tr>
|
|
51
|
+
{#each columns as col (col.key)}
|
|
52
|
+
<th scope="col" style:width={col.width} style:text-align={col.align ?? 'left'}>
|
|
53
|
+
{col.label}
|
|
54
|
+
</th>
|
|
55
|
+
{/each}
|
|
56
|
+
</tr>
|
|
57
|
+
</thead>
|
|
58
|
+
<tbody>
|
|
59
|
+
{#if rows.length === 0}
|
|
60
|
+
<tr>
|
|
61
|
+
<td class="dt-empty" colspan={columns.length}>{empty}</td>
|
|
62
|
+
</tr>
|
|
63
|
+
{:else}
|
|
64
|
+
{#each rows as row (rowKey(row))}
|
|
65
|
+
<tr
|
|
66
|
+
class:clickable={!!onrowclick}
|
|
67
|
+
tabindex={onrowclick ? 0 : undefined}
|
|
68
|
+
role={onrowclick ? 'button' : undefined}
|
|
69
|
+
onclick={onrowclick ? () => onrowclick(row) : undefined}
|
|
70
|
+
onkeydown={onrowclick
|
|
71
|
+
? (e) => {
|
|
72
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
onrowclick(row);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
: undefined}
|
|
78
|
+
>
|
|
79
|
+
{#each columns as col (col.key)}
|
|
80
|
+
<td style:text-align={col.align ?? 'left'}>
|
|
81
|
+
{#if cellSnippets[col.key]}
|
|
82
|
+
{@render cellSnippets[col.key](row)}
|
|
83
|
+
{:else}
|
|
84
|
+
{display(col, row)}
|
|
85
|
+
{/if}
|
|
86
|
+
</td>
|
|
87
|
+
{/each}
|
|
88
|
+
</tr>
|
|
89
|
+
{/each}
|
|
90
|
+
{/if}
|
|
91
|
+
</tbody>
|
|
92
|
+
</table>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<style>
|
|
96
|
+
.dt-scroll {
|
|
97
|
+
width: 100%;
|
|
98
|
+
overflow-x: auto;
|
|
99
|
+
-webkit-overflow-scrolling: touch;
|
|
100
|
+
border: 1px solid var(--border);
|
|
101
|
+
border-radius: var(--r-lg);
|
|
102
|
+
}
|
|
103
|
+
.dt {
|
|
104
|
+
width: 100%;
|
|
105
|
+
border-collapse: collapse;
|
|
106
|
+
font-size: var(--fs-sm);
|
|
107
|
+
background: var(--surface);
|
|
108
|
+
}
|
|
109
|
+
th,
|
|
110
|
+
td {
|
|
111
|
+
padding: var(--sp-2) var(--sp-3);
|
|
112
|
+
border-bottom: 1px solid var(--border);
|
|
113
|
+
}
|
|
114
|
+
th {
|
|
115
|
+
font-size: var(--fs-xs);
|
|
116
|
+
font-weight: var(--fw-semibold);
|
|
117
|
+
color: var(--text-muted);
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
letter-spacing: 0.04em;
|
|
120
|
+
background: var(--bg-elevated-2);
|
|
121
|
+
white-space: nowrap;
|
|
122
|
+
}
|
|
123
|
+
.dt.sticky th {
|
|
124
|
+
position: sticky;
|
|
125
|
+
top: 0;
|
|
126
|
+
z-index: 1;
|
|
127
|
+
}
|
|
128
|
+
tbody tr:last-child td {
|
|
129
|
+
border-bottom: none;
|
|
130
|
+
}
|
|
131
|
+
tr.clickable {
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
transition: background 0.12s var(--ease);
|
|
134
|
+
}
|
|
135
|
+
tr.clickable:hover,
|
|
136
|
+
tr.clickable:focus-visible {
|
|
137
|
+
background: var(--bg-elevated-2);
|
|
138
|
+
outline: none;
|
|
139
|
+
}
|
|
140
|
+
.dt-empty {
|
|
141
|
+
text-align: center;
|
|
142
|
+
color: var(--text-faint);
|
|
143
|
+
padding: var(--sp-8);
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface Column<T> {
|
|
2
|
+
/** Key into the row, or an arbitrary id when paired with a cell snippet. */
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
width?: string;
|
|
6
|
+
align?: 'left' | 'center' | 'right';
|
|
7
|
+
/** Pull a display value from the row (defaults to row[key]). */
|
|
8
|
+
get?: (row: T) => unknown;
|
|
9
|
+
}
|
|
10
|
+
import type { Snippet } from 'svelte';
|
|
11
|
+
declare function $$render<T>(): {
|
|
12
|
+
props: {
|
|
13
|
+
columns: Column<T>[];
|
|
14
|
+
rows: T[];
|
|
15
|
+
/** Stable key for each row (for keyed iteration). */
|
|
16
|
+
rowKey: (row: T) => string | number;
|
|
17
|
+
onrowclick?: (row: T) => void;
|
|
18
|
+
cellSnippets?: Record<string, Snippet<[T]>>;
|
|
19
|
+
empty?: string;
|
|
20
|
+
stickyHeader?: boolean;
|
|
21
|
+
};
|
|
22
|
+
exports: {};
|
|
23
|
+
bindings: "";
|
|
24
|
+
slots: {};
|
|
25
|
+
events: {};
|
|
26
|
+
};
|
|
27
|
+
declare class __sveltets_Render<T> {
|
|
28
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
29
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
30
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
31
|
+
bindings(): "";
|
|
32
|
+
exports(): {};
|
|
33
|
+
}
|
|
34
|
+
interface $$IsomorphicComponent {
|
|
35
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
36
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
37
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
38
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
39
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
40
|
+
}
|
|
41
|
+
declare const DataTable: $$IsomorphicComponent;
|
|
42
|
+
type DataTable<T> = InstanceType<typeof DataTable<T>>;
|
|
43
|
+
export default DataTable;
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const browser: boolean;
|
package/dist/env.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export { autoresize } from './autoresize';
|
|
2
|
+
export { default as Badge } from './components/atoms/Badge.svelte';
|
|
3
|
+
export { default as Button } from './components/atoms/Button.svelte';
|
|
4
|
+
export { default as Card } from './components/atoms/Card.svelte';
|
|
5
|
+
export { default as Checkbox } from './components/atoms/Checkbox.svelte';
|
|
6
|
+
export { default as Chip } from './components/atoms/Chip.svelte';
|
|
7
|
+
export { default as Heading } from './components/atoms/Heading.svelte';
|
|
8
|
+
export type { IconName } from './components/atoms/Icon.svelte';
|
|
9
|
+
export { default as Icon } from './components/atoms/Icon.svelte';
|
|
10
|
+
export { default as Input } from './components/atoms/Input.svelte';
|
|
11
|
+
export { default as Link } from './components/atoms/Link.svelte';
|
|
12
|
+
export { default as Progress } from './components/atoms/Progress.svelte';
|
|
13
|
+
export { default as Select } from './components/atoms/Select.svelte';
|
|
14
|
+
export { default as Slider } from './components/atoms/Slider.svelte';
|
|
15
|
+
export { default as Switch } from './components/atoms/Switch.svelte';
|
|
16
|
+
export { default as Text } from './components/atoms/Text.svelte';
|
|
17
|
+
export { default as Textarea } from './components/atoms/Textarea.svelte';
|
|
18
|
+
export { default as AppShell } from './components/layouts/AppShell.svelte';
|
|
19
|
+
export { default as AutoGrid } from './components/layouts/AutoGrid.svelte';
|
|
20
|
+
export { default as Cluster } from './components/layouts/Cluster.svelte';
|
|
21
|
+
export { default as Container } from './components/layouts/Container.svelte';
|
|
22
|
+
export { default as NavItem } from './components/layouts/NavItem.svelte';
|
|
23
|
+
export { default as Stack } from './components/layouts/Stack.svelte';
|
|
24
|
+
export { type AccordionItem, default as Accordion, } from './components/molecules/Accordion.svelte';
|
|
25
|
+
export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
|
|
26
|
+
export { default as CopyButton } from './components/molecules/CopyButton.svelte';
|
|
27
|
+
export { default as Dropzone } from './components/molecules/Dropzone.svelte';
|
|
28
|
+
export { default as Field } from './components/molecules/Field.svelte';
|
|
29
|
+
export { default as FileButton } from './components/molecules/FileButton.svelte';
|
|
30
|
+
export { default as FontScalePicker } from './components/molecules/FontScalePicker.svelte';
|
|
31
|
+
export { default as IconButton } from './components/molecules/IconButton.svelte';
|
|
32
|
+
export { default as Menu, type MenuItem } from './components/molecules/Menu.svelte';
|
|
33
|
+
export { default as Modal } from './components/molecules/Modal.svelte';
|
|
34
|
+
export { default as OptionButton } from './components/molecules/OptionButton.svelte';
|
|
35
|
+
export { default as Popover } from './components/molecules/Popover.svelte';
|
|
36
|
+
export { default as RadioGroup, type RadioOption, } from './components/molecules/RadioGroup.svelte';
|
|
37
|
+
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
38
|
+
export { default as Tabs, type TabItem } from './components/molecules/Tabs.svelte';
|
|
39
|
+
export { default as ThemePicker } from './components/molecules/ThemePicker.svelte';
|
|
40
|
+
export { default as Toaster } from './components/molecules/Toaster.svelte';
|
|
41
|
+
export { default as Toggle } from './components/molecules/Toggle.svelte';
|
|
42
|
+
export { default as Tooltip } from './components/molecules/Tooltip.svelte';
|
|
43
|
+
export { type Column, default as DataTable } from './components/organisms/DataTable.svelte';
|
|
44
|
+
export { fontScale, SCALE_LEVELS, type ScaleLevel } from './stores/fontscale.svelte';
|
|
45
|
+
export { type Mode, THEMES, theme } from './stores/theme.svelte';
|
|
46
|
+
export { type Toast, type ToastTone, toasts } from './stores/toast.svelte';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @dorsk/tsumikit — public API.
|
|
2
|
+
//
|
|
3
|
+
// Styles are NOT auto-imported (so consumers control bundling). Import once at
|
|
4
|
+
// your app root:
|
|
5
|
+
// import '@dorsk/tsumikit/styles/app.css';
|
|
6
|
+
// then use the components below.
|
|
7
|
+
export { autoresize } from './autoresize';
|
|
8
|
+
export { default as Badge } from './components/atoms/Badge.svelte';
|
|
9
|
+
export { default as Button } from './components/atoms/Button.svelte';
|
|
10
|
+
export { default as Card } from './components/atoms/Card.svelte';
|
|
11
|
+
export { default as Checkbox } from './components/atoms/Checkbox.svelte';
|
|
12
|
+
export { default as Chip } from './components/atoms/Chip.svelte';
|
|
13
|
+
export { default as Heading } from './components/atoms/Heading.svelte';
|
|
14
|
+
export { default as Icon } from './components/atoms/Icon.svelte';
|
|
15
|
+
export { default as Input } from './components/atoms/Input.svelte';
|
|
16
|
+
export { default as Link } from './components/atoms/Link.svelte';
|
|
17
|
+
export { default as Progress } from './components/atoms/Progress.svelte';
|
|
18
|
+
export { default as Select } from './components/atoms/Select.svelte';
|
|
19
|
+
export { default as Slider } from './components/atoms/Slider.svelte';
|
|
20
|
+
export { default as Switch } from './components/atoms/Switch.svelte';
|
|
21
|
+
// ---- atoms ----
|
|
22
|
+
export { default as Text } from './components/atoms/Text.svelte';
|
|
23
|
+
export { default as Textarea } from './components/atoms/Textarea.svelte';
|
|
24
|
+
// ---- layouts ----
|
|
25
|
+
export { default as AppShell } from './components/layouts/AppShell.svelte';
|
|
26
|
+
export { default as AutoGrid } from './components/layouts/AutoGrid.svelte';
|
|
27
|
+
export { default as Cluster } from './components/layouts/Cluster.svelte';
|
|
28
|
+
export { default as Container } from './components/layouts/Container.svelte';
|
|
29
|
+
export { default as NavItem } from './components/layouts/NavItem.svelte';
|
|
30
|
+
export { default as Stack } from './components/layouts/Stack.svelte';
|
|
31
|
+
export { default as Accordion, } from './components/molecules/Accordion.svelte';
|
|
32
|
+
export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
|
|
33
|
+
export { default as CopyButton } from './components/molecules/CopyButton.svelte';
|
|
34
|
+
export { default as Dropzone } from './components/molecules/Dropzone.svelte';
|
|
35
|
+
// ---- molecules ----
|
|
36
|
+
export { default as Field } from './components/molecules/Field.svelte';
|
|
37
|
+
export { default as FileButton } from './components/molecules/FileButton.svelte';
|
|
38
|
+
export { default as FontScalePicker } from './components/molecules/FontScalePicker.svelte';
|
|
39
|
+
export { default as IconButton } from './components/molecules/IconButton.svelte';
|
|
40
|
+
export { default as Menu } from './components/molecules/Menu.svelte';
|
|
41
|
+
export { default as Modal } from './components/molecules/Modal.svelte';
|
|
42
|
+
export { default as OptionButton } from './components/molecules/OptionButton.svelte';
|
|
43
|
+
export { default as Popover } from './components/molecules/Popover.svelte';
|
|
44
|
+
export { default as RadioGroup, } from './components/molecules/RadioGroup.svelte';
|
|
45
|
+
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
46
|
+
export { default as Tabs } from './components/molecules/Tabs.svelte';
|
|
47
|
+
export { default as ThemePicker } from './components/molecules/ThemePicker.svelte';
|
|
48
|
+
export { default as Toaster } from './components/molecules/Toaster.svelte';
|
|
49
|
+
export { default as Toggle } from './components/molecules/Toggle.svelte';
|
|
50
|
+
export { default as Tooltip } from './components/molecules/Tooltip.svelte';
|
|
51
|
+
// ---- organisms ----
|
|
52
|
+
export { default as DataTable } from './components/organisms/DataTable.svelte';
|
|
53
|
+
export { fontScale, SCALE_LEVELS } from './stores/fontscale.svelte';
|
|
54
|
+
// ---- stores / actions ----
|
|
55
|
+
export { THEMES, theme } from './stores/theme.svelte';
|
|
56
|
+
export { toasts } from './stores/toast.svelte';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ScaleLevel {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
value: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const SCALE_LEVELS: ScaleLevel[];
|
|
7
|
+
declare class FontScale {
|
|
8
|
+
levelId: string;
|
|
9
|
+
current: number;
|
|
10
|
+
constructor();
|
|
11
|
+
private apply;
|
|
12
|
+
set(id: string): void;
|
|
13
|
+
}
|
|
14
|
+
export declare const fontScale: FontScale;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { browser } from '../env';
|
|
2
|
+
// OPT-IN. Most apps don't need this: the kit is rem-based and never resets the
|
|
3
|
+
// root font-size, so the user's browser/OS font-size preference and browser
|
|
4
|
+
// zoom already scale the UI correctly. Reach for this control only in
|
|
5
|
+
// reading-dense apps (chat, docs, readers) that want to enlarge BODY TEXT while
|
|
6
|
+
// keeping chrome compact — not wired into AppShell or any default.
|
|
7
|
+
//
|
|
8
|
+
// Global UI font scale. The control multiplies ONLY the font-size tokens
|
|
9
|
+
// (--fs-scale, consumed by every --fs-* in variables.css) — NOT the document
|
|
10
|
+
// root font-size. Scaling the root would grow every rem-based chrome dimension
|
|
11
|
+
// (control heights, padding) in lockstep, so text would barely grow relative to
|
|
12
|
+
// a ballooning UI. Driving --fs-scale leaves spacing/control sizes fixed and
|
|
13
|
+
// enlarges only text. Discrete levels avoid per-pixel reflow churn.
|
|
14
|
+
const KEY = 'tsumikit-font-scale';
|
|
15
|
+
export const SCALE_LEVELS = [
|
|
16
|
+
{ id: 'smallest', label: 'Smallest', value: 0.8 },
|
|
17
|
+
{ id: 'small', label: 'Small', value: 0.9 },
|
|
18
|
+
{ id: 'normal', label: 'Normal', value: 1 },
|
|
19
|
+
{ id: 'large', label: 'Large', value: 1.2 },
|
|
20
|
+
{ id: 'largest', label: 'Largest', value: 1.5 },
|
|
21
|
+
];
|
|
22
|
+
const DEFAULT_LEVEL = 'normal';
|
|
23
|
+
function levelById(id) {
|
|
24
|
+
return SCALE_LEVELS.find((l) => l.id === id) ?? SCALE_LEVELS[2];
|
|
25
|
+
}
|
|
26
|
+
class FontScale {
|
|
27
|
+
levelId = $state(DEFAULT_LEVEL);
|
|
28
|
+
current = $derived(levelById(this.levelId).value);
|
|
29
|
+
constructor() {
|
|
30
|
+
if (browser) {
|
|
31
|
+
const raw = localStorage.getItem(KEY);
|
|
32
|
+
if (raw && SCALE_LEVELS.some((l) => l.id === raw))
|
|
33
|
+
this.levelId = raw;
|
|
34
|
+
this.apply();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
apply() {
|
|
38
|
+
if (!browser)
|
|
39
|
+
return;
|
|
40
|
+
document.documentElement.style.setProperty('--fs-scale', String(levelById(this.levelId).value));
|
|
41
|
+
}
|
|
42
|
+
set(id) {
|
|
43
|
+
this.levelId = SCALE_LEVELS.some((l) => l.id === id) ? id : DEFAULT_LEVEL;
|
|
44
|
+
if (browser)
|
|
45
|
+
localStorage.setItem(KEY, this.levelId);
|
|
46
|
+
this.apply();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export const fontScale = new FontScale();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export declare const THEMES: readonly [{
|
|
2
|
+
readonly id: "dark";
|
|
3
|
+
readonly label: "Dark";
|
|
4
|
+
readonly icon: "☾";
|
|
5
|
+
readonly themeColor: "#0f1115";
|
|
6
|
+
}, {
|
|
7
|
+
readonly id: "light";
|
|
8
|
+
readonly label: "Light";
|
|
9
|
+
readonly icon: "☀";
|
|
10
|
+
readonly themeColor: "#f6f7f9";
|
|
11
|
+
}, {
|
|
12
|
+
readonly id: "sepia";
|
|
13
|
+
readonly label: "Sepia";
|
|
14
|
+
readonly icon: "✶";
|
|
15
|
+
readonly themeColor: "#f4ecd8";
|
|
16
|
+
}, {
|
|
17
|
+
readonly id: "colorblind";
|
|
18
|
+
readonly label: "Color-blind safe";
|
|
19
|
+
readonly icon: "◑";
|
|
20
|
+
readonly themeColor: "#16181d";
|
|
21
|
+
}, {
|
|
22
|
+
readonly id: "mocha";
|
|
23
|
+
readonly label: "Catppuccin Mocha";
|
|
24
|
+
readonly icon: "M";
|
|
25
|
+
readonly themeColor: "#1e1e2e";
|
|
26
|
+
}, {
|
|
27
|
+
readonly id: "dracula";
|
|
28
|
+
readonly label: "Dracula";
|
|
29
|
+
readonly icon: "D";
|
|
30
|
+
readonly themeColor: "#282a36";
|
|
31
|
+
}, {
|
|
32
|
+
readonly id: "nord";
|
|
33
|
+
readonly label: "Nord";
|
|
34
|
+
readonly icon: "N";
|
|
35
|
+
readonly themeColor: "#2e3440";
|
|
36
|
+
}, {
|
|
37
|
+
readonly id: "tokyonight";
|
|
38
|
+
readonly label: "Tokyo Night";
|
|
39
|
+
readonly icon: "✦";
|
|
40
|
+
readonly themeColor: "#1a1b26";
|
|
41
|
+
}, {
|
|
42
|
+
readonly id: "gruvbox";
|
|
43
|
+
readonly label: "Gruvbox";
|
|
44
|
+
readonly icon: "◆";
|
|
45
|
+
readonly themeColor: "#282828";
|
|
46
|
+
}, {
|
|
47
|
+
readonly id: "solarized";
|
|
48
|
+
readonly label: "Solarized Dark";
|
|
49
|
+
readonly icon: "◐";
|
|
50
|
+
readonly themeColor: "#002b36";
|
|
51
|
+
}, {
|
|
52
|
+
readonly id: "rosepine";
|
|
53
|
+
readonly label: "Rosé Pine";
|
|
54
|
+
readonly icon: "❀";
|
|
55
|
+
readonly themeColor: "#191724";
|
|
56
|
+
}, {
|
|
57
|
+
readonly id: "onedark";
|
|
58
|
+
readonly label: "One Dark";
|
|
59
|
+
readonly icon: "①";
|
|
60
|
+
readonly themeColor: "#282c34";
|
|
61
|
+
}, {
|
|
62
|
+
readonly id: "everforest";
|
|
63
|
+
readonly label: "Everforest";
|
|
64
|
+
readonly icon: "☘";
|
|
65
|
+
readonly themeColor: "#2d353b";
|
|
66
|
+
}, {
|
|
67
|
+
readonly id: "monokai";
|
|
68
|
+
readonly label: "Monokai";
|
|
69
|
+
readonly icon: "✸";
|
|
70
|
+
readonly themeColor: "#272822";
|
|
71
|
+
}, {
|
|
72
|
+
readonly id: "amoled";
|
|
73
|
+
readonly label: "AMOLED (high contrast)";
|
|
74
|
+
readonly icon: "◼";
|
|
75
|
+
readonly themeColor: "#000000";
|
|
76
|
+
}, {
|
|
77
|
+
readonly id: "highcontrast";
|
|
78
|
+
readonly label: "High Contrast Light";
|
|
79
|
+
readonly icon: "◻";
|
|
80
|
+
readonly themeColor: "#ffffff";
|
|
81
|
+
}];
|
|
82
|
+
export type Mode = (typeof THEMES)[number]['id'];
|
|
83
|
+
type ThemeOption = (typeof THEMES)[number];
|
|
84
|
+
declare class Theme {
|
|
85
|
+
current: "dark" | "light" | "sepia" | "colorblind" | "mocha" | "dracula" | "nord" | "tokyonight" | "gruvbox" | "solarized" | "rosepine" | "onedark" | "everforest" | "monokai" | "amoled" | "highcontrast";
|
|
86
|
+
constructor();
|
|
87
|
+
private apply;
|
|
88
|
+
get option(): ThemeOption;
|
|
89
|
+
get label(): string;
|
|
90
|
+
get icon(): string;
|
|
91
|
+
get next(): ThemeOption;
|
|
92
|
+
toggle(): void;
|
|
93
|
+
set(mode: Mode): void;
|
|
94
|
+
}
|
|
95
|
+
export declare const theme: Theme;
|
|
96
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { browser } from '../env';
|
|
2
|
+
// Theme registry — the single list the picker and the store both read. Adding a
|
|
3
|
+
// theme = one entry here + one [data-theme="id"] block in variables.css. Nothing
|
|
4
|
+
// else changes. `themeColor` drives the mobile browser-chrome <meta theme-color>.
|
|
5
|
+
const KEY = 'tsumikit-theme';
|
|
6
|
+
export const THEMES = [
|
|
7
|
+
{ id: 'dark', label: 'Dark', icon: '☾', themeColor: '#0f1115' },
|
|
8
|
+
{ id: 'light', label: 'Light', icon: '☀', themeColor: '#f6f7f9' },
|
|
9
|
+
{ id: 'sepia', label: 'Sepia', icon: '✶', themeColor: '#f4ecd8' },
|
|
10
|
+
{ id: 'colorblind', label: 'Color-blind safe', icon: '◑', themeColor: '#16181d' },
|
|
11
|
+
{ id: 'mocha', label: 'Catppuccin Mocha', icon: 'M', themeColor: '#1e1e2e' },
|
|
12
|
+
{ id: 'dracula', label: 'Dracula', icon: 'D', themeColor: '#282a36' },
|
|
13
|
+
{ id: 'nord', label: 'Nord', icon: 'N', themeColor: '#2e3440' },
|
|
14
|
+
{ id: 'tokyonight', label: 'Tokyo Night', icon: '✦', themeColor: '#1a1b26' },
|
|
15
|
+
{ id: 'gruvbox', label: 'Gruvbox', icon: '◆', themeColor: '#282828' },
|
|
16
|
+
{ id: 'solarized', label: 'Solarized Dark', icon: '◐', themeColor: '#002b36' },
|
|
17
|
+
{ id: 'rosepine', label: 'Rosé Pine', icon: '❀', themeColor: '#191724' },
|
|
18
|
+
{ id: 'onedark', label: 'One Dark', icon: '①', themeColor: '#282c34' },
|
|
19
|
+
{ id: 'everforest', label: 'Everforest', icon: '☘', themeColor: '#2d353b' },
|
|
20
|
+
{ id: 'monokai', label: 'Monokai', icon: '✸', themeColor: '#272822' },
|
|
21
|
+
{ id: 'amoled', label: 'AMOLED (high contrast)', icon: '◼', themeColor: '#000000' },
|
|
22
|
+
{ id: 'highcontrast', label: 'High Contrast Light', icon: '◻', themeColor: '#ffffff' },
|
|
23
|
+
];
|
|
24
|
+
const ORDER = THEMES.map((t) => t.id);
|
|
25
|
+
function isMode(value) {
|
|
26
|
+
return THEMES.some((t) => t.id === value);
|
|
27
|
+
}
|
|
28
|
+
function optionFor(mode) {
|
|
29
|
+
return THEMES.find((t) => t.id === mode) ?? THEMES[0];
|
|
30
|
+
}
|
|
31
|
+
class Theme {
|
|
32
|
+
current = $state('dark');
|
|
33
|
+
constructor() {
|
|
34
|
+
if (browser) {
|
|
35
|
+
const saved = localStorage.getItem(KEY);
|
|
36
|
+
this.current = isMode(saved) ? saved : 'dark';
|
|
37
|
+
this.apply();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
apply() {
|
|
41
|
+
if (!browser)
|
|
42
|
+
return;
|
|
43
|
+
document.documentElement.setAttribute('data-theme', this.current);
|
|
44
|
+
document
|
|
45
|
+
.querySelector('meta[name="theme-color"]')
|
|
46
|
+
?.setAttribute('content', this.option.themeColor);
|
|
47
|
+
}
|
|
48
|
+
get option() {
|
|
49
|
+
return optionFor(this.current);
|
|
50
|
+
}
|
|
51
|
+
get label() {
|
|
52
|
+
return this.option.label;
|
|
53
|
+
}
|
|
54
|
+
get icon() {
|
|
55
|
+
return this.option.icon;
|
|
56
|
+
}
|
|
57
|
+
get next() {
|
|
58
|
+
const i = ORDER.indexOf(this.current);
|
|
59
|
+
return optionFor(ORDER[(i + 1) % ORDER.length]);
|
|
60
|
+
}
|
|
61
|
+
toggle() {
|
|
62
|
+
this.set(this.next.id);
|
|
63
|
+
}
|
|
64
|
+
set(mode) {
|
|
65
|
+
this.current = mode;
|
|
66
|
+
if (browser)
|
|
67
|
+
localStorage.setItem(KEY, this.current);
|
|
68
|
+
this.apply();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export const theme = new Theme();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ToastTone = 'neutral' | 'ok' | 'error';
|
|
2
|
+
export interface Toast {
|
|
3
|
+
id: number;
|
|
4
|
+
message: string;
|
|
5
|
+
tone: ToastTone;
|
|
6
|
+
duration: number;
|
|
7
|
+
}
|
|
8
|
+
declare class Toasts {
|
|
9
|
+
items: Toast[];
|
|
10
|
+
show(message: string, opts?: {
|
|
11
|
+
tone?: ToastTone;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}): number;
|
|
14
|
+
ok(message: string, duration?: number): number;
|
|
15
|
+
error(message: string, duration?: number): number;
|
|
16
|
+
dismiss(id: number): void;
|
|
17
|
+
}
|
|
18
|
+
export declare const toasts: Toasts;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Toast manager. A tiny reactive queue; mount one <Toaster /> at the app root
|
|
2
|
+
// and call toasts.show()/ok()/error() from anywhere. Auto-dismisses after
|
|
3
|
+
// `duration` ms (0 = sticky). The Toaster renders an aria-live region so screen
|
|
4
|
+
// readers announce messages.
|
|
5
|
+
let seq = 0;
|
|
6
|
+
class Toasts {
|
|
7
|
+
items = $state([]);
|
|
8
|
+
show(message, opts = {}) {
|
|
9
|
+
const id = ++seq;
|
|
10
|
+
const duration = opts.duration ?? 4000;
|
|
11
|
+
this.items = [...this.items, { id, message, tone: opts.tone ?? 'neutral', duration }];
|
|
12
|
+
if (duration > 0)
|
|
13
|
+
setTimeout(() => this.dismiss(id), duration);
|
|
14
|
+
return id;
|
|
15
|
+
}
|
|
16
|
+
ok(message, duration) {
|
|
17
|
+
return this.show(message, { tone: 'ok', duration });
|
|
18
|
+
}
|
|
19
|
+
error(message, duration) {
|
|
20
|
+
return this.show(message, { tone: 'error', duration });
|
|
21
|
+
}
|
|
22
|
+
dismiss(id) {
|
|
23
|
+
this.items = this.items.filter((t) => t.id !== id);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const toasts = new Toasts();
|