@nqmcreative/ui 0.4.1 → 0.4.2

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 (120) hide show
  1. package/README.md +1 -1
  2. package/dist/core/index.d.ts +1 -1
  3. package/dist/core/index.js +1 -1
  4. package/dist/core/tones.d.ts +10 -0
  5. package/dist/core/tones.js +19 -0
  6. package/dist/core/trigger.d.ts +8 -0
  7. package/dist/core/trigger.js +11 -0
  8. package/dist/styles/matte/Alert.svelte +1 -1
  9. package/dist/styles/matte/Avatar.svelte +16 -3
  10. package/dist/styles/matte/Button.svelte +6 -2
  11. package/dist/styles/matte/Checkbox.svelte +49 -30
  12. package/dist/styles/matte/CheckboxGroup.svelte +15 -4
  13. package/dist/styles/matte/Combobox.svelte +11 -2
  14. package/dist/styles/matte/ConfirmDialog.svelte +10 -2
  15. package/dist/styles/matte/DatePicker.svelte +9 -2
  16. package/dist/styles/matte/Divider.svelte +1 -1
  17. package/dist/styles/matte/Drawer.svelte +10 -2
  18. package/dist/styles/matte/Field.svelte +59 -3
  19. package/dist/styles/matte/Footer.svelte +14 -3
  20. package/dist/styles/matte/Input.svelte +9 -2
  21. package/dist/styles/matte/Label.svelte +3 -1
  22. package/dist/styles/matte/Link.svelte +13 -2
  23. package/dist/styles/matte/MenuItem.svelte +11 -4
  24. package/dist/styles/matte/Modal.svelte +10 -2
  25. package/dist/styles/matte/Navbar.svelte +2 -2
  26. package/dist/styles/matte/Radio.svelte +41 -22
  27. package/dist/styles/matte/RadioGroup.svelte +15 -4
  28. package/dist/styles/matte/Select.svelte +4 -2
  29. package/dist/styles/matte/Skeleton.svelte +2 -1
  30. package/dist/styles/matte/Stat.svelte +27 -3
  31. package/dist/styles/matte/Switch.svelte +6 -1
  32. package/dist/styles/matte/Tabs.svelte +1 -1
  33. package/dist/styles/matte/Textarea.svelte +4 -2
  34. package/dist/styles/matte/Toaster.svelte +21 -3
  35. package/dist/styles/matte/Tooltip.svelte +8 -1
  36. package/dist/styles/matte/index.d.ts +1 -1
  37. package/dist/styles/matte/index.js +1 -1
  38. package/dist/styles/matte/theme.css +21 -0
  39. package/dist/styles/paper/Alert.svelte +2 -2
  40. package/dist/styles/paper/Avatar.svelte +14 -3
  41. package/dist/styles/paper/Button.svelte +5 -1
  42. package/dist/styles/paper/Card.svelte +1 -1
  43. package/dist/styles/paper/Checkbox.svelte +55 -36
  44. package/dist/styles/paper/CheckboxGroup.svelte +15 -4
  45. package/dist/styles/paper/Combobox.svelte +11 -2
  46. package/dist/styles/paper/ConfirmDialog.svelte +8 -2
  47. package/dist/styles/paper/DatePicker.svelte +9 -2
  48. package/dist/styles/paper/Divider.svelte +1 -1
  49. package/dist/styles/paper/Drawer.svelte +8 -2
  50. package/dist/styles/paper/Field.svelte +59 -3
  51. package/dist/styles/paper/Footer.svelte +14 -3
  52. package/dist/styles/paper/Input.svelte +9 -2
  53. package/dist/styles/paper/Label.svelte +3 -1
  54. package/dist/styles/paper/Link.svelte +13 -2
  55. package/dist/styles/paper/MenuItem.svelte +11 -4
  56. package/dist/styles/paper/Modal.svelte +8 -2
  57. package/dist/styles/paper/Navbar.svelte +2 -2
  58. package/dist/styles/paper/Radio.svelte +41 -22
  59. package/dist/styles/paper/RadioGroup.svelte +15 -4
  60. package/dist/styles/paper/SegmentedControl.svelte +2 -2
  61. package/dist/styles/paper/Select.svelte +4 -2
  62. package/dist/styles/paper/Skeleton.svelte +2 -1
  63. package/dist/styles/paper/Stat.svelte +24 -2
  64. package/dist/styles/paper/Switch.svelte +6 -1
  65. package/dist/styles/paper/Tabs.svelte +1 -1
  66. package/dist/styles/paper/Textarea.svelte +4 -2
  67. package/dist/styles/paper/Toaster.svelte +19 -3
  68. package/dist/styles/paper/Tooltip.svelte +8 -1
  69. package/dist/styles/paper/index.d.ts +1 -1
  70. package/dist/styles/paper/index.js +1 -1
  71. package/dist/styles/paper/theme.css +21 -0
  72. package/dist/styles/sprout/Alert.svelte +3 -3
  73. package/dist/styles/sprout/Avatar.svelte +14 -3
  74. package/dist/styles/sprout/Breadcrumb.svelte +1 -1
  75. package/dist/styles/sprout/Button.svelte +8 -4
  76. package/dist/styles/sprout/Calendar.svelte +1 -1
  77. package/dist/styles/sprout/Card.svelte +2 -2
  78. package/dist/styles/sprout/Checkbox.svelte +55 -36
  79. package/dist/styles/sprout/CheckboxGroup.svelte +15 -4
  80. package/dist/styles/sprout/ColorInput.svelte +1 -1
  81. package/dist/styles/sprout/Combobox.svelte +12 -3
  82. package/dist/styles/sprout/ConfirmDialog.svelte +8 -2
  83. package/dist/styles/sprout/DatePicker.svelte +11 -4
  84. package/dist/styles/sprout/Divider.svelte +1 -1
  85. package/dist/styles/sprout/Drawer.svelte +8 -2
  86. package/dist/styles/sprout/Dropzone.svelte +1 -1
  87. package/dist/styles/sprout/Field.svelte +59 -3
  88. package/dist/styles/sprout/FileInput.svelte +3 -3
  89. package/dist/styles/sprout/Footer.svelte +14 -3
  90. package/dist/styles/sprout/Input.svelte +9 -2
  91. package/dist/styles/sprout/Label.svelte +3 -1
  92. package/dist/styles/sprout/Link.svelte +13 -2
  93. package/dist/styles/sprout/LogoCloud.svelte +1 -1
  94. package/dist/styles/sprout/MenuItem.svelte +11 -4
  95. package/dist/styles/sprout/Modal.svelte +8 -2
  96. package/dist/styles/sprout/MultiSelect.svelte +3 -3
  97. package/dist/styles/sprout/Navbar.svelte +2 -2
  98. package/dist/styles/sprout/Radio.svelte +41 -22
  99. package/dist/styles/sprout/RadioGroup.svelte +15 -4
  100. package/dist/styles/sprout/SearchInput.svelte +1 -1
  101. package/dist/styles/sprout/SegmentedControl.svelte +2 -2
  102. package/dist/styles/sprout/Select.svelte +4 -2
  103. package/dist/styles/sprout/Sidebar.svelte +2 -1
  104. package/dist/styles/sprout/Skeleton.svelte +2 -1
  105. package/dist/styles/sprout/Stat.svelte +24 -2
  106. package/dist/styles/sprout/Switch.svelte +6 -1
  107. package/dist/styles/sprout/Table.svelte +1 -1
  108. package/dist/styles/sprout/Tabs.svelte +1 -1
  109. package/dist/styles/sprout/TagsInput.svelte +1 -1
  110. package/dist/styles/sprout/Textarea.svelte +4 -2
  111. package/dist/styles/sprout/TimeInput.svelte +1 -1
  112. package/dist/styles/sprout/Toaster.svelte +22 -6
  113. package/dist/styles/sprout/Tooltip.svelte +8 -1
  114. package/dist/styles/sprout/index.d.ts +2 -2
  115. package/dist/styles/sprout/index.js +2 -2
  116. package/dist/styles/sprout/lift.d.ts +14 -0
  117. package/dist/styles/sprout/lift.js +23 -0
  118. package/dist/styles/sprout/theme.css +50 -24
  119. package/package.json +1 -1
  120. package/registry.json +4 -2
@@ -32,7 +32,15 @@
32
32
  }: Props = $props();
33
33
 
34
34
  const trendTone: Record<StatTrend, Tone> = { up: 'success', down: 'danger', flat: 'neutral' };
35
- const trendGlyph: Record<StatTrend, string> = { up: '↑', down: '↓', flat: '→' };
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
- {trendGlyph[trend]}
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">{description}</span>
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}
@@ -75,7 +75,7 @@
75
75
  }`;
76
76
  }
77
77
  return `${base} h-8 rounded-xl px-3.5 ${
78
- active ? `bg-bg text-text ${edge}` : 'text-text-muted hover:text-text'
78
+ active ? `bg-bg ${toneText[tone]} ${edge}` : 'text-text-muted hover:text-text'
79
79
  }`;
80
80
  }
81
81
  </script>
@@ -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="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 focus:outline-none disabled:pointer-events-none disabled:opacity-50
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
- fromCenter ? { y: fromTop ? -16 : 16, x: 0 } : { x: fromLeft ? -16 : 16, y: 0 }
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 cream card and lets the glyph
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 cream, pill buttons, and a round display face. For school, community and family sites.
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 cream, pill buttons, and a round display face. For school, community and family sites.
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)]',
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * sprout — design tokens.
3
3
  *
4
- * The warm one: leaf green on cream, fully rounded corners, and a display face
4
+ * The warm one: leaf green on white, fully rounded corners, and a display face
5
5
  * with round terminals. Built for school, community and family sites, where the
6
6
  * reader is a parent on a phone rather than an operator at a desk. Import this
7
7
  * after `@import "tailwindcss";` in your app's CSS entry point.
@@ -17,16 +17,21 @@
17
17
  */
18
18
  @theme {
19
19
  /*
20
- * Surfaces are cream rather than white, and the greys are green-cast. A
21
- * neutral grey next to this much foliage reads as a printing error, so every
22
- * step of the scale carries a little of the brand hue.
20
+ * Surfaces are white, and the greys under them are near-neutral with only a
21
+ * hair of warmth. The green belongs to the brand, not to the paper: a page
22
+ * whose every secondary surface and every hover is tinted reads as tiring
23
+ * long before it reads as warm, so the neutrals stay out of the way.
24
+ *
25
+ * `--color-bg` is the face of a card; `--color-bg-alt` is the ground it sits
26
+ * on. Put `bg-bg-alt` on the body and cards float as white, which is the
27
+ * layering this style was drawn from.
23
28
  */
24
29
  /* --- surfaces --- */
25
- --color-bg: #fffefb;
26
- --color-bg-alt: #f0fdf4;
27
- --color-bg-inset: #e3f6e9;
28
- --color-hairline: #ecefe7;
29
- --color-hairline-strong: #d3e0d1;
30
+ --color-bg: #ffffff;
31
+ --color-bg-alt: #f7f7f4;
32
+ --color-bg-inset: #edede8;
33
+ --color-hairline: #e6e6e0;
34
+ --color-hairline-strong: #cfcfc7;
30
35
 
31
36
  /* --- text --- */
32
37
  --color-text: #1a3a1f;
@@ -136,25 +141,25 @@
136
141
  * Every utility built on these tokens follows automatically, so components
137
142
  * need no `dark:` classes of their own.
138
143
  *
139
- * The dark palette is a night garden rather than a neutral slate: the surfaces
140
- * keep the green cast, and every hue lifts a step or two so it still reads
141
- * against them.
144
+ * The dark palette is lamplight rather than a neutral slate: the surfaces carry
145
+ * the same honey cast as the light ones, and every hue lifts a step or two so
146
+ * it still reads against them.
142
147
  */
143
148
  @custom-variant dark (&:where(.dark, .dark *));
144
149
 
145
150
  .dark {
146
151
  color-scheme: dark;
147
152
 
148
- --color-bg: #0e1a11;
149
- --color-bg-alt: #14251a;
150
- --color-bg-inset: #1b3020;
151
- --color-hairline: #1e3524;
152
- --color-hairline-strong: #2f4a36;
153
+ --color-bg: #17140e;
154
+ --color-bg-alt: #211c13;
155
+ --color-bg-inset: #2d261a;
156
+ --color-hairline: #2a2317;
157
+ --color-hairline-strong: #453a27;
153
158
 
154
159
  --color-text: #f0f7f0;
155
160
  --color-text-secondary: #cadfcd;
156
161
  --color-text-muted: #94ad99;
157
- --color-text-inverse: #0e1a11;
162
+ --color-text-inverse: #17140e;
158
163
 
159
164
  --color-brand: #4ade80;
160
165
  --color-brand-hover: #86efac;
@@ -201,16 +206,16 @@
201
206
  :root:not(.light) {
202
207
  color-scheme: dark;
203
208
 
204
- --color-bg: #0e1a11;
205
- --color-bg-alt: #14251a;
206
- --color-bg-inset: #1b3020;
207
- --color-hairline: #1e3524;
208
- --color-hairline-strong: #2f4a36;
209
+ --color-bg: #17140e;
210
+ --color-bg-alt: #211c13;
211
+ --color-bg-inset: #2d261a;
212
+ --color-hairline: #2a2317;
213
+ --color-hairline-strong: #453a27;
209
214
 
210
215
  --color-text: #f0f7f0;
211
216
  --color-text-secondary: #cadfcd;
212
217
  --color-text-muted: #94ad99;
213
- --color-text-inverse: #0e1a11;
218
+ --color-text-inverse: #17140e;
214
219
 
215
220
  --color-brand: #4ade80;
216
221
  --color-brand-hover: #86efac;
@@ -253,3 +258,24 @@
253
258
  --color-neutral-border: #2f4a36;
254
259
  }
255
260
  }
261
+
262
+ /*
263
+ * The surfaces the browser draws for us.
264
+ *
265
+ * A caret and a highlight in the user-agent's blue belong to no design system,
266
+ * and they are the two the reader touches most: one sits in every field they
267
+ * type into, the other appears the moment they drag across a paragraph. The
268
+ * selection is mixed from the brand rather than pinned to a token so it stays
269
+ * a wash — the text underneath keeps its own colour and its own contrast.
270
+ */
271
+ @layer base {
272
+ ::selection {
273
+ background-color: color-mix(in oklab, var(--color-brand) 24%, transparent);
274
+ }
275
+
276
+ input,
277
+ textarea,
278
+ [contenteditable] {
279
+ caret-color: var(--color-brand);
280
+ }
281
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nqmcreative/ui",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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",
package/registry.json CHANGED
@@ -21,7 +21,7 @@
21
21
  {
22
22
  "name": "sprout",
23
23
  "title": "Sprout",
24
- "description": "Warm and fully rounded — leaf green on cream, pill buttons, and a round display face. For school, community and family sites.",
24
+ "description": "Warm and fully rounded — leaf green on white, pill buttons, and a round display face. For school, community and family sites.",
25
25
  "fonts": true,
26
26
  "extra": []
27
27
  }
@@ -1122,7 +1122,9 @@
1122
1122
  "sprout": "@nqmcreative/ui/sprout/tooltip"
1123
1123
  },
1124
1124
  "uses": [],
1125
- "modules": []
1125
+ "modules": [
1126
+ "trigger"
1127
+ ]
1126
1128
  }
1127
1129
  ]
1128
1130
  }