seblify 0.2.1 → 0.3.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 (47) hide show
  1. package/README.md +118 -45
  2. package/dist/components/action/input/select/checkbox/Checkbox.svelte +378 -16
  3. package/dist/components/action/input/select/checkbox/Checkbox.svelte.d.ts +7 -0
  4. package/dist/components/action/input/select/checkbox/checkbox-relations.svelte.d.ts +47 -0
  5. package/dist/components/action/input/select/checkbox/checkbox-relations.svelte.js +154 -0
  6. package/dist/components/action/input/select/switch/Switch.svelte +349 -40
  7. package/dist/components/action/input/select/switch/Switch.svelte.d.ts +8 -3
  8. package/dist/components/display/annotation/tag/Tag.svelte +19 -0
  9. package/dist/components/display/data-display/metric/duration/Duration.svelte +64 -0
  10. package/dist/components/display/data-display/metric/duration/Duration.svelte.d.ts +16 -0
  11. package/dist/components/display/data-display/metric/relative-time/RelativeTime.svelte +104 -0
  12. package/dist/components/display/data-display/metric/relative-time/RelativeTime.svelte.d.ts +16 -0
  13. package/dist/components/display/data-display/visual/timeline/TimelineItem.svelte +19 -0
  14. package/dist/components/display/feedback/loading/loading-dots/LoadingDots.svelte +19 -0
  15. package/dist/components/display/feedback/loading/spinner/Spinner.svelte +19 -0
  16. package/dist/components/display/motion/collapse/Collapse.svelte +115 -0
  17. package/dist/components/display/motion/collapse/Collapse.svelte.d.ts +17 -0
  18. package/dist/components/display/typography/accordion/Accordion.svelte +347 -0
  19. package/dist/components/display/typography/accordion/Accordion.svelte.d.ts +13 -0
  20. package/dist/components/display/typography/accordion/AccordionGroup.svelte +132 -0
  21. package/dist/components/display/typography/accordion/AccordionGroup.svelte.d.ts +11 -0
  22. package/dist/components/display/typography/accordion/context.d.ts +8 -0
  23. package/dist/components/display/typography/accordion/context.js +8 -0
  24. package/dist/components/framework/overlay/tooltip/Tooltip.svelte +1012 -0
  25. package/dist/components/framework/overlay/tooltip/Tooltip.svelte.d.ts +31 -0
  26. package/dist/components/utility/format/duration/formatDuration.d.ts +15 -0
  27. package/dist/components/utility/format/duration/formatDuration.js +218 -0
  28. package/dist/components/utility/format/relative-time/formatRelativeTime.d.ts +35 -0
  29. package/dist/components/utility/format/relative-time/formatRelativeTime.js +343 -0
  30. package/dist/design-system/reserved-props/allowed-options.d.ts +6 -3
  31. package/dist/design-system/theme/tokens.js +48 -0
  32. package/dist/icons/generated/check-thick.svelte +16 -0
  33. package/dist/icons/generated/check-thick.svelte.d.ts +7 -0
  34. package/dist/icons/generated/check.svelte +16 -0
  35. package/dist/icons/generated/check.svelte.d.ts +7 -0
  36. package/dist/icons/generated/close-thick.svelte +16 -0
  37. package/dist/icons/generated/close-thick.svelte.d.ts +7 -0
  38. package/dist/icons/generated/close.svelte +16 -0
  39. package/dist/icons/generated/close.svelte.d.ts +7 -0
  40. package/dist/icons/generated/index.d.ts +4 -0
  41. package/dist/icons/generated/index.js +4 -0
  42. package/dist/icons/generated/manifest.js +28 -0
  43. package/dist/index.d.ts +9 -0
  44. package/dist/index.js +9 -0
  45. package/dist/styles/tone-context.css +19 -0
  46. package/dist/theme.css +22 -0
  47. package/package.json +15 -3
@@ -0,0 +1,347 @@
1
+ <script lang="ts" module>
2
+ let nextAccordionId = 0;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import type { SeblifyProps } from 'seblify';
7
+ import type { Snippet } from 'svelte';
8
+ import { onMount, tick } from 'svelte';
9
+ import ChevronRightIcon from '../../../../icons/generated/chevron-right.svelte';
10
+ import Collapse from '../../motion/collapse/Collapse.svelte';
11
+ import { getAccordionGroupContext } from './context';
12
+
13
+ const COLLAPSE_DURATION_MS = 220;
14
+
15
+ type Props = SeblifyProps<{
16
+ [key: string]: unknown;
17
+ children?: Snippet;
18
+ class?: string;
19
+ description?: string;
20
+ disabled?: boolean;
21
+ open?: boolean;
22
+ title: string;
23
+ value?: string;
24
+ }>;
25
+
26
+ let {
27
+ children,
28
+ class: className,
29
+ description,
30
+ disabled = false,
31
+ open = $bindable(false),
32
+ title,
33
+ value,
34
+ ...restProps
35
+ }: Props = $props();
36
+
37
+ const group = getAccordionGroupContext();
38
+ const generatedValue = `accordion-${++nextAccordionId}`;
39
+ const itemValue = $derived(value ?? generatedValue);
40
+ let closeTimer: ReturnType<typeof setTimeout> | undefined;
41
+ let hasInitialized = false;
42
+ let isRegisteredWithGroup = $state(false);
43
+ let motionVersion = 0;
44
+ let nativeOpen = $state(open);
45
+ let visualOpen = $state(open);
46
+
47
+ function clearCloseTimer() {
48
+ if (closeTimer) {
49
+ clearTimeout(closeTimer);
50
+ closeTimer = undefined;
51
+ }
52
+ }
53
+
54
+ function nextFrame(): Promise<void> {
55
+ return new Promise((resolve) => {
56
+ if (typeof requestAnimationFrame === 'function') {
57
+ requestAnimationFrame(() => resolve());
58
+ return;
59
+ }
60
+
61
+ setTimeout(resolve, 0);
62
+ });
63
+ }
64
+
65
+ function finishClose(version = motionVersion) {
66
+ if (version !== motionVersion || open) {
67
+ return;
68
+ }
69
+
70
+ clearCloseTimer();
71
+ nativeOpen = false;
72
+ }
73
+
74
+ $effect(() => {
75
+ const nextOpen = open;
76
+ const version = ++motionVersion;
77
+
78
+ clearCloseTimer();
79
+
80
+ if (!hasInitialized) {
81
+ hasInitialized = true;
82
+ nativeOpen = nextOpen;
83
+ visualOpen = nextOpen;
84
+ return;
85
+ }
86
+
87
+ if (nextOpen) {
88
+ nativeOpen = true;
89
+
90
+ void tick()
91
+ .then(nextFrame)
92
+ .then(() => {
93
+ if (version !== motionVersion || !open) {
94
+ return;
95
+ }
96
+
97
+ visualOpen = true;
98
+ });
99
+
100
+ return;
101
+ }
102
+
103
+ nativeOpen = true;
104
+ visualOpen = false;
105
+
106
+ closeTimer = setTimeout(() => {
107
+ finishClose(version);
108
+ }, COLLAPSE_DURATION_MS + 80);
109
+ });
110
+
111
+ onMount(() => {
112
+ if (!group) {
113
+ return;
114
+ }
115
+
116
+ group.register(itemValue, open);
117
+ open = group.isOpen(itemValue);
118
+ isRegisteredWithGroup = true;
119
+
120
+ return () => {
121
+ isRegisteredWithGroup = false;
122
+ group.unregister(itemValue);
123
+ };
124
+ });
125
+
126
+ $effect(() => {
127
+ if (!group || !isRegisteredWithGroup) {
128
+ return;
129
+ }
130
+
131
+ const groupOpen = group.isOpen(itemValue);
132
+
133
+ if (open !== groupOpen) {
134
+ open = groupOpen;
135
+ }
136
+ });
137
+
138
+ function handleSummaryClick(event: MouseEvent) {
139
+ event.preventDefault();
140
+
141
+ if (disabled) {
142
+ event.stopPropagation();
143
+ return;
144
+ }
145
+
146
+ setOpenState(!open);
147
+ }
148
+
149
+ function handleSummaryKeydown(event: KeyboardEvent) {
150
+ if (event.key !== 'Enter' && event.key !== ' ') {
151
+ return;
152
+ }
153
+
154
+ event.preventDefault();
155
+
156
+ if (disabled) {
157
+ event.stopPropagation();
158
+ return;
159
+ }
160
+
161
+ setOpenState(!open);
162
+ }
163
+
164
+ function handleToggle(event: Event) {
165
+ const details = event.currentTarget as HTMLDetailsElement;
166
+
167
+ if (disabled) {
168
+ details.open = nativeOpen;
169
+ return;
170
+ }
171
+
172
+ if (details.open !== nativeOpen) {
173
+ setOpenState(details.open);
174
+ }
175
+ }
176
+
177
+ function setOpenState(nextOpen: boolean) {
178
+ if (group) {
179
+ group.setOpen(itemValue, nextOpen);
180
+ open = group.isOpen(itemValue);
181
+ return;
182
+ }
183
+
184
+ open = nextOpen;
185
+ }
186
+
187
+ function handleCollapseTransitionEnd(event: TransitionEvent) {
188
+ if (event.propertyName !== 'height') {
189
+ return;
190
+ }
191
+
192
+ finishClose();
193
+ }
194
+ </script>
195
+
196
+ <details
197
+ {...restProps}
198
+ class={`accordion${className ? ` ${className}` : ''}`}
199
+ data-disabled={disabled}
200
+ data-state={open ? 'open' : 'closed'}
201
+ open={nativeOpen}
202
+ ontoggle={handleToggle}
203
+ >
204
+ <summary
205
+ class="accordion__summary"
206
+ aria-disabled={disabled}
207
+ aria-expanded={open}
208
+ onclick={handleSummaryClick}
209
+ onkeydown={handleSummaryKeydown}
210
+ tabindex={disabled ? -1 : undefined}
211
+ >
212
+ <span class="accordion__summary-text">
213
+ <span class="accordion__title">{title}</span>
214
+ {#if description}
215
+ <span class="accordion__description">{description}</span>
216
+ {/if}
217
+ </span>
218
+ <span class="accordion__icon" aria-hidden="true">
219
+ <ChevronRightIcon size="1.125rem" />
220
+ </span>
221
+ </summary>
222
+
223
+ <Collapse
224
+ class="accordion__collapse"
225
+ expanded={visualOpen}
226
+ aria-hidden={!open}
227
+ ontransitionend={handleCollapseTransitionEnd}
228
+ >
229
+ <div class="accordion__body">
230
+ {@render children?.()}
231
+ </div>
232
+ </Collapse>
233
+ </details>
234
+
235
+ <style>
236
+ .accordion {
237
+ --accordion-radius: 8px;
238
+ --accordion-border: var(--seblify-color-border, #c8d2df);
239
+ --accordion-border-hover: var(--seblify-color-border-strong, #aab7c5);
240
+ --accordion-surface: var(--seblify-color-surface, #ffffff);
241
+ --accordion-surface-hover: var(--seblify-color-surface-hover, #f1f4f9);
242
+ --accordion-text: var(--seblify-color-text, #172033);
243
+ --accordion-description: var(--seblify-color-text-muted, #516176);
244
+ --accordion-focus-ring: color-mix(
245
+ in srgb,
246
+ var(--seblify-color-focus-ring, #9bd4d8) 72%,
247
+ transparent
248
+ );
249
+
250
+ display: block;
251
+ border: 1px solid var(--accordion-border);
252
+ border-radius: var(--accordion-radius);
253
+ background: var(--accordion-surface);
254
+ color: var(--accordion-text);
255
+ }
256
+
257
+ .accordion__summary {
258
+ display: grid;
259
+ grid-template-columns: minmax(0, 1fr) auto;
260
+ gap: 0.75rem;
261
+ align-items: center;
262
+ min-height: 3rem;
263
+ padding: 0.75rem 1rem;
264
+ border-radius: calc(var(--accordion-radius) - 1px);
265
+ cursor: pointer;
266
+ list-style: none;
267
+ }
268
+
269
+ .accordion[data-state='open'] .accordion__summary {
270
+ border-bottom-left-radius: 0;
271
+ border-bottom-right-radius: 0;
272
+ }
273
+
274
+ .accordion__summary::-webkit-details-marker {
275
+ display: none;
276
+ }
277
+
278
+ .accordion__summary:hover {
279
+ background: var(--accordion-surface-hover);
280
+ }
281
+
282
+ .accordion__summary:focus-visible {
283
+ outline: 3px solid var(--accordion-focus-ring);
284
+ outline-offset: 2px;
285
+ }
286
+
287
+ .accordion__summary-text {
288
+ display: grid;
289
+ gap: 0.25rem;
290
+ min-width: 0;
291
+ }
292
+
293
+ .accordion__title {
294
+ overflow-wrap: anywhere;
295
+ font-size: 0.95rem;
296
+ font-weight: 650;
297
+ line-height: 1.35;
298
+ }
299
+
300
+ .accordion__description {
301
+ overflow-wrap: anywhere;
302
+ color: var(--accordion-description);
303
+ font-size: 0.875rem;
304
+ line-height: 1.4;
305
+ }
306
+
307
+ .accordion__icon {
308
+ display: inline-flex;
309
+ color: var(--accordion-description);
310
+ transition: transform 160ms ease;
311
+ }
312
+
313
+ .accordion[data-state='open'] .accordion__icon {
314
+ transform: rotate(90deg);
315
+ }
316
+
317
+ .accordion :global(.accordion__collapse) {
318
+ display: block;
319
+ }
320
+
321
+ .accordion__body {
322
+ padding: 0 1rem 1rem;
323
+ color: var(--accordion-text);
324
+ font-size: 0.95rem;
325
+ line-height: 1.55;
326
+ }
327
+
328
+ .accordion__body :global(:first-child) {
329
+ margin-top: 0;
330
+ }
331
+
332
+ .accordion__body :global(:last-child) {
333
+ margin-bottom: 0;
334
+ }
335
+
336
+ .accordion[data-disabled='true'] {
337
+ opacity: var(--seblify-opacity-disabled, 0.56);
338
+ }
339
+
340
+ .accordion[data-disabled='true'] .accordion__summary {
341
+ cursor: not-allowed;
342
+ }
343
+
344
+ .accordion[data-disabled='true'] .accordion__summary:hover {
345
+ background: transparent;
346
+ }
347
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ declare const Accordion: import("svelte").Component<{
3
+ [x: string]: unknown;
4
+ children?: Snippet | undefined;
5
+ class?: string | undefined;
6
+ description?: string | undefined;
7
+ disabled?: boolean | undefined;
8
+ open?: boolean | undefined;
9
+ title: string;
10
+ value?: string | undefined;
11
+ }, {}, "open">;
12
+ type Accordion = ReturnType<typeof Accordion>;
13
+ export default Accordion;
@@ -0,0 +1,132 @@
1
+ <script lang="ts">
2
+ import type { SeblifyProps } from 'seblify';
3
+ import type { Snippet } from 'svelte';
4
+ import { setAccordionGroupContext } from './context';
5
+
6
+ type AccordionGroupElement = keyof HTMLElementTagNameMap;
7
+
8
+ type Props = SeblifyProps<{
9
+ [key: string]: unknown;
10
+ as?: AccordionGroupElement;
11
+ children?: Snippet;
12
+ class?: string;
13
+ multiple?: boolean;
14
+ }>;
15
+
16
+ let {
17
+ as = 'div',
18
+ children,
19
+ class: className,
20
+ multiple = false,
21
+ ...restProps
22
+ }: Props = $props();
23
+
24
+ let itemState = $state<Record<string, boolean>>({});
25
+
26
+ function closeOtherItems(value: string) {
27
+ for (const itemValue of Object.keys(itemState)) {
28
+ if (itemValue !== value) {
29
+ itemState[itemValue] = false;
30
+ }
31
+ }
32
+ }
33
+
34
+ function register(value: string, initialOpen: boolean) {
35
+ if (value in itemState) {
36
+ return;
37
+ }
38
+
39
+ if (initialOpen && !multiple) {
40
+ closeOtherItems(value);
41
+ }
42
+
43
+ itemState[value] = initialOpen;
44
+ }
45
+
46
+ function unregister(value: string) {
47
+ delete itemState[value];
48
+ }
49
+
50
+ function isOpen(value: string): boolean {
51
+ return itemState[value] === true;
52
+ }
53
+
54
+ function setOpen(value: string, open: boolean) {
55
+ if (open && !multiple) {
56
+ closeOtherItems(value);
57
+ }
58
+
59
+ itemState[value] = open;
60
+ }
61
+
62
+ $effect(() => {
63
+ if (multiple) {
64
+ return;
65
+ }
66
+
67
+ const openValues = Object.entries(itemState)
68
+ .filter(([, open]) => open)
69
+ .map(([value]) => value);
70
+
71
+ for (const value of openValues.slice(1)) {
72
+ itemState[value] = false;
73
+ }
74
+ });
75
+
76
+ setAccordionGroupContext({
77
+ isOpen,
78
+ register,
79
+ setOpen,
80
+ unregister,
81
+ });
82
+ </script>
83
+
84
+ <svelte:element
85
+ this={as}
86
+ {...restProps}
87
+ class={`accordion-group${className ? ` ${className}` : ''}`}
88
+ data-multiple={multiple}
89
+ >
90
+ {@render children?.()}
91
+ </svelte:element>
92
+
93
+ <style>
94
+ .accordion-group {
95
+ --accordion-group-radius: 8px;
96
+
97
+ display: block;
98
+ }
99
+
100
+ .accordion-group :global(.accordion) {
101
+ border-radius: 0;
102
+ }
103
+
104
+ .accordion-group :global(.accordion + .accordion) {
105
+ margin-top: -1px;
106
+ }
107
+
108
+ .accordion-group :global(.accordion .accordion__summary) {
109
+ border-radius: 0;
110
+ }
111
+
112
+ .accordion-group :global(.accordion:first-child) {
113
+ border-top-left-radius: var(--accordion-group-radius);
114
+ border-top-right-radius: var(--accordion-group-radius);
115
+ }
116
+
117
+ .accordion-group :global(.accordion:first-child .accordion__summary) {
118
+ border-top-left-radius: calc(var(--accordion-group-radius) - 1px);
119
+ border-top-right-radius: calc(var(--accordion-group-radius) - 1px);
120
+ }
121
+
122
+ .accordion-group :global(.accordion:last-child) {
123
+ border-bottom-left-radius: var(--accordion-group-radius);
124
+ border-bottom-right-radius: var(--accordion-group-radius);
125
+ }
126
+
127
+ .accordion-group
128
+ :global(.accordion:last-child[data-state='closed'] .accordion__summary) {
129
+ border-bottom-left-radius: calc(var(--accordion-group-radius) - 1px);
130
+ border-bottom-right-radius: calc(var(--accordion-group-radius) - 1px);
131
+ }
132
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ type AccordionGroupElement = keyof HTMLElementTagNameMap;
3
+ declare const AccordionGroup: import("svelte").Component<{
4
+ [x: string]: unknown;
5
+ as?: AccordionGroupElement | undefined;
6
+ children?: Snippet | undefined;
7
+ class?: string | undefined;
8
+ multiple?: boolean | undefined;
9
+ }, {}, "">;
10
+ type AccordionGroup = ReturnType<typeof AccordionGroup>;
11
+ export default AccordionGroup;
@@ -0,0 +1,8 @@
1
+ export interface AccordionGroupContext {
2
+ isOpen: (value: string) => boolean;
3
+ register: (value: string, initialOpen: boolean) => void;
4
+ setOpen: (value: string, open: boolean) => void;
5
+ unregister: (value: string) => void;
6
+ }
7
+ export declare function setAccordionGroupContext(value: AccordionGroupContext): void;
8
+ export declare function getAccordionGroupContext(): AccordionGroupContext | undefined;
@@ -0,0 +1,8 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const accordionGroupContextKey = Symbol('seblify-accordion-group');
3
+ export function setAccordionGroupContext(value) {
4
+ setContext(accordionGroupContextKey, value);
5
+ }
6
+ export function getAccordionGroupContext() {
7
+ return getContext(accordionGroupContextKey);
8
+ }