@nqmcreative/ui 0.4.2 → 0.4.4
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/cli/index.mjs +5 -3
- package/dist/styles/matte/Button.svelte +4 -1
- package/dist/styles/matte/Combobox.svelte +1 -1
- package/dist/styles/matte/CommandPalette.svelte +1 -1
- package/dist/styles/matte/ContextMenu.svelte +1 -1
- package/dist/styles/matte/DatePicker.svelte +1 -1
- package/dist/styles/matte/Dropdown.svelte +1 -1
- package/dist/styles/matte/Modal.svelte +1 -1
- package/dist/styles/matte/MultiSelect.svelte +1 -1
- package/dist/styles/matte/Popover.svelte +1 -1
- package/dist/styles/matte/theme.css +43 -1
- package/dist/styles/paper/AccordionItem.svelte +38 -11
- package/dist/styles/paper/AccordionItem.svelte.d.ts +1 -1
- package/dist/styles/paper/Button.svelte +4 -1
- package/dist/styles/paper/Combobox.svelte +1 -1
- package/dist/styles/paper/CommandPalette.svelte +1 -1
- package/dist/styles/paper/ContextMenu.svelte +1 -1
- package/dist/styles/paper/DatePicker.svelte +1 -1
- package/dist/styles/paper/Dropdown.svelte +1 -1
- package/dist/styles/paper/Modal.svelte +1 -1
- package/dist/styles/paper/MultiSelect.svelte +1 -1
- package/dist/styles/paper/Popover.svelte +1 -1
- package/dist/styles/paper/theme.css +64 -13
- package/dist/styles/sprout/theme.css +18 -0
- package/package.json +1 -1
package/cli/index.mjs
CHANGED
|
@@ -389,9 +389,11 @@ async function init() {
|
|
|
389
389
|
`@import '@nqmcreative/ui/${style.name}/theme.css';`,
|
|
390
390
|
...(style.fonts ? [`@import '@nqmcreative/ui/${style.name}/fonts.css';`] : []),
|
|
391
391
|
``,
|
|
392
|
-
`/* Tailwind v4 skips node_modules — point it at this style's folder
|
|
393
|
-
` the class names used inside
|
|
394
|
-
|
|
392
|
+
`/* Tailwind v4 skips node_modules — point it at this style's folder and`,
|
|
393
|
+
` the shared core, so the class names used inside the components (tone`,
|
|
394
|
+
` colours and the like live in core) are generated. */`,
|
|
395
|
+
`@source '${source}/styles/${style.name}';`,
|
|
396
|
+
`@source '${source}/core';`
|
|
395
397
|
];
|
|
396
398
|
|
|
397
399
|
const css = await readOr(project.css);
|
|
@@ -63,7 +63,10 @@
|
|
|
63
63
|
}: Props = $props();
|
|
64
64
|
|
|
65
65
|
const base =
|
|
66
|
-
|
|
66
|
+
// The press: matte can't sink or scale, so a click reads as the whole
|
|
67
|
+
// button dimming a notch (brightness, not a shadow or a transform).
|
|
68
|
+
// Snaps in at 75ms, settles back over the base 150ms on release.
|
|
69
|
+
'inline-flex items-center justify-center gap-2 border font-sans font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,gap,filter] duration-150 ease-brand-out active:brightness-90 active:duration-75 disabled:opacity-50 disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:pointer-events-none';
|
|
67
70
|
|
|
68
71
|
const sizes: Record<ButtonSize, string> = {
|
|
69
72
|
sm: 'h-8 px-3.5 text-[13px]',
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
use:clickOutside={{ onoutside: () => (open = false), ignore: [wrapper] }}
|
|
215
215
|
id="{id ?? 'combobox'}-listbox"
|
|
216
216
|
role="listbox"
|
|
217
|
-
class="z-50 max-h-64 overflow-y-auto border border-hairline bg-bg py-1 font-sans"
|
|
217
|
+
class="z-50 max-h-64 animate-reveal overflow-y-auto border border-hairline bg-bg py-1 font-sans"
|
|
218
218
|
>
|
|
219
219
|
{#if flat.length === 0}
|
|
220
220
|
<p class="px-3 py-6 text-center text-sm text-text-muted">
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
onclick={(event) => {
|
|
161
161
|
if (event.target === dialog) open = false;
|
|
162
162
|
}}
|
|
163
|
-
class="mx-auto mt-[12vh] mb-auto w-[min(36rem,calc(100vw-2rem))] max-w-none border border-hairline bg-bg p-0 text-text backdrop:bg-text/40 {className}"
|
|
163
|
+
class="mx-auto mt-[12vh] mb-auto w-[min(36rem,calc(100vw-2rem))] max-w-none animate-enter border border-hairline bg-bg p-0 text-text backdrop:bg-text/40 {className}"
|
|
164
164
|
>
|
|
165
165
|
<div use:focusTrap={{ autofocus: false }} onkeydown={onKeydown} role="none" class="flex flex-col">
|
|
166
166
|
<div class="flex items-center gap-3 border-b border-hairline px-3.5">
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
role="menu"
|
|
79
79
|
aria-label={label ?? 'Menu'}
|
|
80
80
|
tabindex="-1"
|
|
81
|
-
class="z-50 flex max-h-[min(24rem,80vh)] min-w-44 flex-col overflow-y-auto border border-hairline bg-bg py-1 font-sans {menuClass}"
|
|
81
|
+
class="z-50 flex max-h-[min(24rem,80vh)] min-w-44 animate-reveal flex-col overflow-y-auto border border-hairline bg-bg py-1 font-sans {menuClass}"
|
|
82
82
|
>
|
|
83
83
|
{@render menu()}
|
|
84
84
|
</div>
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
use:anchored={{ anchor: wrapper, placement: 'bottom-start', offset: 4 }}
|
|
170
170
|
use:clickOutside={{ onoutside: () => (open = false), ignore: [wrapper] }}
|
|
171
171
|
id="{id ?? 'datepicker'}-calendar"
|
|
172
|
-
class="z-50 border border-hairline bg-bg"
|
|
172
|
+
class="z-50 animate-reveal border border-hairline bg-bg"
|
|
173
173
|
>
|
|
174
174
|
<Calendar
|
|
175
175
|
bind:value
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
role="menu"
|
|
85
85
|
aria-label={label ?? 'Menu'}
|
|
86
86
|
tabindex="-1"
|
|
87
|
-
class="z-50 flex max-h-[min(24rem,80vh)] min-w-44 flex-col overflow-y-auto border border-hairline bg-bg py-1 font-sans {className}"
|
|
87
|
+
class="z-50 flex max-h-[min(24rem,80vh)] min-w-44 animate-reveal flex-col overflow-y-auto border border-hairline bg-bg py-1 font-sans {className}"
|
|
88
88
|
>
|
|
89
89
|
{@render children()}
|
|
90
90
|
</div>
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
oncancel={(event) => {
|
|
74
74
|
if (!dismissible) event.preventDefault();
|
|
75
75
|
}}
|
|
76
|
-
class="m-auto w-[calc(100%-2rem)] border border-hairline bg-bg p-0 text-text backdrop:bg-text/40 {sizes[
|
|
76
|
+
class="m-auto w-[calc(100%-2rem)] animate-enter border border-hairline bg-bg p-0 text-text backdrop:bg-text/40 {sizes[
|
|
77
77
|
size
|
|
78
78
|
]} {className}"
|
|
79
79
|
{...rest}
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
id="{id ?? 'multiselect'}-listbox"
|
|
223
223
|
role="listbox"
|
|
224
224
|
aria-multiselectable="true"
|
|
225
|
-
class="z-50 max-h-64 overflow-y-auto border border-hairline bg-bg py-1 font-sans"
|
|
225
|
+
class="z-50 max-h-64 animate-reveal overflow-y-auto border border-hairline bg-bg py-1 font-sans"
|
|
226
226
|
>
|
|
227
227
|
{#if flat.length === 0}
|
|
228
228
|
<p class="px-3 py-6 text-center text-sm text-text-muted">
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
role="dialog"
|
|
78
78
|
aria-label={title}
|
|
79
79
|
tabindex="-1"
|
|
80
|
-
class="z-50 flex max-w-xs flex-col gap-2 border border-hairline bg-bg p-4 font-sans {className}"
|
|
80
|
+
class="z-50 flex max-w-xs animate-reveal flex-col gap-2 border border-hairline bg-bg p-4 font-sans {className}"
|
|
81
81
|
>
|
|
82
82
|
{#if title}
|
|
83
83
|
<p class="font-heading text-sm font-medium text-text">{title}</p>
|
|
@@ -78,9 +78,37 @@
|
|
|
78
78
|
--font-heading: 'Sora', ui-sans-serif, system-ui, sans-serif;
|
|
79
79
|
--font-mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
80
80
|
|
|
81
|
-
/* --- motion ---
|
|
81
|
+
/* --- motion ---
|
|
82
|
+
* matte enters by fading and revealing along an axis — never by moving or
|
|
83
|
+
* scaling (the style's first law). One token per overlay kind: `enter` for
|
|
84
|
+
* dialog panels + backdrops, `reveal` for anchored menus and popovers.
|
|
85
|
+
* paper and sprout define these same two names their own way, so an
|
|
86
|
+
* overlay's markup never changes between styles — only these values do.
|
|
87
|
+
*/
|
|
82
88
|
--ease-brand: cubic-bezier(0.16, 1, 0.3, 1);
|
|
83
89
|
--ease-brand-out: cubic-bezier(0.25, 1, 0.5, 1);
|
|
90
|
+
--animate-enter: nqm-fade 150ms var(--ease-brand-out);
|
|
91
|
+
--animate-reveal: nqm-shutter 160ms var(--ease-brand-out);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Enter-only, matching the Drawer: the native <dialog> and the {#if}-mounted
|
|
95
|
+
* popover each play these once on mount and leave instantly. */
|
|
96
|
+
@keyframes nqm-fade {
|
|
97
|
+
from {
|
|
98
|
+
opacity: 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
@keyframes nqm-shutter {
|
|
102
|
+
from {
|
|
103
|
+
opacity: 0;
|
|
104
|
+
clip-path: inset(0 0 100% 0);
|
|
105
|
+
}
|
|
106
|
+
to {
|
|
107
|
+
opacity: 1;
|
|
108
|
+
/* Both ends must be inset() — animating to `none` (the default) can't
|
|
109
|
+
* interpolate and the panel would snap open at the midpoint instead. */
|
|
110
|
+
clip-path: inset(0 0 0 0);
|
|
111
|
+
}
|
|
84
112
|
}
|
|
85
113
|
|
|
86
114
|
/*
|
|
@@ -218,6 +246,20 @@
|
|
|
218
246
|
* a wash — the text underneath keeps its own colour and its own contrast.
|
|
219
247
|
*/
|
|
220
248
|
@layer base {
|
|
249
|
+
/* The one overlay surface a class can't reach: every <dialog> backdrop
|
|
250
|
+
* fades with its panel. */
|
|
251
|
+
dialog::backdrop {
|
|
252
|
+
animation: nqm-fade 150ms var(--ease-brand-out);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@media (prefers-reduced-motion: reduce) {
|
|
256
|
+
.animate-enter,
|
|
257
|
+
.animate-reveal,
|
|
258
|
+
dialog::backdrop {
|
|
259
|
+
animation: none;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
221
263
|
::selection {
|
|
222
264
|
background-color: color-mix(in oklab, var(--color-brand) 24%, transparent);
|
|
223
265
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { toneText, type Tone } from '../../core/tones.js';
|
|
5
5
|
import { iconMd } from './icon.js';
|
|
6
6
|
|
|
7
|
-
interface Props extends HTMLAttributes<
|
|
7
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
8
8
|
title: string;
|
|
9
9
|
open?: boolean;
|
|
10
10
|
tone?: Tone;
|
|
@@ -25,27 +25,54 @@
|
|
|
25
25
|
children,
|
|
26
26
|
...rest
|
|
27
27
|
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const uid = $props.id();
|
|
28
30
|
</script>
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
32
|
+
<!--
|
|
33
|
+
A disclosure, not a native <details>: the panel is a grid whose single row
|
|
34
|
+
animates from 0fr to 1fr, which is the one height animation that works in
|
|
35
|
+
every browser (auto-height keyframes don't interpolate). The <button> keeps
|
|
36
|
+
the keyboard and focus behaviour <summary> gave us for free; aria-expanded
|
|
37
|
+
and the region wiring replace what the open attribute carried.
|
|
38
|
+
-->
|
|
39
|
+
<div class="border-b border-hairline last:border-b-0 {className}" {...rest}>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
id="{uid}-trigger"
|
|
43
|
+
aria-expanded={open}
|
|
44
|
+
aria-controls="{uid}-panel"
|
|
45
|
+
onclick={() => (open = !open)}
|
|
46
|
+
class="flex w-full cursor-pointer items-center gap-4 px-4 py-3.5 text-left font-sans text-sm font-medium text-text transition-colors duration-150 ease-brand-out select-none hover:bg-bg-alt focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-current"
|
|
33
47
|
>
|
|
34
48
|
{#if icon}<span class={iconMd}>{@render icon()}</span>{/if}
|
|
35
49
|
<span class="flex-1">{title}</span>
|
|
36
50
|
{#if meta}<span class="text-[13px] font-normal text-text-muted">{meta}</span>{/if}
|
|
37
51
|
<svg
|
|
38
|
-
class="size-4 shrink-0 transition-transform duration-200 ease-brand-out
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
class="size-4 shrink-0 transition-transform duration-200 ease-brand-out {open
|
|
53
|
+
? 'rotate-180'
|
|
54
|
+
: ''} {toneText[tone]}"
|
|
41
55
|
viewBox="0 0 16 16"
|
|
42
56
|
fill="none"
|
|
43
57
|
aria-hidden="true"
|
|
44
58
|
>
|
|
45
59
|
<path d="m4 6 4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
46
60
|
</svg>
|
|
47
|
-
</
|
|
48
|
-
<div
|
|
49
|
-
{
|
|
61
|
+
</button>
|
|
62
|
+
<div
|
|
63
|
+
id="{uid}-panel"
|
|
64
|
+
role="region"
|
|
65
|
+
aria-labelledby="{uid}-trigger"
|
|
66
|
+
inert={!open}
|
|
67
|
+
class="grid transition-[grid-template-rows] duration-200 ease-brand-out"
|
|
68
|
+
style:grid-template-rows={open ? '1fr' : '0fr'}
|
|
69
|
+
>
|
|
70
|
+
<!-- overflow-hidden gives this grid row a min height of 0 so 0fr can
|
|
71
|
+
collapse it fully, and clips the content while it slides. -->
|
|
72
|
+
<div class="overflow-hidden">
|
|
73
|
+
<div class="px-4 pb-4 font-sans text-sm leading-relaxed text-text-secondary">
|
|
74
|
+
{@render children()}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
50
77
|
</div>
|
|
51
|
-
</
|
|
78
|
+
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
import { type Tone } from '../../core/tones.js';
|
|
4
|
-
interface Props extends HTMLAttributes<
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
title: string;
|
|
6
6
|
open?: boolean;
|
|
7
7
|
tone?: Tone;
|
|
@@ -63,7 +63,10 @@
|
|
|
63
63
|
}: Props = $props();
|
|
64
64
|
|
|
65
65
|
const base =
|
|
66
|
-
|
|
66
|
+
// The press: shadow is paper's language, so a click flattens the button
|
|
67
|
+
// onto the page (shadow drops) and dims it a notch. Snaps in at 75ms,
|
|
68
|
+
// settles back over the base 150ms on release.
|
|
69
|
+
'inline-flex items-center justify-center gap-2 rounded-md border font-sans font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,filter] duration-150 ease-brand-out active:shadow-none active:brightness-95 active:duration-75 disabled:opacity-50 disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:pointer-events-none';
|
|
67
70
|
|
|
68
71
|
const sizes: Record<ButtonSize, string> = {
|
|
69
72
|
sm: 'h-8 px-3 text-[13px]',
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
use:clickOutside={{ onoutside: () => (open = false), ignore: [wrapper] }}
|
|
220
220
|
id="{id ?? 'combobox'}-listbox"
|
|
221
221
|
role="listbox"
|
|
222
|
-
class="z-50 max-h-64 overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg"
|
|
222
|
+
class="z-50 max-h-64 animate-reveal overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg"
|
|
223
223
|
>
|
|
224
224
|
{#if flat.length === 0}
|
|
225
225
|
<p class="px-3 py-6 text-center text-sm text-text-muted">
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
onclick={(event) => {
|
|
161
161
|
if (event.target === dialog) open = false;
|
|
162
162
|
}}
|
|
163
|
-
class="mx-auto mt-[12vh] mb-auto w-[min(36rem,calc(100vw-2rem))] max-w-none rounded-xl border border-hairline bg-bg p-0 text-text shadow-2xl backdrop:bg-text/30 backdrop:backdrop-blur-[1px] {className}"
|
|
163
|
+
class="mx-auto mt-[12vh] mb-auto w-[min(36rem,calc(100vw-2rem))] max-w-none animate-enter rounded-xl border border-hairline bg-bg p-0 text-text shadow-2xl backdrop:bg-text/30 backdrop:backdrop-blur-[1px] {className}"
|
|
164
164
|
>
|
|
165
165
|
<div use:focusTrap={{ autofocus: false }} onkeydown={onKeydown} role="none" class="flex flex-col">
|
|
166
166
|
<div class="flex items-center gap-3 border-b border-hairline px-4">
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
role="menu"
|
|
79
79
|
aria-label={label ?? 'Menu'}
|
|
80
80
|
tabindex="-1"
|
|
81
|
-
class="z-50 flex max-h-[min(24rem,80vh)] min-w-48 flex-col overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg {menuClass}"
|
|
81
|
+
class="z-50 flex max-h-[min(24rem,80vh)] min-w-48 animate-reveal flex-col overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg {menuClass}"
|
|
82
82
|
>
|
|
83
83
|
{@render menu()}
|
|
84
84
|
</div>
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
use:anchored={{ anchor: wrapper, placement: 'bottom-start', offset: 4 }}
|
|
180
180
|
use:clickOutside={{ onoutside: () => (open = false), ignore: [wrapper] }}
|
|
181
181
|
id="{id ?? 'datepicker'}-calendar"
|
|
182
|
-
class="z-50 rounded-lg border border-hairline bg-bg shadow-lg"
|
|
182
|
+
class="z-50 animate-reveal rounded-lg border border-hairline bg-bg shadow-lg"
|
|
183
183
|
>
|
|
184
184
|
<Calendar
|
|
185
185
|
bind:value
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
role="menu"
|
|
85
85
|
aria-label={label ?? 'Menu'}
|
|
86
86
|
tabindex="-1"
|
|
87
|
-
class="z-50 flex max-h-[min(24rem,80vh)] min-w-48 flex-col overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg {className}"
|
|
87
|
+
class="z-50 flex max-h-[min(24rem,80vh)] min-w-48 animate-reveal flex-col overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg {className}"
|
|
88
88
|
>
|
|
89
89
|
{@render children()}
|
|
90
90
|
</div>
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
oncancel={(event) => {
|
|
74
74
|
if (!dismissible) event.preventDefault();
|
|
75
75
|
}}
|
|
76
|
-
class="m-auto w-[calc(100%-2rem)] rounded-xl border border-hairline bg-bg p-0 text-text shadow-2xl backdrop:bg-text/30 backdrop:backdrop-blur-[1px] {sizes[
|
|
76
|
+
class="m-auto w-[calc(100%-2rem)] animate-enter rounded-xl border border-hairline bg-bg p-0 text-text shadow-2xl backdrop:bg-text/30 backdrop:backdrop-blur-[1px] {sizes[
|
|
77
77
|
size
|
|
78
78
|
]} {className}"
|
|
79
79
|
{...rest}
|
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
id="{id ?? 'multiselect'}-listbox"
|
|
233
233
|
role="listbox"
|
|
234
234
|
aria-multiselectable="true"
|
|
235
|
-
class="z-50 max-h-64 overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg"
|
|
235
|
+
class="z-50 max-h-64 animate-reveal overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg"
|
|
236
236
|
>
|
|
237
237
|
{#if flat.length === 0}
|
|
238
238
|
<p class="px-3 py-6 text-center text-sm text-text-muted">
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
role="dialog"
|
|
78
78
|
aria-label={title}
|
|
79
79
|
tabindex="-1"
|
|
80
|
-
class="z-50 flex max-w-xs flex-col gap-2 rounded-lg border border-hairline bg-bg p-4 font-sans shadow-lg {className}"
|
|
80
|
+
class="z-50 flex max-w-xs animate-reveal flex-col gap-2 rounded-lg border border-hairline bg-bg p-4 font-sans shadow-lg {className}"
|
|
81
81
|
>
|
|
82
82
|
{#if title}
|
|
83
83
|
<p class="font-heading text-sm font-semibold text-text">{title}</p>
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
--color-text-inverse: #ffffff;
|
|
30
30
|
|
|
31
31
|
/* --- brand / primary (indigo) --- */
|
|
32
|
-
--color-brand: #
|
|
33
|
-
--color-brand-hover: #
|
|
34
|
-
--color-brand-light: #
|
|
35
|
-
--color-brand-border: #
|
|
32
|
+
--color-brand: #15803d;
|
|
33
|
+
--color-brand-hover: #166534;
|
|
34
|
+
--color-brand-light: #e8f6ee;
|
|
35
|
+
--color-brand-border: #a7d9b9;
|
|
36
36
|
|
|
37
37
|
/* --- accent (rose) --- */
|
|
38
38
|
--color-accent: #e11d48;
|
|
@@ -95,9 +95,46 @@
|
|
|
95
95
|
'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
96
96
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
|
|
97
97
|
|
|
98
|
-
/* --- motion ---
|
|
98
|
+
/* --- motion ---
|
|
99
|
+
* paper enters by rising and fading — depth is its language, so overlays
|
|
100
|
+
* settle onto the page rather than snapping or scaling in. One token per
|
|
101
|
+
* overlay kind: `enter` for dialog panels + backdrops, `reveal` for anchored
|
|
102
|
+
* menus and popovers. matte and sprout define these same two names their own
|
|
103
|
+
* way, so an overlay's markup never changes between styles — only the values.
|
|
104
|
+
*/
|
|
99
105
|
--ease-brand: cubic-bezier(0.4, 0, 0.2, 1);
|
|
100
106
|
--ease-brand-out: cubic-bezier(0, 0, 0.2, 1);
|
|
107
|
+
--animate-enter: nqm-rise 200ms var(--ease-brand-out);
|
|
108
|
+
--animate-reveal: nqm-drop 180ms var(--ease-brand-out);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Enter-only, matching the Drawer: the native <dialog> and the {#if}-mounted
|
|
112
|
+
* popover each play these once on mount and leave instantly. */
|
|
113
|
+
@keyframes nqm-fade {
|
|
114
|
+
from {
|
|
115
|
+
opacity: 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
@keyframes nqm-rise {
|
|
119
|
+
from {
|
|
120
|
+
opacity: 0;
|
|
121
|
+
/* dialogs rise and grow a hair — a gentle zoom onto the page */
|
|
122
|
+
transform: translateY(8px) scale(0.98);
|
|
123
|
+
/* one layer, matching the panel's shadow-2xl, so box-shadow interpolates
|
|
124
|
+
* to the resting shadow instead of snapping (a mismatched layer count is
|
|
125
|
+
* discrete). The shadow deepens as the panel settles. */
|
|
126
|
+
box-shadow: 0 8px 20px -12px rgb(0 0 0 / 0.18);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
@keyframes nqm-drop {
|
|
130
|
+
from {
|
|
131
|
+
opacity: 0;
|
|
132
|
+
transform: translateY(-6px);
|
|
133
|
+
/* two layers, matching the menus' shadow-lg — same reason as above */
|
|
134
|
+
box-shadow:
|
|
135
|
+
0 2px 6px -3px rgb(0 0 0 / 0.08),
|
|
136
|
+
0 1px 3px -2px rgb(0 0 0 / 0.06);
|
|
137
|
+
}
|
|
101
138
|
}
|
|
102
139
|
|
|
103
140
|
/*
|
|
@@ -126,10 +163,10 @@
|
|
|
126
163
|
--color-text-muted: #9ca3af;
|
|
127
164
|
--color-text-inverse: #0b0f19;
|
|
128
165
|
|
|
129
|
-
--color-brand: #
|
|
130
|
-
--color-brand-hover: #
|
|
131
|
-
--color-brand-light: #
|
|
132
|
-
--color-brand-border: #
|
|
166
|
+
--color-brand: #46a06a;
|
|
167
|
+
--color-brand-hover: #5cb47e;
|
|
168
|
+
--color-brand-light: #12281b;
|
|
169
|
+
--color-brand-border: #255034;
|
|
133
170
|
|
|
134
171
|
--color-accent: #fb7185;
|
|
135
172
|
--color-accent-hover: #fda4af;
|
|
@@ -182,10 +219,10 @@
|
|
|
182
219
|
--color-text-muted: #9ca3af;
|
|
183
220
|
--color-text-inverse: #0b0f19;
|
|
184
221
|
|
|
185
|
-
--color-brand: #
|
|
186
|
-
--color-brand-hover: #
|
|
187
|
-
--color-brand-light: #
|
|
188
|
-
--color-brand-border: #
|
|
222
|
+
--color-brand: #46a06a;
|
|
223
|
+
--color-brand-hover: #5cb47e;
|
|
224
|
+
--color-brand-light: #12281b;
|
|
225
|
+
--color-brand-border: #255034;
|
|
189
226
|
|
|
190
227
|
--color-accent: #fb7185;
|
|
191
228
|
--color-accent-hover: #fda4af;
|
|
@@ -234,6 +271,20 @@
|
|
|
234
271
|
* a wash — the text underneath keeps its own colour and its own contrast.
|
|
235
272
|
*/
|
|
236
273
|
@layer base {
|
|
274
|
+
/* The one overlay surface a class can't reach: every <dialog> backdrop
|
|
275
|
+
* fades with its panel. */
|
|
276
|
+
dialog::backdrop {
|
|
277
|
+
animation: nqm-fade 180ms var(--ease-brand-out);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@media (prefers-reduced-motion: reduce) {
|
|
281
|
+
.animate-enter,
|
|
282
|
+
.animate-reveal,
|
|
283
|
+
dialog::backdrop {
|
|
284
|
+
animation: none;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
237
288
|
::selection {
|
|
238
289
|
background-color: color-mix(in oklab, var(--color-brand) 24%, transparent);
|
|
239
290
|
}
|
|
@@ -129,6 +129,24 @@
|
|
|
129
129
|
*/
|
|
130
130
|
--ease-brand: cubic-bezier(0.34, 1.4, 0.64, 1);
|
|
131
131
|
--ease-brand-out: cubic-bezier(0.22, 1, 0.36, 1);
|
|
132
|
+
/* Overlay entrances — sprout pops in with a little overshoot, on the same
|
|
133
|
+
* `enter`/`reveal` token names matte and paper use. Declared here so the
|
|
134
|
+
* three styles stay in token-name parity; sprout's overlay components adopt
|
|
135
|
+
* them in a later pass. */
|
|
136
|
+
--animate-enter: nqm-pop 240ms var(--ease-brand);
|
|
137
|
+
--animate-reveal: nqm-pop 220ms var(--ease-brand);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@keyframes nqm-fade {
|
|
141
|
+
from {
|
|
142
|
+
opacity: 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
@keyframes nqm-pop {
|
|
146
|
+
from {
|
|
147
|
+
opacity: 0;
|
|
148
|
+
transform: scale(0.96);
|
|
149
|
+
}
|
|
132
150
|
}
|
|
133
151
|
|
|
134
152
|
/*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nqmcreative/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Svelte 5 + Tailwind CSS v4 component library — three styles (matte, paper, sprout), 70 components each, one shared behaviour core. No runtime dependencies.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|