@nqmcreative/ui 0.4.1 → 0.4.3
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/README.md +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/tones.d.ts +10 -0
- package/dist/core/tones.js +19 -0
- package/dist/core/trigger.d.ts +8 -0
- package/dist/core/trigger.js +11 -0
- package/dist/styles/matte/Alert.svelte +1 -1
- package/dist/styles/matte/Avatar.svelte +16 -3
- package/dist/styles/matte/Button.svelte +9 -2
- package/dist/styles/matte/Checkbox.svelte +49 -30
- package/dist/styles/matte/CheckboxGroup.svelte +15 -4
- package/dist/styles/matte/Combobox.svelte +12 -3
- package/dist/styles/matte/CommandPalette.svelte +1 -1
- package/dist/styles/matte/ConfirmDialog.svelte +10 -2
- package/dist/styles/matte/ContextMenu.svelte +1 -1
- package/dist/styles/matte/DatePicker.svelte +10 -3
- package/dist/styles/matte/Divider.svelte +1 -1
- package/dist/styles/matte/Drawer.svelte +10 -2
- package/dist/styles/matte/Dropdown.svelte +1 -1
- package/dist/styles/matte/Field.svelte +59 -3
- package/dist/styles/matte/Footer.svelte +14 -3
- package/dist/styles/matte/Input.svelte +9 -2
- package/dist/styles/matte/Label.svelte +3 -1
- package/dist/styles/matte/Link.svelte +13 -2
- package/dist/styles/matte/MenuItem.svelte +11 -4
- package/dist/styles/matte/Modal.svelte +11 -3
- package/dist/styles/matte/MultiSelect.svelte +1 -1
- package/dist/styles/matte/Navbar.svelte +2 -2
- package/dist/styles/matte/Popover.svelte +1 -1
- package/dist/styles/matte/Radio.svelte +41 -22
- package/dist/styles/matte/RadioGroup.svelte +15 -4
- package/dist/styles/matte/Select.svelte +4 -2
- package/dist/styles/matte/Skeleton.svelte +2 -1
- package/dist/styles/matte/Stat.svelte +27 -3
- package/dist/styles/matte/Switch.svelte +6 -1
- package/dist/styles/matte/Tabs.svelte +1 -1
- package/dist/styles/matte/Textarea.svelte +4 -2
- package/dist/styles/matte/Toaster.svelte +21 -3
- package/dist/styles/matte/Tooltip.svelte +8 -1
- package/dist/styles/matte/index.d.ts +1 -1
- package/dist/styles/matte/index.js +1 -1
- package/dist/styles/matte/theme.css +64 -1
- package/dist/styles/paper/AccordionItem.svelte +38 -11
- package/dist/styles/paper/AccordionItem.svelte.d.ts +1 -1
- package/dist/styles/paper/Alert.svelte +2 -2
- package/dist/styles/paper/Avatar.svelte +14 -3
- package/dist/styles/paper/Button.svelte +9 -2
- package/dist/styles/paper/Card.svelte +1 -1
- package/dist/styles/paper/Checkbox.svelte +55 -36
- package/dist/styles/paper/CheckboxGroup.svelte +15 -4
- package/dist/styles/paper/Combobox.svelte +12 -3
- package/dist/styles/paper/CommandPalette.svelte +1 -1
- package/dist/styles/paper/ConfirmDialog.svelte +8 -2
- package/dist/styles/paper/ContextMenu.svelte +1 -1
- package/dist/styles/paper/DatePicker.svelte +10 -3
- package/dist/styles/paper/Divider.svelte +1 -1
- package/dist/styles/paper/Drawer.svelte +8 -2
- package/dist/styles/paper/Dropdown.svelte +1 -1
- package/dist/styles/paper/Field.svelte +59 -3
- package/dist/styles/paper/Footer.svelte +14 -3
- package/dist/styles/paper/Input.svelte +9 -2
- package/dist/styles/paper/Label.svelte +3 -1
- package/dist/styles/paper/Link.svelte +13 -2
- package/dist/styles/paper/MenuItem.svelte +11 -4
- package/dist/styles/paper/Modal.svelte +9 -3
- package/dist/styles/paper/MultiSelect.svelte +1 -1
- package/dist/styles/paper/Navbar.svelte +2 -2
- package/dist/styles/paper/Popover.svelte +1 -1
- package/dist/styles/paper/Radio.svelte +41 -22
- package/dist/styles/paper/RadioGroup.svelte +15 -4
- package/dist/styles/paper/SegmentedControl.svelte +2 -2
- package/dist/styles/paper/Select.svelte +4 -2
- package/dist/styles/paper/Skeleton.svelte +2 -1
- package/dist/styles/paper/Stat.svelte +24 -2
- package/dist/styles/paper/Switch.svelte +6 -1
- package/dist/styles/paper/Tabs.svelte +1 -1
- package/dist/styles/paper/Textarea.svelte +4 -2
- package/dist/styles/paper/Toaster.svelte +19 -3
- package/dist/styles/paper/Tooltip.svelte +8 -1
- package/dist/styles/paper/index.d.ts +1 -1
- package/dist/styles/paper/index.js +1 -1
- package/dist/styles/paper/theme.css +85 -13
- package/dist/styles/sprout/Alert.svelte +3 -3
- package/dist/styles/sprout/Avatar.svelte +14 -3
- package/dist/styles/sprout/Breadcrumb.svelte +1 -1
- package/dist/styles/sprout/Button.svelte +8 -4
- package/dist/styles/sprout/Calendar.svelte +1 -1
- package/dist/styles/sprout/Card.svelte +2 -2
- package/dist/styles/sprout/Checkbox.svelte +55 -36
- package/dist/styles/sprout/CheckboxGroup.svelte +15 -4
- package/dist/styles/sprout/ColorInput.svelte +1 -1
- package/dist/styles/sprout/Combobox.svelte +12 -3
- package/dist/styles/sprout/ConfirmDialog.svelte +8 -2
- package/dist/styles/sprout/DatePicker.svelte +11 -4
- package/dist/styles/sprout/Divider.svelte +1 -1
- package/dist/styles/sprout/Drawer.svelte +8 -2
- package/dist/styles/sprout/Dropzone.svelte +1 -1
- package/dist/styles/sprout/Field.svelte +59 -3
- package/dist/styles/sprout/FileInput.svelte +3 -3
- package/dist/styles/sprout/Footer.svelte +14 -3
- package/dist/styles/sprout/Input.svelte +9 -2
- package/dist/styles/sprout/Label.svelte +3 -1
- package/dist/styles/sprout/Link.svelte +13 -2
- package/dist/styles/sprout/LogoCloud.svelte +1 -1
- package/dist/styles/sprout/MenuItem.svelte +11 -4
- package/dist/styles/sprout/Modal.svelte +8 -2
- package/dist/styles/sprout/MultiSelect.svelte +3 -3
- package/dist/styles/sprout/Navbar.svelte +2 -2
- package/dist/styles/sprout/Radio.svelte +41 -22
- package/dist/styles/sprout/RadioGroup.svelte +15 -4
- package/dist/styles/sprout/SearchInput.svelte +1 -1
- package/dist/styles/sprout/SegmentedControl.svelte +2 -2
- package/dist/styles/sprout/Select.svelte +4 -2
- package/dist/styles/sprout/Sidebar.svelte +2 -1
- package/dist/styles/sprout/Skeleton.svelte +2 -1
- package/dist/styles/sprout/Stat.svelte +24 -2
- package/dist/styles/sprout/Switch.svelte +6 -1
- package/dist/styles/sprout/Table.svelte +1 -1
- package/dist/styles/sprout/Tabs.svelte +1 -1
- package/dist/styles/sprout/TagsInput.svelte +1 -1
- package/dist/styles/sprout/Textarea.svelte +4 -2
- package/dist/styles/sprout/TimeInput.svelte +1 -1
- package/dist/styles/sprout/Toaster.svelte +22 -6
- package/dist/styles/sprout/Tooltip.svelte +8 -1
- package/dist/styles/sprout/index.d.ts +2 -2
- package/dist/styles/sprout/index.js +2 -2
- package/dist/styles/sprout/lift.d.ts +14 -0
- package/dist/styles/sprout/lift.js +23 -0
- package/dist/styles/sprout/theme.css +68 -24
- package/package.json +1 -1
- package/registry.json +4 -2
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
...rest
|
|
41
41
|
}: Props = $props();
|
|
42
42
|
|
|
43
|
+
const uid = $props.id();
|
|
44
|
+
|
|
43
45
|
let dialog: HTMLDialogElement | null = $state(null);
|
|
44
46
|
|
|
45
47
|
const closable = $derived(showClose ?? dismissible);
|
|
@@ -65,6 +67,8 @@
|
|
|
65
67
|
|
|
66
68
|
<dialog
|
|
67
69
|
bind:this={dialog}
|
|
70
|
+
aria-labelledby={title ? `${uid}-title` : undefined}
|
|
71
|
+
aria-describedby={description ? `${uid}-description` : undefined}
|
|
68
72
|
onclose={close}
|
|
69
73
|
onclick={onBackdrop}
|
|
70
74
|
oncancel={(event) => {
|
|
@@ -81,10 +85,12 @@
|
|
|
81
85
|
{#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
|
|
82
86
|
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
83
87
|
{#if title}
|
|
84
|
-
<h2 class="font-heading text-base font-semibold text-text">{title}</h2>
|
|
88
|
+
<h2 id="{uid}-title" class="font-heading text-base font-semibold text-text">{title}</h2>
|
|
85
89
|
{/if}
|
|
86
90
|
{#if description}
|
|
87
|
-
<p class="font-sans text-[13px] text-text-muted">
|
|
91
|
+
<p id="{uid}-description" class="font-sans text-[13px] text-text-muted">
|
|
92
|
+
{description}
|
|
93
|
+
</p>
|
|
88
94
|
{/if}
|
|
89
95
|
</div>
|
|
90
96
|
{#if closable}
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
type="button"
|
|
162
162
|
onclick={() => remove(option)}
|
|
163
163
|
aria-label="Remove {option.label}"
|
|
164
|
-
class="inline-flex size-4 items-center justify-center rounded transition-colors duration-150 hover:bg-bg/70 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current"
|
|
164
|
+
class="inline-flex size-4 items-center justify-center rounded-full transition-colors duration-150 hover:bg-bg/70 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current"
|
|
165
165
|
>
|
|
166
166
|
<svg class="size-2.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
167
167
|
<path
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
inputEl?.focus();
|
|
211
211
|
}}
|
|
212
212
|
aria-label="Clear selection"
|
|
213
|
-
class="shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
213
|
+
class="shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
214
214
|
>
|
|
215
215
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
216
216
|
<path
|
|
@@ -261,7 +261,7 @@
|
|
|
261
261
|
{index === active ? toneSoft[tone] : 'text-text-secondary'}"
|
|
262
262
|
>
|
|
263
263
|
<span
|
|
264
|
-
class="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded border transition-colors duration-100
|
|
264
|
+
class="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-md border transition-colors duration-100
|
|
265
265
|
{checked ? `border-transparent ${toneText[tone]}` : 'border-hairline-strong'}"
|
|
266
266
|
>
|
|
267
267
|
{#if checked}
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
class="{link} {focusRing} {toneRing[tone]} {item.active
|
|
112
112
|
? toneSoft[tone]
|
|
113
113
|
: 'text-text-secondary hover:bg-bg-inset hover:text-text'} {item.disabled
|
|
114
|
-
? 'pointer-events-none opacity-
|
|
114
|
+
? 'pointer-events-none opacity-40'
|
|
115
115
|
: ''}"
|
|
116
116
|
>
|
|
117
117
|
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
class="flex items-center justify-between gap-3 rounded-xl px-3 py-2.5 font-sans text-sm font-medium {item.active
|
|
165
165
|
? `${toneSoft[tone]}`
|
|
166
166
|
: 'text-text-secondary hover:bg-bg-inset'} {item.disabled
|
|
167
|
-
? 'pointer-events-none opacity-
|
|
167
|
+
? 'pointer-events-none opacity-40'
|
|
168
168
|
: ''}"
|
|
169
169
|
>
|
|
170
170
|
<span class="flex min-w-0 items-center gap-2.5">
|
|
@@ -25,32 +25,51 @@
|
|
|
25
25
|
children,
|
|
26
26
|
...rest
|
|
27
27
|
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const uid = $props.id();
|
|
28
30
|
</script>
|
|
29
31
|
|
|
30
|
-
<
|
|
31
|
-
class="
|
|
32
|
-
|
|
32
|
+
<div
|
|
33
|
+
class="inline-block font-sans text-sm {disabled
|
|
34
|
+
? 'pointer-events-none opacity-50'
|
|
35
|
+
: ''} {className}"
|
|
33
36
|
>
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
<label class="group inline-flex items-start gap-2.5 {disabled ? '' : 'cursor-pointer'}">
|
|
38
|
+
<input
|
|
39
|
+
type="radio"
|
|
40
|
+
bind:group
|
|
41
|
+
{value}
|
|
42
|
+
{disabled}
|
|
43
|
+
aria-describedby={description ? `${uid}-description` : undefined}
|
|
44
|
+
class="peer sr-only"
|
|
45
|
+
{...rest}
|
|
46
|
+
/>
|
|
47
|
+
<span
|
|
48
|
+
class="mt-0.5 flex size-[18px] shrink-0 items-center justify-center rounded-full border border-hairline-strong bg-bg {edge} transition-colors duration-150 ease-brand-out
|
|
37
49
|
{tonePeerChecked[tone]} {peerFocusRing} {tonePeerFocus[tone]}"
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
>
|
|
52
|
+
<span
|
|
53
|
+
class="size-1.5 rounded-full bg-text-inverse opacity-0 transition-opacity duration-150 group-has-[:checked]:opacity-100"
|
|
54
|
+
></span>
|
|
55
|
+
</span>
|
|
56
|
+
{#if label || children}
|
|
57
|
+
<span class="flex flex-col gap-0.5">
|
|
58
|
+
{#if children}
|
|
59
|
+
{@render children()}
|
|
60
|
+
{:else if label}
|
|
61
|
+
<span class="leading-snug text-text">{label}</span>
|
|
62
|
+
{/if}
|
|
63
|
+
</span>
|
|
64
|
+
{/if}
|
|
65
|
+
</label>
|
|
66
|
+
{#if description}
|
|
67
|
+
<!-- ponytail: the indent tracks the 18px dot plus the label's gap; move one, move both. -->
|
|
40
68
|
<span
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<span class="flex flex-col gap-0.5">
|
|
46
|
-
{#if children}
|
|
47
|
-
{@render children()}
|
|
48
|
-
{:else if label}
|
|
49
|
-
<span class="leading-snug text-text">{label}</span>
|
|
50
|
-
{/if}
|
|
51
|
-
{#if description}
|
|
52
|
-
<span class="text-[13px] leading-snug text-text-muted">{description}</span>
|
|
53
|
-
{/if}
|
|
69
|
+
id="{uid}-description"
|
|
70
|
+
class="mt-0.5 block pl-[28px] text-[13px] leading-snug text-text-muted"
|
|
71
|
+
>
|
|
72
|
+
{description}
|
|
54
73
|
</span>
|
|
55
74
|
{/if}
|
|
56
|
-
</
|
|
75
|
+
</div>
|
|
@@ -45,13 +45,24 @@
|
|
|
45
45
|
children,
|
|
46
46
|
...rest
|
|
47
47
|
}: Props = $props();
|
|
48
|
+
|
|
49
|
+
const uid = $props.id();
|
|
50
|
+
// The group's own hint and error sit outside every control in it, so the
|
|
51
|
+
// fieldset is what has to point at them.
|
|
52
|
+
const messageId = $derived(error ? `${uid}-error` : hint ? `${uid}-hint` : undefined);
|
|
48
53
|
</script>
|
|
49
54
|
|
|
50
|
-
<fieldset
|
|
55
|
+
<fieldset
|
|
56
|
+
aria-describedby={messageId}
|
|
57
|
+
class="flex flex-col gap-3 border-0 p-0 {className}"
|
|
58
|
+
{...rest}
|
|
59
|
+
>
|
|
51
60
|
{#if legend}
|
|
52
61
|
<legend class="mb-1 font-sans text-sm font-medium text-text">
|
|
53
62
|
{legend}
|
|
54
|
-
{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span
|
|
63
|
+
{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span><span
|
|
64
|
+
class="sr-only">(required)</span
|
|
65
|
+
>{/if}
|
|
55
66
|
</legend>
|
|
56
67
|
{/if}
|
|
57
68
|
|
|
@@ -95,8 +106,8 @@
|
|
|
95
106
|
</div>
|
|
96
107
|
|
|
97
108
|
{#if error}
|
|
98
|
-
<p class="font-sans text-[13px] text-danger">{error}</p>
|
|
109
|
+
<p id="{uid}-error" role="alert" class="font-sans text-[13px] text-danger">{error}</p>
|
|
99
110
|
{:else if hint}
|
|
100
|
-
<p class="font-sans text-[13px] text-text-muted">{hint}</p>
|
|
111
|
+
<p id="{uid}-hint" class="font-sans text-[13px] text-text-muted">{hint}</p>
|
|
101
112
|
{/if}
|
|
102
113
|
</fieldset>
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
type="button"
|
|
74
74
|
onclick={clear}
|
|
75
75
|
aria-label="Clear search"
|
|
76
|
-
class="-mr-1 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
76
|
+
class="-mr-1 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
77
77
|
>
|
|
78
78
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
79
79
|
<path
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
-
import { focusRing, toneRing, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { focusRing, toneRing, toneText, type Tone } from '../../core/tones.js';
|
|
5
5
|
import { iconMd } from './icon.js';
|
|
6
6
|
import { edge } from './lift.js';
|
|
7
7
|
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
onclick={() => select(option)}
|
|
94
94
|
class="inline-flex items-center justify-center rounded-xl font-sans font-medium whitespace-nowrap transition-all duration-150 ease-brand-out disabled:pointer-events-none disabled:opacity-40
|
|
95
95
|
{iconOnly ? squares[size] : sizes[size]} {focusRing} {toneRing[tone]}
|
|
96
|
-
{value === option.value ? `bg-bg
|
|
96
|
+
{value === option.value ? `bg-bg ${toneText[tone]} ${edge}` : 'text-text-muted hover:text-text'}"
|
|
97
97
|
>
|
|
98
98
|
{#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
|
|
99
99
|
{#if !iconOnly}{option.label}{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
4
|
-
import { toneFocusBorder, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { focusRing, toneFocusBorder, toneRing, type Tone } from '../../core/tones.js';
|
|
5
5
|
import { edge } from './lift.js';
|
|
6
6
|
|
|
7
7
|
export interface SelectOption {
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
<select
|
|
37
37
|
bind:value
|
|
38
38
|
aria-invalid={invalid ? 'true' : undefined}
|
|
39
|
-
class="
|
|
39
|
+
class="{focusRing} {toneRing[
|
|
40
|
+
tone
|
|
41
|
+
]} h-10 w-full appearance-none rounded-xl border bg-bg py-0 pr-9 pl-3 font-sans text-sm text-text {edge} transition-colors duration-150 ease-brand-out disabled:pointer-events-none disabled:opacity-50
|
|
40
42
|
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusBorder[tone]}`} {className}"
|
|
41
43
|
{...rest}
|
|
42
44
|
>
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
<script lang="ts">
|
|
38
38
|
import { iconMd } from './icon.js';
|
|
39
|
+
import { soft } from './lift.js';
|
|
39
40
|
import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
|
|
40
41
|
|
|
41
42
|
interface Props {
|
|
@@ -90,7 +91,7 @@
|
|
|
90
91
|
const surfaces: Record<SidebarVariant, string> = {
|
|
91
92
|
plain: 'h-full bg-transparent',
|
|
92
93
|
filled: 'h-full border-r border-hairline bg-bg-alt',
|
|
93
|
-
floating:
|
|
94
|
+
floating: `m-3 h-[calc(100%-1.5rem)] rounded-2xl border border-hairline bg-bg-alt ${soft}`
|
|
94
95
|
};
|
|
95
96
|
|
|
96
97
|
const row =
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
31
|
{#if variant === 'text' && lines > 1}
|
|
32
|
-
<div class="flex w-full flex-col gap-2 {className}" {...rest}>
|
|
32
|
+
<div aria-hidden="true" class="flex w-full flex-col gap-2 {className}" {...rest}>
|
|
33
33
|
{#each { length: lines }, i (i)}
|
|
34
34
|
<div
|
|
35
35
|
class="h-4 animate-pulse rounded-xl bg-bg-inset {i === lines - 1 ? 'w-2/3' : 'w-full'}"
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
{:else}
|
|
41
41
|
<div
|
|
42
|
+
aria-hidden="true"
|
|
42
43
|
class="animate-pulse bg-bg-inset {variants[variant]} {className}"
|
|
43
44
|
style="{width ? `width:${width};` : ''}{height ? `height:${height};` : ''}"
|
|
44
45
|
{...rest}
|
|
@@ -32,7 +32,15 @@
|
|
|
32
32
|
}: Props = $props();
|
|
33
33
|
|
|
34
34
|
const trendTone: Record<StatTrend, Tone> = { up: 'success', down: 'danger', flat: 'neutral' };
|
|
35
|
-
|
|
35
|
+
// Drawn rather than typed. `↑` renders in whatever face the reader's system
|
|
36
|
+
// substitutes for it and is announced as "up arrow" or nothing at all, and
|
|
37
|
+
// the colour beside it says nothing either — so the word ships with it.
|
|
38
|
+
const trendPath: Record<StatTrend, string> = {
|
|
39
|
+
up: 'M7 11V3.5M7 3l3.5 3.5M7 3 3.5 6.5',
|
|
40
|
+
down: 'M7 3v7.5M7 11l3.5-3.5M7 11 3.5 7.5',
|
|
41
|
+
flat: 'M3 7h7.5M11 7 7.5 3.5M11 7l-3.5 3.5'
|
|
42
|
+
};
|
|
43
|
+
const trendWord: Record<StatTrend, string> = { up: 'up', down: 'down', flat: 'no change' };
|
|
36
44
|
</script>
|
|
37
45
|
|
|
38
46
|
<div class="flex flex-col gap-1 font-sans {className}" {...rest}>
|
|
@@ -49,7 +57,21 @@
|
|
|
49
57
|
</span>
|
|
50
58
|
{#if delta}
|
|
51
59
|
<span class="text-sm font-medium {toneText[trendTone[trend]]}">
|
|
52
|
-
|
|
60
|
+
<svg
|
|
61
|
+
class="inline-block size-[1em] align-[-0.115em]"
|
|
62
|
+
viewBox="0 0 14 14"
|
|
63
|
+
fill="none"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
>
|
|
66
|
+
<path
|
|
67
|
+
d={trendPath[trend]}
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
stroke-width="1.6"
|
|
70
|
+
stroke-linecap="round"
|
|
71
|
+
stroke-linejoin="round"
|
|
72
|
+
/>
|
|
73
|
+
</svg>
|
|
74
|
+
<span class="sr-only">{trendWord[trend]}</span>
|
|
53
75
|
{delta}
|
|
54
76
|
</span>
|
|
55
77
|
{/if}
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
md: { track: 'h-6 w-11', thumb: 'size-5', travel: 'translate-x-5' }
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
const uid = $props.id();
|
|
35
|
+
|
|
34
36
|
function toggle() {
|
|
35
37
|
checked = !checked;
|
|
36
38
|
onchange?.(checked);
|
|
@@ -43,6 +45,7 @@
|
|
|
43
45
|
role="switch"
|
|
44
46
|
aria-checked={checked}
|
|
45
47
|
aria-label={label}
|
|
48
|
+
aria-describedby={description ? `${uid}-description` : undefined}
|
|
46
49
|
{disabled}
|
|
47
50
|
onclick={toggle}
|
|
48
51
|
class="relative mt-0.5 inline-flex shrink-0 items-center rounded-full p-0.5 transition-colors duration-200 ease-brand-out disabled:pointer-events-none
|
|
@@ -58,7 +61,9 @@
|
|
|
58
61
|
<span class="flex flex-col gap-0.5 font-sans text-sm">
|
|
59
62
|
{#if label}<span class="leading-snug text-text">{label}</span>{/if}
|
|
60
63
|
{#if description}
|
|
61
|
-
<span class="text-[13px] leading-snug text-text-muted"
|
|
64
|
+
<span id="{uid}-description" class="text-[13px] leading-snug text-text-muted"
|
|
65
|
+
>{description}</span
|
|
66
|
+
>
|
|
62
67
|
{/if}
|
|
63
68
|
</span>
|
|
64
69
|
{/if}
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
<button
|
|
196
196
|
type="button"
|
|
197
197
|
onclick={() => toggleSort(column)}
|
|
198
|
-
class="inline-flex items-center gap-1.5 rounded transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current
|
|
198
|
+
class="inline-flex items-center gap-1.5 rounded-md transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current
|
|
199
199
|
{sort?.key === column.key ? 'text-text' : ''}"
|
|
200
200
|
>
|
|
201
201
|
{column.label}
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
type="button"
|
|
136
136
|
onclick={() => remove(index)}
|
|
137
137
|
aria-label="Remove {tag}"
|
|
138
|
-
class="inline-flex size-4 items-center justify-center rounded transition-colors duration-150 hover:bg-bg/70 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current"
|
|
138
|
+
class="inline-flex size-4 items-center justify-center rounded-full transition-colors duration-150 hover:bg-bg/70 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current"
|
|
139
139
|
>
|
|
140
140
|
<svg class="size-2.5" viewBox="0 0 10 10" fill="none" aria-hidden="true">
|
|
141
141
|
<path
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
3
|
-
import { toneFocusBorder, type Tone } from '../../core/tones.js';
|
|
3
|
+
import { focusRing, toneFocusBorder, toneRing, type Tone } from '../../core/tones.js';
|
|
4
4
|
import { edge } from './lift.js';
|
|
5
5
|
|
|
6
6
|
interface Props extends HTMLTextareaAttributes {
|
|
@@ -49,7 +49,9 @@
|
|
|
49
49
|
bind:value
|
|
50
50
|
{rows}
|
|
51
51
|
aria-invalid={invalid ? 'true' : undefined}
|
|
52
|
-
class="
|
|
52
|
+
class="{focusRing} {toneRing[
|
|
53
|
+
tone
|
|
54
|
+
]} w-full rounded-xl border bg-bg px-3 py-2 font-sans text-sm text-text {edge} transition-colors duration-150 ease-brand-out placeholder:text-text-muted disabled:pointer-events-none disabled:opacity-50
|
|
53
55
|
{autoresize ? 'resize-none overflow-hidden' : 'resize-y'}
|
|
54
56
|
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusBorder[tone]}`} {className}"
|
|
55
57
|
{...rest}></textarea>
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const stepper =
|
|
74
|
-
'grid size-6 place-items-center rounded text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current';
|
|
74
|
+
'grid size-6 place-items-center rounded-lg text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current';
|
|
75
75
|
</script>
|
|
76
76
|
|
|
77
77
|
<Input
|
|
@@ -31,8 +31,24 @@
|
|
|
31
31
|
const fromLeft = $derived(position.endsWith('left'));
|
|
32
32
|
const fromCenter = $derived(position.endsWith('center'));
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* A card sliding in from the edge of the screen, and a stack of them shuffling
|
|
36
|
+
* to make room, is exactly the motion this preference exists to stop. Under it
|
|
37
|
+
* the toast fades in place and the stack reorders instantly — the state change
|
|
38
|
+
* still reads, it just stops travelling.
|
|
39
|
+
*
|
|
40
|
+
* ponytail: read once at mount. Someone who flips the OS setting mid-session
|
|
41
|
+
* gets the new behaviour on the next load; a live listener is the upgrade.
|
|
42
|
+
*/
|
|
43
|
+
const stillness =
|
|
44
|
+
typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
45
|
+
|
|
34
46
|
const enter = $derived(
|
|
35
|
-
|
|
47
|
+
stillness
|
|
48
|
+
? { x: 0, y: 0 }
|
|
49
|
+
: fromCenter
|
|
50
|
+
? { y: fromTop ? -16 : 16, x: 0 }
|
|
51
|
+
: { x: fromLeft ? -16 : 16, y: 0 }
|
|
36
52
|
);
|
|
37
53
|
</script>
|
|
38
54
|
|
|
@@ -46,12 +62,12 @@
|
|
|
46
62
|
>
|
|
47
63
|
{#each toast.items as item (item.id)}
|
|
48
64
|
<!--
|
|
49
|
-
sprout puts every toast on the same
|
|
65
|
+
sprout puts every toast on the same white card and lets the glyph
|
|
50
66
|
carry the tone. matte tints the whole surface; here a tinted block
|
|
51
67
|
would fight the warm page underneath.
|
|
52
68
|
-->
|
|
53
69
|
<div
|
|
54
|
-
animate:flip={{ duration: 200 }}
|
|
70
|
+
animate:flip={{ duration: stillness ? 0 : 200 }}
|
|
55
71
|
in:fly={{ ...enter, duration: 220 }}
|
|
56
72
|
out:fly={{ ...enter, duration: 160 }}
|
|
57
73
|
role={item.tone === 'danger' ? 'alert' : 'status'}
|
|
@@ -93,7 +109,7 @@
|
|
|
93
109
|
</span>
|
|
94
110
|
|
|
95
111
|
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
96
|
-
<p class="text-sm leading-snug font-semibold text-text">{item.title}</p>
|
|
112
|
+
<p class="font-heading text-sm leading-snug font-semibold text-text">{item.title}</p>
|
|
97
113
|
{#if item.description}
|
|
98
114
|
<p class="text-[13px] leading-relaxed text-text-secondary">{item.description}</p>
|
|
99
115
|
{/if}
|
|
@@ -104,7 +120,7 @@
|
|
|
104
120
|
item.action?.onclick();
|
|
105
121
|
toast.dismiss(item.id);
|
|
106
122
|
}}
|
|
107
|
-
class="mt-1 self-start rounded font-sans text-[13px] font-semibold underline underline-offset-2 {toneText[
|
|
123
|
+
class="mt-1 self-start rounded-md font-sans text-[13px] font-semibold underline underline-offset-2 {toneText[
|
|
108
124
|
item.tone
|
|
109
125
|
]}"
|
|
110
126
|
>
|
|
@@ -117,7 +133,7 @@
|
|
|
117
133
|
type="button"
|
|
118
134
|
onclick={() => toast.dismiss(item.id)}
|
|
119
135
|
aria-label="Dismiss"
|
|
120
|
-
class="-m-1 shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
136
|
+
class="-m-1 shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
121
137
|
>
|
|
122
138
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
123
139
|
<path
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import { describedBy } from '../../core/trigger.js';
|
|
3
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
5
|
import { soft } from './lift.js';
|
|
5
6
|
|
|
@@ -14,6 +15,11 @@
|
|
|
14
15
|
|
|
15
16
|
let { content, placement = 'top', class: className = '', children, ...rest }: Props = $props();
|
|
16
17
|
|
|
18
|
+
const uid = $props.id();
|
|
19
|
+
let wrapper: HTMLElement | null = $state(null);
|
|
20
|
+
|
|
21
|
+
$effect(() => describedBy(wrapper, `${uid}-tip`));
|
|
22
|
+
|
|
17
23
|
const placements: Record<TooltipPlacement, string> = {
|
|
18
24
|
top: 'bottom-full left-1/2 mb-1.5 -translate-x-1/2',
|
|
19
25
|
bottom: 'top-full left-1/2 mt-1.5 -translate-x-1/2',
|
|
@@ -22,9 +28,10 @@
|
|
|
22
28
|
};
|
|
23
29
|
</script>
|
|
24
30
|
|
|
25
|
-
<span class="group/tt relative inline-flex" {...rest}>
|
|
31
|
+
<span bind:this={wrapper} class="group/tt relative inline-flex" {...rest}>
|
|
26
32
|
{@render children()}
|
|
27
33
|
<span
|
|
34
|
+
id="{uid}-tip"
|
|
28
35
|
role="tooltip"
|
|
29
36
|
class="pointer-events-none invisible absolute z-50 rounded-xl bg-text px-2 py-1 font-sans text-xs font-medium whitespace-nowrap text-text-inverse opacity-0 {soft} transition-opacity duration-150 ease-brand-out group-focus-within/tt:visible group-focus-within/tt:opacity-100 group-hover/tt:visible group-hover/tt:opacity-100
|
|
30
37
|
{placements[placement]} {className}"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sprout — Warm and fully rounded — leaf green on
|
|
2
|
+
* Sprout — Warm and fully rounded — leaf green on white, pill buttons, and a round display face. For school, community and family sites.
|
|
3
3
|
*
|
|
4
4
|
* Generated by `bun run index`. Behaviour comes from
|
|
5
5
|
* `@nqmcreative/ui/core`, re-exported below so this one import covers
|
|
6
6
|
* the whole style.
|
|
7
7
|
*/
|
|
8
|
-
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText, anchored, clickOutside, focusTrap, focusable, navigateList, portal, toast, applyTheme, storedTheme, toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from '../../core/index.js';
|
|
8
|
+
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, focusWithinRing, toneFocusWithinRing, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText, anchored, clickOutside, focusTrap, focusable, navigateList, portal, toast, applyTheme, storedTheme, toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from '../../core/index.js';
|
|
9
9
|
export type { Tone, Placement, AnchorOptions, ClickOutsideOptions, FocusTrapOptions, Toast, ToastOptions, ToastAction, Theme, DateFormat, TableSort, SortDirection, RejectedFile } from '../../core/index.js';
|
|
10
10
|
export { default as Button } from './Button.svelte';
|
|
11
11
|
export type { ButtonVariant, ButtonSize } from './Button.svelte';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sprout — Warm and fully rounded — leaf green on
|
|
2
|
+
* Sprout — Warm and fully rounded — leaf green on white, pill buttons, and a round display face. For school, community and family sites.
|
|
3
3
|
*
|
|
4
4
|
* Generated by `bun run index`. Behaviour comes from
|
|
5
5
|
* `@nqmcreative/ui/core`, re-exported below so this one import covers
|
|
6
6
|
* the whole style.
|
|
7
7
|
*/
|
|
8
8
|
/* --- shared behaviour --- */
|
|
9
|
-
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText, anchored, clickOutside, focusTrap, focusable, navigateList, portal, toast, applyTheme, storedTheme, toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from '../../core/index.js';
|
|
9
|
+
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, focusWithinRing, toneFocusWithinRing, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText, anchored, clickOutside, focusTrap, focusable, navigateList, portal, toast, applyTheme, storedTheme, toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from '../../core/index.js';
|
|
10
10
|
/* --- actions --- */
|
|
11
11
|
export { default as Button } from './Button.svelte';
|
|
12
12
|
export { default as Link } from './Link.svelte';
|
|
@@ -36,5 +36,19 @@ export declare const float = "shadow-[0_6px_0_0_rgba(20,83,45,0.09),0_28px_60px_
|
|
|
36
36
|
* reads as one pressed object.
|
|
37
37
|
*/
|
|
38
38
|
export declare const lift: Record<Tone, string>;
|
|
39
|
+
/**
|
|
40
|
+
* The quiet lip, for a button that is not a solid fill: `soft` and `outline`.
|
|
41
|
+
*
|
|
42
|
+
* Drawn in the tone's own border colour — the line `outline` already carries —
|
|
43
|
+
* so the bottom reads as that border thickening rather than as a shadow stuck
|
|
44
|
+
* underneath. `soft` has no border of its own, so pairing the two here is what
|
|
45
|
+
* gives both variants the same edge: the only difference left between them is
|
|
46
|
+
* the face above it.
|
|
47
|
+
*
|
|
48
|
+
* Shortens by the same 3px the button travels, exactly like `lift`. Without
|
|
49
|
+
* that second step a pressed button just slides down with its shadow attached
|
|
50
|
+
* and never reads as pressed at all.
|
|
51
|
+
*/
|
|
52
|
+
export declare const liftSoft: Record<Tone, string>;
|
|
39
53
|
/** The same lip at 3px, for things too small to travel: icon tiles, dots. */
|
|
40
54
|
export declare const liftSm: Record<Tone, string>;
|
|
@@ -23,6 +23,29 @@ export const lift = {
|
|
|
23
23
|
danger: 'shadow-[0_5px_0_0_color-mix(in_oklab,var(--color-danger)_72%,black)] active:shadow-[0_2px_0_0_color-mix(in_oklab,var(--color-danger)_72%,black)]',
|
|
24
24
|
neutral: 'shadow-[0_5px_0_0_color-mix(in_oklab,var(--color-neutral)_72%,black)] active:shadow-[0_2px_0_0_color-mix(in_oklab,var(--color-neutral)_72%,black)]'
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* The quiet lip, for a button that is not a solid fill: `soft` and `outline`.
|
|
28
|
+
*
|
|
29
|
+
* Drawn in the tone's own border colour — the line `outline` already carries —
|
|
30
|
+
* so the bottom reads as that border thickening rather than as a shadow stuck
|
|
31
|
+
* underneath. `soft` has no border of its own, so pairing the two here is what
|
|
32
|
+
* gives both variants the same edge: the only difference left between them is
|
|
33
|
+
* the face above it.
|
|
34
|
+
*
|
|
35
|
+
* Shortens by the same 3px the button travels, exactly like `lift`. Without
|
|
36
|
+
* that second step a pressed button just slides down with its shadow attached
|
|
37
|
+
* and never reads as pressed at all.
|
|
38
|
+
*/
|
|
39
|
+
export const liftSoft = {
|
|
40
|
+
brand: 'shadow-[0_5px_0_0_var(--color-brand-border)] active:shadow-[0_2px_0_0_var(--color-brand-border)]',
|
|
41
|
+
accent: 'shadow-[0_5px_0_0_var(--color-accent-border)] active:shadow-[0_2px_0_0_var(--color-accent-border)]',
|
|
42
|
+
violet: 'shadow-[0_5px_0_0_var(--color-violet-border)] active:shadow-[0_2px_0_0_var(--color-violet-border)]',
|
|
43
|
+
info: 'shadow-[0_5px_0_0_var(--color-info-border)] active:shadow-[0_2px_0_0_var(--color-info-border)]',
|
|
44
|
+
success: 'shadow-[0_5px_0_0_var(--color-success-border)] active:shadow-[0_2px_0_0_var(--color-success-border)]',
|
|
45
|
+
warning: 'shadow-[0_5px_0_0_var(--color-warning-border)] active:shadow-[0_2px_0_0_var(--color-warning-border)]',
|
|
46
|
+
danger: 'shadow-[0_5px_0_0_var(--color-danger-border)] active:shadow-[0_2px_0_0_var(--color-danger-border)]',
|
|
47
|
+
neutral: 'shadow-[0_5px_0_0_var(--color-neutral-border)] active:shadow-[0_2px_0_0_var(--color-neutral-border)]'
|
|
48
|
+
};
|
|
26
49
|
/** The same lip at 3px, for things too small to travel: icon tiles, dots. */
|
|
27
50
|
export const liftSm = {
|
|
28
51
|
brand: 'shadow-[0_3px_0_0_color-mix(in_oklab,var(--color-brand)_72%,black)]',
|