@nqmcreative/ui 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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +507 -0
  3. package/cli/index.mjs +410 -0
  4. package/dist/actions/anchor.d.ts +29 -0
  5. package/dist/actions/anchor.js +122 -0
  6. package/dist/actions/dismissable.d.ts +44 -0
  7. package/dist/actions/dismissable.js +147 -0
  8. package/dist/components/Accordion.svelte +14 -0
  9. package/dist/components/Accordion.svelte.d.ts +8 -0
  10. package/dist/components/AccordionItem.svelte +46 -0
  11. package/dist/components/AccordionItem.svelte.d.ts +14 -0
  12. package/dist/components/Alert.svelte +88 -0
  13. package/dist/components/Alert.svelte.d.ts +18 -0
  14. package/dist/components/Avatar.svelte +58 -0
  15. package/dist/components/Avatar.svelte.d.ts +16 -0
  16. package/dist/components/AvatarGroup.svelte +17 -0
  17. package/dist/components/AvatarGroup.svelte.d.ts +8 -0
  18. package/dist/components/Badge.svelte +53 -0
  19. package/dist/components/Badge.svelte.d.ts +18 -0
  20. package/dist/components/Breadcrumb.svelte +46 -0
  21. package/dist/components/Breadcrumb.svelte.d.ts +14 -0
  22. package/dist/components/Button.svelte +94 -0
  23. package/dist/components/Button.svelte.d.ts +21 -0
  24. package/dist/components/Calendar.svelte +206 -0
  25. package/dist/components/Calendar.svelte.d.ts +22 -0
  26. package/dist/components/Card.svelte +76 -0
  27. package/dist/components/Card.svelte.d.ts +21 -0
  28. package/dist/components/Checkbox.svelte +69 -0
  29. package/dist/components/Checkbox.svelte.d.ts +15 -0
  30. package/dist/components/CheckboxGroup.svelte +109 -0
  31. package/dist/components/CheckboxGroup.svelte.d.ts +30 -0
  32. package/dist/components/Combobox.svelte +268 -0
  33. package/dist/components/Combobox.svelte.d.ts +29 -0
  34. package/dist/components/CommandPalette.svelte +226 -0
  35. package/dist/components/CommandPalette.svelte.d.ts +32 -0
  36. package/dist/components/ConfirmDialog.svelte +97 -0
  37. package/dist/components/ConfirmDialog.svelte.d.ts +22 -0
  38. package/dist/components/ContextMenu.svelte +90 -0
  39. package/dist/components/ContextMenu.svelte.d.ts +15 -0
  40. package/dist/components/DatePicker.svelte +186 -0
  41. package/dist/components/DatePicker.svelte.d.ts +23 -0
  42. package/dist/components/Divider.svelte +30 -0
  43. package/dist/components/Divider.svelte.d.ts +10 -0
  44. package/dist/components/Drawer.svelte +161 -0
  45. package/dist/components/Drawer.svelte.d.ts +20 -0
  46. package/dist/components/Dropdown.svelte +112 -0
  47. package/dist/components/Dropdown.svelte.d.ts +23 -0
  48. package/dist/components/Dropzone.svelte +226 -0
  49. package/dist/components/Dropzone.svelte.d.ts +32 -0
  50. package/dist/components/EmptyState.svelte +57 -0
  51. package/dist/components/EmptyState.svelte.d.ts +18 -0
  52. package/dist/components/Field.svelte +42 -0
  53. package/dist/components/Field.svelte.d.ts +16 -0
  54. package/dist/components/Footer.svelte +93 -0
  55. package/dist/components/Footer.svelte.d.ts +25 -0
  56. package/dist/components/Input.svelte +60 -0
  57. package/dist/components/Input.svelte.d.ts +18 -0
  58. package/dist/components/InputGroup.svelte +19 -0
  59. package/dist/components/InputGroup.svelte.d.ts +10 -0
  60. package/dist/components/Kbd.svelte +17 -0
  61. package/dist/components/Kbd.svelte.d.ts +8 -0
  62. package/dist/components/Label.svelte +16 -0
  63. package/dist/components/Label.svelte.d.ts +9 -0
  64. package/dist/components/Link.svelte +44 -0
  65. package/dist/components/Link.svelte.d.ts +16 -0
  66. package/dist/components/LocaleProvider.svelte +24 -0
  67. package/dist/components/LocaleProvider.svelte.d.ts +10 -0
  68. package/dist/components/MenuItem.svelte +58 -0
  69. package/dist/components/MenuItem.svelte.d.ts +18 -0
  70. package/dist/components/MenuSeparator.svelte +22 -0
  71. package/dist/components/MenuSeparator.svelte.d.ts +8 -0
  72. package/dist/components/Modal.svelte +114 -0
  73. package/dist/components/Modal.svelte.d.ts +21 -0
  74. package/dist/components/MultiSelect.svelte +292 -0
  75. package/dist/components/MultiSelect.svelte.d.ts +31 -0
  76. package/dist/components/Navbar.svelte +190 -0
  77. package/dist/components/Navbar.svelte.d.ts +33 -0
  78. package/dist/components/NumberInput.svelte +100 -0
  79. package/dist/components/NumberInput.svelte.d.ts +15 -0
  80. package/dist/components/Pagination.svelte +90 -0
  81. package/dist/components/Pagination.svelte.d.ts +15 -0
  82. package/dist/components/PasswordInput.svelte +104 -0
  83. package/dist/components/PasswordInput.svelte.d.ts +12 -0
  84. package/dist/components/Popover.svelte +114 -0
  85. package/dist/components/Popover.svelte.d.ts +19 -0
  86. package/dist/components/Progress.svelte +56 -0
  87. package/dist/components/Progress.svelte.d.ts +17 -0
  88. package/dist/components/Radio.svelte +55 -0
  89. package/dist/components/Radio.svelte.d.ts +15 -0
  90. package/dist/components/RadioGroup.svelte +93 -0
  91. package/dist/components/RadioGroup.svelte.d.ts +27 -0
  92. package/dist/components/SegmentedControl.svelte +88 -0
  93. package/dist/components/SegmentedControl.svelte.d.ts +25 -0
  94. package/dist/components/Select.svelte +61 -0
  95. package/dist/components/Select.svelte.d.ts +20 -0
  96. package/dist/components/Sidebar.svelte +206 -0
  97. package/dist/components/Sidebar.svelte.d.ts +36 -0
  98. package/dist/components/Skeleton.svelte +46 -0
  99. package/dist/components/Skeleton.svelte.d.ts +13 -0
  100. package/dist/components/Slider.svelte +79 -0
  101. package/dist/components/Slider.svelte.d.ts +19 -0
  102. package/dist/components/Spinner.svelte +34 -0
  103. package/dist/components/Spinner.svelte.d.ts +13 -0
  104. package/dist/components/Stat.svelte +49 -0
  105. package/dist/components/Stat.svelte.d.ts +16 -0
  106. package/dist/components/Steps.svelte +136 -0
  107. package/dist/components/Steps.svelte.d.ts +24 -0
  108. package/dist/components/Switch.svelte +64 -0
  109. package/dist/components/Switch.svelte.d.ts +14 -0
  110. package/dist/components/Table.svelte +236 -0
  111. package/dist/components/Table.svelte.d.ts +70 -0
  112. package/dist/components/Tabs.svelte +91 -0
  113. package/dist/components/Tabs.svelte.d.ts +23 -0
  114. package/dist/components/Textarea.svelte +54 -0
  115. package/dist/components/Textarea.svelte.d.ts +12 -0
  116. package/dist/components/ThemeToggle.svelte +133 -0
  117. package/dist/components/ThemeToggle.svelte.d.ts +20 -0
  118. package/dist/components/Toaster.svelte +110 -0
  119. package/dist/components/Toaster.svelte.d.ts +8 -0
  120. package/dist/components/Tooltip.svelte +33 -0
  121. package/dist/components/Tooltip.svelte.d.ts +12 -0
  122. package/dist/date.d.ts +35 -0
  123. package/dist/date.js +108 -0
  124. package/dist/fonts.css +193 -0
  125. package/dist/index.d.ts +105 -0
  126. package/dist/index.js +77 -0
  127. package/dist/locale.svelte.d.ts +93 -0
  128. package/dist/locale.svelte.js +153 -0
  129. package/dist/theme.css +210 -0
  130. package/dist/toast.svelte.d.ts +47 -0
  131. package/dist/toast.svelte.js +60 -0
  132. package/dist/tones.d.ts +48 -0
  133. package/dist/tones.js +209 -0
  134. package/package.json +349 -0
  135. package/registry.json +669 -0
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @nqmcreative/ui — public entry point.
3
+ * Tokens live in `theme.css`; shared tone maps live in `tones.ts`.
4
+ */
5
+ /* --- tokens --- */
6
+ export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText } from './tones.js';
7
+ /* --- behaviour (actions + stores) --- */
8
+ export { anchored } from './actions/anchor.js';
9
+ export { clickOutside, focusTrap, focusable, navigateList, portal } from './actions/dismissable.js';
10
+ export { toast } from './toast.svelte.js';
11
+ /* --- locale --- */
12
+ export { locale, setLocale, provideLocale, useLocale, enUS, idID, LocaleStore } from './locale.svelte.js';
13
+ export { default as LocaleProvider } from './components/LocaleProvider.svelte';
14
+ /* --- actions --- */
15
+ export { default as Button } from './components/Button.svelte';
16
+ export { default as Link } from './components/Link.svelte';
17
+ /* --- forms --- */
18
+ export { default as Input } from './components/Input.svelte';
19
+ export { default as Textarea } from './components/Textarea.svelte';
20
+ export { default as Select } from './components/Select.svelte';
21
+ export { default as Checkbox } from './components/Checkbox.svelte';
22
+ export { default as Radio } from './components/Radio.svelte';
23
+ export { default as Switch } from './components/Switch.svelte';
24
+ export { default as Field } from './components/Field.svelte';
25
+ export { default as Label } from './components/Label.svelte';
26
+ export { default as PasswordInput } from './components/PasswordInput.svelte';
27
+ export { default as NumberInput } from './components/NumberInput.svelte';
28
+ export { default as Slider } from './components/Slider.svelte';
29
+ export { default as RadioGroup } from './components/RadioGroup.svelte';
30
+ export { default as CheckboxGroup } from './components/CheckboxGroup.svelte';
31
+ export { default as SegmentedControl } from './components/SegmentedControl.svelte';
32
+ export { default as InputGroup } from './components/InputGroup.svelte';
33
+ export { default as Combobox } from './components/Combobox.svelte';
34
+ export { default as MultiSelect } from './components/MultiSelect.svelte';
35
+ export { default as DatePicker } from './components/DatePicker.svelte';
36
+ export { default as Calendar } from './components/Calendar.svelte';
37
+ export { toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from './date.js';
38
+ export { default as Dropzone } from './components/Dropzone.svelte';
39
+ /* --- data display --- */
40
+ export { default as Badge } from './components/Badge.svelte';
41
+ export { default as Avatar } from './components/Avatar.svelte';
42
+ export { default as AvatarGroup } from './components/AvatarGroup.svelte';
43
+ export { default as Card } from './components/Card.svelte';
44
+ export { default as Stat } from './components/Stat.svelte';
45
+ export { default as Table } from './components/Table.svelte';
46
+ export { default as Kbd } from './components/Kbd.svelte';
47
+ /* --- feedback --- */
48
+ export { default as Alert } from './components/Alert.svelte';
49
+ export { default as Progress } from './components/Progress.svelte';
50
+ export { default as Spinner } from './components/Spinner.svelte';
51
+ export { default as Skeleton } from './components/Skeleton.svelte';
52
+ export { default as EmptyState } from './components/EmptyState.svelte';
53
+ export { default as Toaster } from './components/Toaster.svelte';
54
+ /* --- layout & navigation --- */
55
+ export { default as Divider } from './components/Divider.svelte';
56
+ export { default as Tabs } from './components/Tabs.svelte';
57
+ export { default as Accordion } from './components/Accordion.svelte';
58
+ export { default as AccordionItem } from './components/AccordionItem.svelte';
59
+ export { default as Breadcrumb } from './components/Breadcrumb.svelte';
60
+ export { default as Pagination } from './components/Pagination.svelte';
61
+ export { default as Steps } from './components/Steps.svelte';
62
+ /* --- app shell --- */
63
+ export { default as Navbar } from './components/Navbar.svelte';
64
+ export { default as Sidebar } from './components/Sidebar.svelte';
65
+ export { default as Footer } from './components/Footer.svelte';
66
+ /* --- overlay --- */
67
+ export { default as Modal } from './components/Modal.svelte';
68
+ export { default as Drawer } from './components/Drawer.svelte';
69
+ export { default as ConfirmDialog } from './components/ConfirmDialog.svelte';
70
+ export { default as CommandPalette } from './components/CommandPalette.svelte';
71
+ export { default as ContextMenu } from './components/ContextMenu.svelte';
72
+ export { default as Dropdown } from './components/Dropdown.svelte';
73
+ export { default as MenuItem } from './components/MenuItem.svelte';
74
+ export { default as MenuSeparator } from './components/MenuSeparator.svelte';
75
+ export { default as Popover } from './components/Popover.svelte';
76
+ export { default as ThemeToggle, applyTheme, storedTheme } from './components/ThemeToggle.svelte';
77
+ export { default as Tooltip } from './components/Tooltip.svelte';
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Every string a component renders on its own — labels, ARIA names, the words
3
+ * inside a rejected-file message. Override them once instead of passing a prop
4
+ * to every instance.
5
+ *
6
+ * Two ways in:
7
+ *
8
+ * 1. `setLocale(idID)` at app start — one global locale, the common case.
9
+ * 2. `<LocaleProvider locale={idID}>` — scoped to a subtree, and the right
10
+ * choice under SSR, where module state is shared between requests.
11
+ *
12
+ * Per-instance props still win over both: `<Table empty="…" />`.
13
+ */
14
+ export interface Locale {
15
+ close: string;
16
+ dismiss: string;
17
+ loading: string;
18
+ cancel: string;
19
+ confirm: string;
20
+ clear: string;
21
+ search: string;
22
+ menu: string;
23
+ navigation: string;
24
+ openMenu: string;
25
+ closeMenu: string;
26
+ collapseSidebar: string;
27
+ expandSidebar: string;
28
+ notifications: string;
29
+ commandPlaceholder: string;
30
+ noResults: string;
31
+ noData: string;
32
+ selectAllRows: string;
33
+ selectRow: string;
34
+ pagination: string;
35
+ previousPage: string;
36
+ nextPage: string;
37
+ breadcrumb: string;
38
+ comboboxPlaceholder: string;
39
+ noMatches: string;
40
+ dropFiles: string;
41
+ or: string;
42
+ browse: string;
43
+ remove: string;
44
+ rejectedType: string;
45
+ rejectedSize: string;
46
+ rejectedCount: string;
47
+ /** Byte-size units, smallest first. */
48
+ byteUnits: [string, string, string, string];
49
+ showPassword: string;
50
+ hidePassword: string;
51
+ /** Five buckets, from empty to strongest. */
52
+ passwordStrength: [string, string, string, string, string];
53
+ decrease: string;
54
+ increase: string;
55
+ /** BCP 47 tag handed to `Intl` for month and weekday names. */
56
+ dateLocale: string;
57
+ /** Order of the date parts the input accepts and prints. */
58
+ dateFormat: 'dmy' | 'mdy' | 'ymd';
59
+ today: string;
60
+ previousMonth: string;
61
+ nextMonth: string;
62
+ selectDate: string;
63
+ selected: string;
64
+ removeItem: string;
65
+ toggleTheme: string;
66
+ lightMode: string;
67
+ darkMode: string;
68
+ }
69
+ export declare const enUS: Locale;
70
+ /** Bahasa Indonesia. Pass to `setLocale` or `<LocaleProvider>`. */
71
+ export declare const idID: Locale;
72
+ export declare class LocaleStore {
73
+ current: Locale;
74
+ /** Merges on top of what is already there. */
75
+ set(next: Partial<Locale>): void;
76
+ /**
77
+ * Replaces everything with `enUS` plus `next`. Unlike `set` this never reads
78
+ * `current`, so it is safe to call from an effect without the write feeding
79
+ * back into the effect's own dependencies.
80
+ */
81
+ replace(next: Partial<Locale>): void;
82
+ }
83
+ /** The global locale, used by any component with no provider above it. */
84
+ export declare const locale: LocaleStore;
85
+ /** Replaces some or all of the global strings. Call once, at app start. */
86
+ export declare function setLocale(next: Partial<Locale>): void;
87
+ /**
88
+ * Scopes a locale to the current component subtree. Call during init — the
89
+ * `LocaleProvider` component does exactly this.
90
+ */
91
+ export declare function provideLocale(next: Partial<Locale>): LocaleStore;
92
+ /** The nearest provided locale, falling back to the global one. */
93
+ export declare function useLocale(): LocaleStore;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Every string a component renders on its own — labels, ARIA names, the words
3
+ * inside a rejected-file message. Override them once instead of passing a prop
4
+ * to every instance.
5
+ *
6
+ * Two ways in:
7
+ *
8
+ * 1. `setLocale(idID)` at app start — one global locale, the common case.
9
+ * 2. `<LocaleProvider locale={idID}>` — scoped to a subtree, and the right
10
+ * choice under SSR, where module state is shared between requests.
11
+ *
12
+ * Per-instance props still win over both: `<Table empty="…" />`.
13
+ */
14
+ import { getContext, setContext } from 'svelte';
15
+ export const enUS = {
16
+ close: 'Close',
17
+ dismiss: 'Dismiss',
18
+ loading: 'Loading',
19
+ cancel: 'Cancel',
20
+ confirm: 'Confirm',
21
+ clear: 'Clear selection',
22
+ search: 'Search',
23
+ menu: 'Menu',
24
+ navigation: 'Navigation',
25
+ openMenu: 'Open menu',
26
+ closeMenu: 'Close menu',
27
+ collapseSidebar: 'Collapse sidebar',
28
+ expandSidebar: 'Expand sidebar',
29
+ notifications: 'Notifications',
30
+ commandPlaceholder: 'Type a command or search…',
31
+ noResults: 'No results',
32
+ noData: 'No data',
33
+ selectAllRows: 'Select all rows',
34
+ selectRow: 'Select row',
35
+ pagination: 'Pagination',
36
+ previousPage: 'Previous page',
37
+ nextPage: 'Next page',
38
+ breadcrumb: 'Breadcrumb',
39
+ comboboxPlaceholder: 'Search…',
40
+ noMatches: 'No matches',
41
+ dropFiles: 'Drop files here',
42
+ or: 'or',
43
+ browse: 'browse',
44
+ remove: 'Remove',
45
+ rejectedType: 'wrong file type',
46
+ rejectedSize: 'too large',
47
+ rejectedCount: 'over the file limit',
48
+ byteUnits: ['B', 'KB', 'MB', 'GB'],
49
+ showPassword: 'Show password',
50
+ hidePassword: 'Hide password',
51
+ passwordStrength: ['Too short', 'Weak', 'Fair', 'Good', 'Strong'],
52
+ decrease: 'Decrease',
53
+ increase: 'Increase',
54
+ dateLocale: 'en-US',
55
+ dateFormat: 'mdy',
56
+ today: 'Today',
57
+ previousMonth: 'Previous month',
58
+ nextMonth: 'Next month',
59
+ selectDate: 'Select a date',
60
+ selected: 'selected',
61
+ removeItem: 'Remove',
62
+ toggleTheme: 'Toggle theme',
63
+ lightMode: 'Light',
64
+ darkMode: 'Dark'
65
+ };
66
+ /** Bahasa Indonesia. Pass to `setLocale` or `<LocaleProvider>`. */
67
+ export const idID = {
68
+ close: 'Tutup',
69
+ dismiss: 'Tutup',
70
+ loading: 'Memuat',
71
+ cancel: 'Batal',
72
+ confirm: 'Konfirmasi',
73
+ clear: 'Hapus pilihan',
74
+ search: 'Cari',
75
+ menu: 'Menu',
76
+ navigation: 'Navigasi',
77
+ openMenu: 'Buka menu',
78
+ closeMenu: 'Tutup menu',
79
+ collapseSidebar: 'Ciutkan bilah sisi',
80
+ expandSidebar: 'Bentangkan bilah sisi',
81
+ notifications: 'Notifikasi',
82
+ commandPlaceholder: 'Ketik perintah atau cari…',
83
+ noResults: 'Tidak ada hasil',
84
+ noData: 'Tidak ada data',
85
+ selectAllRows: 'Pilih semua baris',
86
+ selectRow: 'Pilih baris',
87
+ pagination: 'Paginasi',
88
+ previousPage: 'Halaman sebelumnya',
89
+ nextPage: 'Halaman berikutnya',
90
+ breadcrumb: 'Remah roti',
91
+ comboboxPlaceholder: 'Cari…',
92
+ noMatches: 'Tidak ditemukan',
93
+ dropFiles: 'Letakkan berkas di sini',
94
+ or: 'atau',
95
+ browse: 'jelajahi',
96
+ remove: 'Hapus',
97
+ rejectedType: 'tipe berkas tidak sesuai',
98
+ rejectedSize: 'ukuran terlalu besar',
99
+ rejectedCount: 'melebihi batas jumlah berkas',
100
+ byteUnits: ['B', 'KB', 'MB', 'GB'],
101
+ showPassword: 'Tampilkan kata sandi',
102
+ hidePassword: 'Sembunyikan kata sandi',
103
+ passwordStrength: ['Terlalu pendek', 'Lemah', 'Cukup', 'Baik', 'Kuat'],
104
+ decrease: 'Kurangi',
105
+ increase: 'Tambah',
106
+ dateLocale: 'id-ID',
107
+ dateFormat: 'dmy',
108
+ today: 'Hari ini',
109
+ previousMonth: 'Bulan sebelumnya',
110
+ nextMonth: 'Bulan berikutnya',
111
+ selectDate: 'Pilih tanggal',
112
+ selected: 'dipilih',
113
+ removeItem: 'Hapus',
114
+ toggleTheme: 'Ganti tema',
115
+ lightMode: 'Terang',
116
+ darkMode: 'Gelap'
117
+ };
118
+ export class LocaleStore {
119
+ current = $state({ ...enUS });
120
+ /** Merges on top of what is already there. */
121
+ set(next) {
122
+ this.current = { ...this.current, ...next };
123
+ }
124
+ /**
125
+ * Replaces everything with `enUS` plus `next`. Unlike `set` this never reads
126
+ * `current`, so it is safe to call from an effect without the write feeding
127
+ * back into the effect's own dependencies.
128
+ */
129
+ replace(next) {
130
+ this.current = { ...enUS, ...next };
131
+ }
132
+ }
133
+ const KEY = Symbol.for('@nqmcreative/ui:locale');
134
+ /** The global locale, used by any component with no provider above it. */
135
+ export const locale = new LocaleStore();
136
+ /** Replaces some or all of the global strings. Call once, at app start. */
137
+ export function setLocale(next) {
138
+ locale.set(next);
139
+ }
140
+ /**
141
+ * Scopes a locale to the current component subtree. Call during init — the
142
+ * `LocaleProvider` component does exactly this.
143
+ */
144
+ export function provideLocale(next) {
145
+ const store = new LocaleStore();
146
+ store.replace(next);
147
+ setContext(KEY, store);
148
+ return store;
149
+ }
150
+ /** The nearest provided locale, falling back to the global one. */
151
+ export function useLocale() {
152
+ return getContext(KEY) ?? locale;
153
+ }
package/dist/theme.css ADDED
@@ -0,0 +1,210 @@
1
+ /*
2
+ * @nqmcreative/ui design tokens
3
+ * Mirrors the visual language of https://nqmcreative.com
4
+ * Flat, no shadows, no border-radius (except pills). Import this after
5
+ * `@import "tailwindcss";` in your app's CSS entry point. Ships light and dark
6
+ * palettes — see the bottom of this file.
7
+ *
8
+ * Colour system: `brand` (teal green) is the primary. Every other hue below
9
+ * follows the same 4-step shape so components can swap tones freely:
10
+ * --color-<tone> base / solid fill / text on light surfaces
11
+ * --color-<tone>-hover darker step for hover + active
12
+ * --color-<tone>-light tinted surface (soft variants, alerts, badges)
13
+ * --color-<tone>-border hairline that reads against the tinted surface
14
+ */
15
+ @theme {
16
+ /* --- surfaces --- */
17
+ --color-bg: #ffffff;
18
+ --color-bg-alt: #f8fafc;
19
+ --color-bg-inset: #f1f5f9;
20
+ --color-hairline: #e5e7eb;
21
+ --color-hairline-strong: #cbd5e1;
22
+
23
+ /* --- text --- */
24
+ --color-text: #0f172a;
25
+ --color-text-secondary: #475569;
26
+ --color-text-muted: #64748b;
27
+ --color-text-inverse: #ffffff;
28
+
29
+ /* --- brand / primary (teal green) --- */
30
+ --color-brand: #1c6358;
31
+ --color-brand-hover: #155047;
32
+ --color-brand-light: #e8f3f1;
33
+ --color-brand-border: #a8ccc5;
34
+
35
+ /* --- accent (copper) --- */
36
+ --color-accent: #c2410c;
37
+ --color-accent-hover: #9a3412;
38
+ --color-accent-light: #fdf1e9;
39
+ --color-accent-border: #f0c5a8;
40
+
41
+ /* --- violet --- */
42
+ --color-violet: #6d28d9;
43
+ --color-violet-hover: #5b21b6;
44
+ --color-violet-light: #f3edfd;
45
+ --color-violet-border: #cfbdf5;
46
+
47
+ /* --- info (blue) --- */
48
+ --color-info: #1d4ed8;
49
+ --color-info-hover: #1e40af;
50
+ --color-info-light: #eaf0fd;
51
+ --color-info-border: #b8caf7;
52
+
53
+ /* --- success (green) --- */
54
+ --color-success: #15803d;
55
+ --color-success-hover: #166534;
56
+ --color-success-light: #e9f6ed;
57
+ --color-success-border: #a8d9b8;
58
+
59
+ /* --- warning (amber) --- */
60
+ --color-warning: #b45309;
61
+ --color-warning-hover: #92400e;
62
+ --color-warning-light: #fdf4e6;
63
+ --color-warning-border: #f0d3a3;
64
+
65
+ /* --- danger (red) --- */
66
+ --color-danger: #b91c1c;
67
+ --color-danger-hover: #991b1b;
68
+ --color-danger-light: #fdecec;
69
+ --color-danger-border: #f2b9b9;
70
+
71
+ /* --- neutral (slate) --- */
72
+ --color-neutral: #475569;
73
+ --color-neutral-hover: #334155;
74
+ --color-neutral-light: #f1f5f9;
75
+ --color-neutral-border: #cbd5e1;
76
+
77
+ /* --- type --- */
78
+ --font-sans: 'Work Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
79
+ --font-heading: 'Sora', ui-sans-serif, system-ui, sans-serif;
80
+ --font-mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace;
81
+
82
+ /* --- motion --- */
83
+ --ease-brand: cubic-bezier(0.16, 1, 0.3, 1);
84
+ --ease-brand-out: cubic-bezier(0.25, 1, 0.5, 1);
85
+ }
86
+
87
+ /*
88
+ * Dark mode. Two ways in, and they cooperate:
89
+ *
90
+ * 1. put `class="dark"` on <html> — an explicit choice always wins
91
+ * 2. do nothing — the OS preference applies, unless <html> says `class="light"`
92
+ *
93
+ * Both blocks below carry the same values on purpose; keep them in sync.
94
+ * Every utility built on these tokens (`bg-bg`, `text-text-secondary`,
95
+ * `bg-brand`, `border-hairline`, the whole tone system) follows automatically —
96
+ * components need no `dark:` classes of their own.
97
+ */
98
+ @custom-variant dark (&:where(.dark, .dark *));
99
+
100
+ .dark {
101
+ color-scheme: dark;
102
+
103
+ --color-bg: #0f172a;
104
+ --color-bg-alt: #16203a;
105
+ --color-bg-inset: #1e293b;
106
+ --color-hairline: #1e293b;
107
+ --color-hairline-strong: #334155;
108
+
109
+ --color-text: #f1f5f9;
110
+ --color-text-secondary: #cbd5e1;
111
+ --color-text-muted: #94a3b8;
112
+ --color-text-inverse: #0f172a;
113
+
114
+ --color-brand: #3f9585;
115
+ --color-brand-hover: #55ab9a;
116
+ --color-brand-light: #10302b;
117
+ --color-brand-border: #235047;
118
+
119
+ --color-accent: #ea7d4b;
120
+ --color-accent-hover: #f59564;
121
+ --color-accent-light: #3a1d0e;
122
+ --color-accent-border: #6b3417;
123
+
124
+ --color-violet: #a78bfa;
125
+ --color-violet-hover: #bda5fb;
126
+ --color-violet-light: #241a3d;
127
+ --color-violet-border: #40306b;
128
+
129
+ --color-info: #6b9bf7;
130
+ --color-info-hover: #8fb4f9;
131
+ --color-info-light: #14213f;
132
+ --color-info-border: #26406e;
133
+
134
+ --color-success: #46a06a;
135
+ --color-success-hover: #5cb47e;
136
+ --color-success-light: #10281a;
137
+ --color-success-border: #23492f;
138
+
139
+ --color-warning: #d99b3f;
140
+ --color-warning-hover: #e8ae55;
141
+ --color-warning-light: #33240c;
142
+ --color-warning-border: #5c421a;
143
+
144
+ --color-danger: #ef6b6b;
145
+ --color-danger-hover: #f38585;
146
+ --color-danger-light: #3a1616;
147
+ --color-danger-border: #6b2626;
148
+
149
+ --color-neutral: #94a3b8;
150
+ --color-neutral-hover: #cbd5e1;
151
+ --color-neutral-light: #1e293b;
152
+ --color-neutral-border: #334155;
153
+ }
154
+
155
+ @media (prefers-color-scheme: dark) {
156
+ :root:not(.light) {
157
+ color-scheme: dark;
158
+
159
+ --color-bg: #0f172a;
160
+ --color-bg-alt: #16203a;
161
+ --color-bg-inset: #1e293b;
162
+ --color-hairline: #1e293b;
163
+ --color-hairline-strong: #334155;
164
+
165
+ --color-text: #f1f5f9;
166
+ --color-text-secondary: #cbd5e1;
167
+ --color-text-muted: #94a3b8;
168
+ --color-text-inverse: #0f172a;
169
+
170
+ --color-brand: #3f9585;
171
+ --color-brand-hover: #55ab9a;
172
+ --color-brand-light: #10302b;
173
+ --color-brand-border: #235047;
174
+
175
+ --color-accent: #ea7d4b;
176
+ --color-accent-hover: #f59564;
177
+ --color-accent-light: #3a1d0e;
178
+ --color-accent-border: #6b3417;
179
+
180
+ --color-violet: #a78bfa;
181
+ --color-violet-hover: #bda5fb;
182
+ --color-violet-light: #241a3d;
183
+ --color-violet-border: #40306b;
184
+
185
+ --color-info: #6b9bf7;
186
+ --color-info-hover: #8fb4f9;
187
+ --color-info-light: #14213f;
188
+ --color-info-border: #26406e;
189
+
190
+ --color-success: #46a06a;
191
+ --color-success-hover: #5cb47e;
192
+ --color-success-light: #10281a;
193
+ --color-success-border: #23492f;
194
+
195
+ --color-warning: #d99b3f;
196
+ --color-warning-hover: #e8ae55;
197
+ --color-warning-light: #33240c;
198
+ --color-warning-border: #5c421a;
199
+
200
+ --color-danger: #ef6b6b;
201
+ --color-danger-hover: #f38585;
202
+ --color-danger-light: #3a1616;
203
+ --color-danger-border: #6b2626;
204
+
205
+ --color-neutral: #94a3b8;
206
+ --color-neutral-hover: #cbd5e1;
207
+ --color-neutral-light: #1e293b;
208
+ --color-neutral-border: #334155;
209
+ }
210
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Toast queue. One module-level store, so any file can fire a toast without
3
+ * prop-drilling — mount a single `<Toaster />` once, near the root.
4
+ *
5
+ * import { toast } from '@nqmcreative/ui';
6
+ * toast.success('Project archived');
7
+ * toast.error('Upload failed', { description: 'The file is over 25 MB.' });
8
+ * const id = toast.loading('Rendering…');
9
+ * toast.dismiss(id);
10
+ */
11
+ import type { Tone } from './tones.js';
12
+ export interface ToastAction {
13
+ label: string;
14
+ onclick: () => void;
15
+ }
16
+ export interface ToastOptions {
17
+ description?: string;
18
+ tone?: Tone;
19
+ /** Milliseconds on screen. `0` keeps it until dismissed. Default 5000. */
20
+ duration?: number;
21
+ action?: ToastAction;
22
+ /** Swaps the glyph for a spinner and defaults `duration` to 0. */
23
+ loading?: boolean;
24
+ }
25
+ export interface Toast extends ToastOptions {
26
+ id: number;
27
+ title: string;
28
+ tone: Tone;
29
+ duration: number;
30
+ }
31
+ declare class ToastStore {
32
+ items: Toast[];
33
+ private timers;
34
+ /** Fires a toast and returns its id, for `update` or `dismiss`. */
35
+ show(title: string, options?: ToastOptions): number;
36
+ /** Replaces a live toast in place — a loading toast becoming a result. */
37
+ update(id: number, title: string, options?: ToastOptions): void;
38
+ dismiss(id: number): void;
39
+ clear(): void;
40
+ success: (title: string, options?: ToastOptions) => number;
41
+ error: (title: string, options?: ToastOptions) => number;
42
+ warning: (title: string, options?: ToastOptions) => number;
43
+ info: (title: string, options?: ToastOptions) => number;
44
+ loading: (title: string, options?: ToastOptions) => number;
45
+ }
46
+ export declare const toast: ToastStore;
47
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Toast queue. One module-level store, so any file can fire a toast without
3
+ * prop-drilling — mount a single `<Toaster />` once, near the root.
4
+ *
5
+ * import { toast } from '@nqmcreative/ui';
6
+ * toast.success('Project archived');
7
+ * toast.error('Upload failed', { description: 'The file is over 25 MB.' });
8
+ * const id = toast.loading('Rendering…');
9
+ * toast.dismiss(id);
10
+ */
11
+ let nextId = 0;
12
+ class ToastStore {
13
+ items = $state([]);
14
+ timers = new Map();
15
+ /** Fires a toast and returns its id, for `update` or `dismiss`. */
16
+ show(title, options = {}) {
17
+ const id = nextId++;
18
+ const duration = options.duration ?? (options.loading ? 0 : 5000);
19
+ const toast = { ...options, id, title, tone: options.tone ?? 'neutral', duration };
20
+ this.items = [...this.items, toast];
21
+ if (duration > 0)
22
+ this.timers.set(id, setTimeout(() => this.dismiss(id), duration));
23
+ return id;
24
+ }
25
+ /** Replaces a live toast in place — a loading toast becoming a result. */
26
+ update(id, title, options = {}) {
27
+ const existing = this.items.find((t) => t.id === id);
28
+ if (!existing)
29
+ return;
30
+ clearTimeout(this.timers.get(id));
31
+ const duration = options.duration ?? (options.loading ? 0 : 5000);
32
+ const next = {
33
+ ...existing,
34
+ ...options,
35
+ loading: options.loading ?? false,
36
+ title,
37
+ tone: options.tone ?? existing.tone,
38
+ duration
39
+ };
40
+ this.items = this.items.map((t) => (t.id === id ? next : t));
41
+ if (duration > 0)
42
+ this.timers.set(id, setTimeout(() => this.dismiss(id), duration));
43
+ }
44
+ dismiss(id) {
45
+ clearTimeout(this.timers.get(id));
46
+ this.timers.delete(id);
47
+ this.items = this.items.filter((t) => t.id !== id);
48
+ }
49
+ clear() {
50
+ this.timers.forEach(clearTimeout);
51
+ this.timers.clear();
52
+ this.items = [];
53
+ }
54
+ success = (title, options) => this.show(title, { ...options, tone: 'success' });
55
+ error = (title, options) => this.show(title, { ...options, tone: 'danger', duration: options?.duration ?? 8000 });
56
+ warning = (title, options) => this.show(title, { ...options, tone: 'warning' });
57
+ info = (title, options) => this.show(title, { ...options, tone: 'info' });
58
+ loading = (title, options) => this.show(title, { ...options, tone: 'brand', loading: true });
59
+ }
60
+ export const toast = new ToastStore();