@dxlbnl/ui 0.1.0 → 1.0.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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/components/cards/NoteCard.svelte +1 -1
  3. package/dist/components/cards/ProductCard.svelte +1 -1
  4. package/dist/components/cards/ProjectCard.svelte +1 -1
  5. package/dist/components/feedback/Alert.stories.svelte +131 -0
  6. package/dist/components/feedback/Alert.svelte +117 -0
  7. package/dist/components/{patterns → feedback}/Alert.svelte.d.ts +4 -2
  8. package/dist/components/feedback/Modal.stories.svelte +49 -117
  9. package/dist/components/feedback/Modal.svelte +47 -26
  10. package/dist/components/feedback/Toast.stories.svelte +89 -53
  11. package/dist/components/feedback/Toast.svelte +5 -64
  12. package/dist/components/feedback/Toast.svelte.d.ts +2 -0
  13. package/dist/components/feedback/ToastRegion.stories.svelte +11 -0
  14. package/dist/components/feedback/ToastRegion.svelte +1 -0
  15. package/dist/components/feedback/index.d.ts +1 -0
  16. package/dist/components/feedback/index.js +1 -0
  17. package/dist/components/forms/Input.stories.svelte +15 -0
  18. package/dist/components/forms/Input.svelte +11 -0
  19. package/dist/components/forms/InputWrap.composition.stories.svelte +11 -0
  20. package/dist/components/forms/InputWrap.stories.svelte +9 -0
  21. package/dist/components/forms/InputWrap.svelte +13 -11
  22. package/dist/components/forms/Switch.stories.svelte +55 -47
  23. package/dist/components/forms/Switch.svelte +4 -3
  24. package/dist/components/patterns/CtaBlock.svelte +1 -0
  25. package/dist/components/patterns/PageHero.stories.svelte +39 -0
  26. package/dist/components/patterns/PageHero.svelte +17 -3
  27. package/dist/components/patterns/PageHero.svelte.d.ts +5 -1
  28. package/dist/components/patterns/index.d.ts +0 -1
  29. package/dist/components/patterns/index.js +0 -1
  30. package/dist/components/primitives/Heading.stories.svelte +6 -30
  31. package/dist/components/primitives/Heading.svelte +2 -11
  32. package/dist/components/primitives/Heading.svelte.d.ts +0 -3
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +2 -2
  35. package/dist/stores/toast.d.ts +2 -0
  36. package/dist/stores/toast.js +1 -0
  37. package/dist/tokens/tokens.css +2 -0
  38. package/package.json +1 -1
  39. package/dist/components/patterns/Alert.stories.svelte +0 -63
  40. package/dist/components/patterns/Alert.svelte +0 -91
  41. /package/dist/components/{patterns → feedback}/Alert.stories.svelte.d.ts +0 -0
@@ -11,9 +11,11 @@
11
11
  });
12
12
  </script>
13
13
 
14
- <!-- AC-44, AC-45, AC-46, AC-47: Off state -->
15
- <Story name="Off (Default)" args={{ label: "Dark mode", checked: false }}
16
- play={async ({ canvasElement }) => {
14
+ <!-- AC-44, AC-45, AC-46, AC-47, AC-50: Off state + Space key toggle -->
15
+ <Story
16
+ name="Off (Default)"
17
+ args={{ label: "Dark mode", checked: false }}
18
+ play={async ({ canvasElement, userEvent }) => {
17
19
  const canvas = within(canvasElement);
18
20
  // AC-44: renders a button with role="switch"
19
21
  // AC-46: accessible name provided by aria-label={label}
@@ -24,11 +26,32 @@
24
26
  // AC-47: track background matches var(--bg-sunken) when off
25
27
  const bgSunken = resolveTokenColor("--bg-sunken");
26
28
  await expect(getComputedStyle(sw).backgroundColor).toBe(bgSunken);
27
- }} />
29
+ // AC-1: off-state border resolves to --rule
30
+ const ruleColor = resolveTokenColor("--rule");
31
+ await expect(getComputedStyle(sw).borderColor).toBe(ruleColor);
32
+ // AC-2: border is NOT invisible (not the same as --bg)
33
+ const bgColor = resolveTokenColor("--bg");
34
+ await expect(getComputedStyle(sw).borderColor).not.toBe(bgColor);
35
+ // AC-5: clicking the label text toggles the switch on
36
+ await userEvent.click(canvas.getByText("Dark mode"));
37
+ await expect(sw.getAttribute("aria-checked")).toBe("true");
38
+ // AC-8: clicking the button itself flips it back to off
39
+ await userEvent.click(sw);
40
+ await expect(sw.getAttribute("aria-checked")).toBe("false");
41
+ // AC-50: Space key toggles switch from off to on
42
+ sw.focus();
43
+ await expect(sw).toHaveFocus();
44
+ await userEvent.keyboard(" ");
45
+ await expect(sw.getAttribute("aria-checked")).toBe("true");
46
+ await userEvent.keyboard(" ");
47
+ }}
48
+ />
28
49
 
29
- <!-- AC-45, AC-48: On state -->
30
- <Story name="On" args={{ label: "Dark mode", checked: true }}
31
- play={async ({ canvasElement }) => {
50
+ <!-- AC-45, AC-48, AC-52: On state + amber track + pill shape dimensions -->
51
+ <Story
52
+ name="On"
53
+ args={{ label: "Dark mode", checked: true }}
54
+ play={async ({ canvasElement, userEvent }) => {
32
55
  const canvas = within(canvasElement);
33
56
  const sw = canvas.getByRole("switch", { name: "Dark mode" });
34
57
  // AC-45: aria-checked is "true" when on
@@ -36,51 +59,36 @@
36
59
  // AC-48: track background matches var(--amber) when on
37
60
  const amberColor = resolveTokenColor("--amber");
38
61
  await expect(getComputedStyle(sw).backgroundColor).toBe(amberColor);
39
- }} />
62
+ // AC-3: on-state border resolves to --amber
63
+ await expect(getComputedStyle(sw).borderColor).toBe(amberColor);
64
+ // AC-52: pill shape — borderRadius is 11px, width 40px, height 22px
65
+ await expect(getComputedStyle(sw).borderRadius).toBe("11px");
66
+ await expect(getComputedStyle(sw).width).toBe("40px");
67
+ await expect(getComputedStyle(sw).height).toBe("22px");
68
+ // AC-6: clicking the label text toggles the switch off
69
+ await userEvent.click(canvas.getByText("Dark mode"));
70
+ await expect(sw.getAttribute("aria-checked")).toBe("false");
71
+ await userEvent.click(canvas.getByText("Dark mode"));
72
+ }}
73
+ />
40
74
 
41
- <!-- AC-49: disabled off — toBeDisabled and wrap opacity 0.4 -->
42
- <Story name="Disabled Off" args={{ label: "Feature flag", disabled: true, checked: false }}
43
- play={async ({ canvasElement }) => {
75
+ <!-- AC-49: disabled — toBeDisabled, wrap opacity 0.4, no-toggle, and disabled-on aria-checked -->
76
+ <Story
77
+ name="Disabled"
78
+ args={{ label: "Feature flag", disabled: true, checked: false }}
79
+ play={async ({ canvasElement, userEvent }) => {
44
80
  const canvas = within(canvasElement);
45
81
  const sw = canvas.getByRole("switch", { name: "Feature flag" });
46
82
  await expect(sw).toBeDisabled();
47
83
  // AC-49: wrap opacity is 0.4
48
84
  const wrap = canvasElement.querySelector(".switch-wrap");
49
85
  await expect(getComputedStyle(wrap!).opacity).toBe("0.4");
50
- }} />
51
-
52
- <!-- AC-49: disabled on — toBeDisabled and aria-checked true -->
53
- <Story name="Disabled On" args={{ label: "Feature flag", disabled: true, checked: true }}
54
- play={async ({ canvasElement }) => {
55
- const canvas = within(canvasElement);
56
- const sw = canvas.getByRole("switch", { name: "Feature flag" });
57
- await expect(sw).toBeDisabled();
58
- await expect(sw.getAttribute("aria-checked")).toBe("true");
59
- }} />
60
-
61
- <!-- AC-50: Space key toggles switch from off to on -->
62
- <Story name="Space to Toggle" args={{ label: "Toggle", checked: false }}
63
- play={async ({ canvasElement, userEvent }) => {
64
- const canvas = within(canvasElement);
65
- const sw = canvas.getByRole("switch", { name: "Toggle" });
86
+ // AC-4: disabled off-state border still resolves to --rule
87
+ const ruleColor = resolveTokenColor("--rule");
88
+ await expect(getComputedStyle(sw).borderColor).toBe(ruleColor);
89
+ // AC-7: clicking the label on a disabled switch must NOT toggle it
90
+ await userEvent.click(canvas.getByText("Feature flag"));
66
91
  await expect(sw.getAttribute("aria-checked")).toBe("false");
67
- await userEvent.tab();
68
- await expect(sw).toHaveFocus();
69
- await userEvent.keyboard(" ");
70
- // AC-50: after Space on an off switch, aria-checked becomes "true"
71
- await expect(sw.getAttribute("aria-checked")).toBe("true");
72
- }} />
73
-
74
- <!-- AC-48, AC-52: amber track when on + pill shape dimensions -->
75
- <Story name="Amber Track When On" args={{ label: "Power", checked: true }}
76
- play={async ({ canvasElement }) => {
77
- const canvas = within(canvasElement);
78
- const sw = canvas.getByRole("switch", { name: "Power" });
79
- // AC-48: track background matches var(--amber)
80
- const amberColor = resolveTokenColor("--amber");
81
- await expect(getComputedStyle(sw).backgroundColor).toBe(amberColor);
82
- // AC-52: pill shape — borderRadius is 11px, width 40px, height 22px
83
- await expect(getComputedStyle(sw).borderRadius).toBe("11px");
84
- await expect(getComputedStyle(sw).width).toBe("40px");
85
- await expect(getComputedStyle(sw).height).toBe("22px");
86
- }} />
92
+ await userEvent.click(canvas.getByText("Feature flag"));
93
+ }}
94
+ />
@@ -46,7 +46,7 @@
46
46
  >
47
47
  <span class="switch-knob" aria-hidden="true"></span>
48
48
  </button>
49
- <span class="switch-label">{label}</span>
49
+ <span class="switch-label" onclick={() => { if (!disabled) checked = !checked }}>{label}</span>
50
50
  </span>
51
51
 
52
52
  <style>
@@ -70,7 +70,7 @@
70
70
  width: 40px;
71
71
  height: 22px;
72
72
  border-radius: 11px;
73
- border: 1px solid var(--rail);
73
+ border: 1px solid var(--rule);
74
74
  background: var(--bg-sunken);
75
75
  cursor: pointer;
76
76
  padding: 0;
@@ -98,12 +98,13 @@
98
98
  width: 16px;
99
99
  height: 16px;
100
100
  border-radius: 50%;
101
- background: var(--bg);
101
+ background: var(--ink-faint);
102
102
  transform: translateX(2px);
103
103
  transition: transform var(--transition);
104
104
  }
105
105
 
106
106
  .switch.on .switch-knob {
107
+ background: var(--bg);
107
108
  transform: translateX(20px);
108
109
  }
109
110
 
@@ -48,6 +48,7 @@
48
48
 
49
49
  <style>
50
50
  .cta-block {
51
+ display: block;
51
52
  border: 1px solid var(--amber);
52
53
  padding: 24px 32px;
53
54
  color: inherit;
@@ -60,3 +60,42 @@
60
60
  <Button variant="primary">View Catalogue</Button>
61
61
  <Button variant="ghost">View Projects →</Button>
62
62
  </Story>
63
+
64
+ <!-- B36 AC-1, AC-2 branch B, AC-3, AC-4, AC-8: snippet-based heading with mixed ink colors -->
65
+ <Story name="SnippetHeading"
66
+ play={async ({ canvasElement }) => {
67
+ // AC-3a: h1 is present and visible
68
+ const h1 = canvasElement.querySelector("h1");
69
+ await expect(h1).not.toBeNull();
70
+ await expect(h1).toBeVisible();
71
+
72
+ // AC-3b: h1 contains an <em> child
73
+ const em = h1!.querySelector("em");
74
+ await expect(em).not.toBeNull();
75
+
76
+ // AC-3d: computed fontStyle of <em> is "normal" (proves :global rule fired)
77
+ await expect(getComputedStyle(em!).fontStyle).toBe("normal");
78
+ }}>
79
+ {#snippet template(args)}
80
+ {#snippet headingContent()}
81
+ Dexter.<br /><em>Things built</em><br />in the lab.
82
+ {/snippet}
83
+ <PageHero eyebrow="// DEXTERLABS" headingContent={headingContent} />
84
+ {/snippet}
85
+ </Story>
86
+
87
+ <!-- B36 AC-5, AC-6: border prop — suppresses bottom rule -->
88
+ <Story name="NoBorder" args={{ eyebrow: "// DEXTERLABS", heading: "Homepage", border: false }}
89
+ play={async ({ canvasElement }) => {
90
+ const canvas = within(canvasElement);
91
+
92
+ // AC-6a: heading is visible
93
+ await expect(canvas.getByRole("heading", { level: 1, name: "Homepage" })).toBeVisible();
94
+
95
+ // AC-6b: <header> element is present
96
+ const header = canvasElement.querySelector("header");
97
+ await expect(header).not.toBeNull();
98
+
99
+ // AC-6c: border-bottom is suppressed
100
+ await expect(getComputedStyle(header!).borderBottomStyle).toBe("none");
101
+ }} />
@@ -8,9 +8,13 @@
8
8
  /** Small mono label shown above the heading. */
9
9
  eyebrow?: string
10
10
  /** Hero heading text. */
11
- heading: string
11
+ heading?: string
12
+ /** Snippet-based hero heading — takes precedence over `heading` string when provided. */
13
+ headingContent?: Snippet
12
14
  /** Subtitle / lede text shown below the heading. */
13
15
  lede?: string
16
+ /** Show bottom border rule. @default true */
17
+ border?: boolean
14
18
  children?: Snippet
15
19
  [key: string]: unknown
16
20
  }
@@ -18,17 +22,19 @@
18
22
  let {
19
23
  eyebrow,
20
24
  heading,
25
+ headingContent,
21
26
  lede,
27
+ border = true,
22
28
  children,
23
29
  ...rest
24
30
  }: Props = $props()
25
31
  </script>
26
32
 
27
- <header class="page-hero" {...rest}>
33
+ <header class="page-hero" class:page-hero--bordered={border} {...rest}>
28
34
  {#if eyebrow}
29
35
  <div class="page-hero-eyebrow"><Text variant="eyebrow">{eyebrow}</Text></div>
30
36
  {/if}
31
- <Heading level={1} variant="hero">{heading}</Heading>
37
+ <Heading level={1} variant="hero">{#if headingContent}{@render headingContent()}{:else}{heading}{/if}</Heading>
32
38
  {#if lede}
33
39
  <div class="page-hero-lede"><Text variant="lede">{lede}</Text></div>
34
40
  {/if}
@@ -44,6 +50,9 @@
44
50
  <style>
45
51
  .page-hero {
46
52
  padding: 48px 0 40px;
53
+ }
54
+
55
+ .page-hero--bordered {
47
56
  border-bottom: 1px solid var(--rule);
48
57
  }
49
58
 
@@ -59,4 +68,9 @@
59
68
  .page-hero-actions {
60
69
  margin-top: 24px;
61
70
  }
71
+
72
+ .page-hero :global(.hero-heading em) {
73
+ font-style: normal;
74
+ color: var(--ink-faint);
75
+ }
62
76
  </style>
@@ -3,9 +3,13 @@ interface Props {
3
3
  /** Small mono label shown above the heading. */
4
4
  eyebrow?: string;
5
5
  /** Hero heading text. */
6
- heading: string;
6
+ heading?: string;
7
+ /** Snippet-based hero heading — takes precedence over `heading` string when provided. */
8
+ headingContent?: Snippet;
7
9
  /** Subtitle / lede text shown below the heading. */
8
10
  lede?: string;
11
+ /** Show bottom border rule. @default true */
12
+ border?: boolean;
9
13
  children?: Snippet;
10
14
  [key: string]: unknown;
11
15
  }
@@ -1,4 +1,3 @@
1
- export { default as Alert } from './Alert.svelte';
2
1
  export { default as CtaBlock } from './CtaBlock.svelte';
3
2
  export { default as StatCard } from './StatCard.svelte';
4
3
  export { default as KvList } from './KvList.svelte';
@@ -1,4 +1,3 @@
1
- export { default as Alert } from './Alert.svelte';
2
1
  export { default as CtaBlock } from './CtaBlock.svelte';
3
2
  export { default as StatCard } from './StatCard.svelte';
4
3
  export { default as KvList } from './KvList.svelte';
@@ -12,6 +12,9 @@
12
12
 
13
13
  <!-- AC-17, AC-18, AC-19, AC-21, AC-22, AC-23: levels 1/2/3 render h1/h2/h3 with default variant classes -->
14
14
  <!-- AC-20: default level is 2 -->
15
+ <!-- AC-12: H1 font-size === 72px (var(--t-h1)) -->
16
+ <!-- AC-10: H2 font-size === 36px (var(--t-h2)) -->
17
+ <!-- AC-11: H3 font-size === 24px (var(--t-h3)) -->
15
18
  <Story name="Levels"
16
19
  play={async ({ canvasElement }) => {
17
20
  const canvas = within(canvasElement);
@@ -19,14 +22,17 @@
19
22
  const h1 = canvas.getByTestId("level-1");
20
23
  await expect(h1.tagName).toBe("H1");
21
24
  await expect(h1.classList.contains("h1")).toBe(true);
25
+ await expect(getComputedStyle(h1).fontSize).toBe("72px");
22
26
 
23
27
  const h2 = canvas.getByTestId("level-2");
24
28
  await expect(h2.tagName).toBe("H2");
25
29
  await expect(h2.classList.contains("h2")).toBe(true);
30
+ await expect(getComputedStyle(h2).fontSize).toBe("36px");
26
31
 
27
32
  const h3 = canvas.getByTestId("level-3");
28
33
  await expect(h3.tagName).toBe("H3");
29
34
  await expect(h3.classList.contains("h3")).toBe(true);
35
+ await expect(getComputedStyle(h3).fontSize).toBe("24px");
30
36
 
31
37
  const defaultLevel = canvas.getByTestId("level-default");
32
38
  await expect(defaultLevel.tagName).toBe("H2");
@@ -105,33 +111,3 @@
105
111
  Section Title
106
112
  </Story>
107
113
 
108
- <!-- B26 AC-12, AC-43: size="xs" on h3 → 12px -->
109
- <Story name="SizeXs" args={{ level: 3, size: "xs" }}
110
- play={async ({ canvasElement }) => {
111
- const el = canvasElement.firstElementChild!;
112
- await expect(el.getAttribute("data-size")).toBe("xs");
113
- await expect(getComputedStyle(el).fontSize).toBe("12px");
114
- }}>
115
- Micro heading
116
- </Story>
117
-
118
- <!-- B26 AC-13, AC-15, AC-43: size="lg" on h3 → 19px, letterSpacing stays at h3 default -0.01em -->
119
- <Story name="SizeLg" args={{ level: 3, size: "lg" }}
120
- play={async ({ canvasElement }) => {
121
- const el = canvasElement.firstElementChild!;
122
- await expect(el.getAttribute("data-size")).toBe("lg");
123
- await expect(getComputedStyle(el).fontSize).toBe("19px");
124
- await expect(getComputedStyle(el).letterSpacing).toBe("-0.19px");
125
- }}>
126
- Lede-size heading
127
- </Story>
128
-
129
- <!-- B26 AC-14: size="xl" on h3 → 24px -->
130
- <Story name="SizeXl" args={{ level: 3, size: "xl" }}
131
- play={async ({ canvasElement }) => {
132
- const el = canvasElement.firstElementChild!;
133
- await expect(el.getAttribute("data-size")).toBe("xl");
134
- await expect(getComputedStyle(el).fontSize).toBe("24px");
135
- }}>
136
- XL heading
137
- </Story>
@@ -5,8 +5,6 @@
5
5
  type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6
6
6
  type HeadingVariant = 'display' | 'hero' | 'h1' | 'h2' | 'h3'
7
7
  type TextColor = 'ink' | 'dim' | 'faint' | 'amber' | 'cyan' | 'ok' | 'danger'
8
- type SizeVariant = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
9
-
10
8
  interface Props {
11
9
  /** HTML heading level (h1–h6). @default 2 */
12
10
  level?: HeadingLevel
@@ -14,8 +12,6 @@
14
12
  variant?: HeadingVariant
15
13
  /** Text colour mapped to a design token. */
16
14
  color?: TextColor
17
- /** Font size override mapped to a type-scale token. */
18
- size?: SizeVariant
19
15
  children?: Snippet
20
16
  class?: ClassValue | null
21
17
  style?: string | null
@@ -46,7 +42,7 @@
46
42
  danger: 'danger',
47
43
  }
48
44
 
49
- let { level = 2, variant, color, size, children, class: klass = '', style, ...rest }: Props = $props()
45
+ let { level = 2, variant, color, children, class: klass = '', style, ...rest }: Props = $props()
50
46
 
51
47
  const resolvedVariant = $derived(variant ?? DEFAULT_VARIANT[level])
52
48
  const variantClass = $derived(resolvedVariant ? VARIANT_CLASS[resolvedVariant] : undefined)
@@ -54,7 +50,7 @@
54
50
  const mergedStyle = $derived([colorStyle, style].filter(Boolean).join(' ') || undefined)
55
51
  </script>
56
52
 
57
- <svelte:element this={"h" + level} class={[variantClass, klass]} style={mergedStyle} data-size={size || undefined} {...rest}>
53
+ <svelte:element this={"h" + level} class={[variantClass, klass]} style={mergedStyle} {...rest}>
58
54
  {@render children?.()}
59
55
  </svelte:element>
60
56
 
@@ -99,9 +95,4 @@
99
95
  line-height: 0.9;
100
96
  }
101
97
 
102
- [data-size="xs"] { font-size: var(--t-micro) }
103
- [data-size="sm"] { font-size: var(--t-mono) }
104
- [data-size="md"] { font-size: var(--t-body) }
105
- [data-size="lg"] { font-size: var(--t-lede) }
106
- [data-size="xl"] { font-size: var(--t-h3) }
107
98
  </style>
@@ -3,7 +3,6 @@ import type { Snippet } from 'svelte';
3
3
  type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
4
4
  type HeadingVariant = 'display' | 'hero' | 'h1' | 'h2' | 'h3';
5
5
  type TextColor = 'ink' | 'dim' | 'faint' | 'amber' | 'cyan' | 'ok' | 'danger';
6
- type SizeVariant = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
7
6
  interface Props {
8
7
  /** HTML heading level (h1–h6). @default 2 */
9
8
  level?: HeadingLevel;
@@ -11,8 +10,6 @@ interface Props {
11
10
  variant?: HeadingVariant;
12
11
  /** Text colour mapped to a design token. */
13
12
  color?: TextColor;
14
- /** Font size override mapped to a type-scale token. */
15
- size?: SizeVariant;
16
13
  children?: Snippet;
17
14
  class?: ClassValue | null;
18
15
  style?: string | null;
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ export { Stack, Inline, Spread, Grid, Container, Rule, Prose } from './component
3
3
  export { Card, ProductCard, ProjectCard, NoteCard } from './components/cards/index.js';
4
4
  export { Nav, Breadcrumb } from './components/navigation/index.js';
5
5
  export { Input, Textarea, Select, InputWrap, Field, Checkbox, Radio, RadioGroup, Switch } from './components/forms/index.js';
6
- export { Modal, Toast, ToastRegion } from './components/feedback/index.js';
6
+ export { Alert, Modal, Toast, ToastRegion } from './components/feedback/index.js';
7
7
  export { toast } from './stores/toast.js';
8
8
  export type { ToastItem, ToastVariant, ToastOptions } from './stores/toast.js';
9
- export { Alert, CtaBlock, StatCard, KvList, ProgressBar, ActivityRow, SectionHead, SectionFoot, PageHero } from './components/patterns/index.js';
9
+ export { CtaBlock, StatCard, KvList, ProgressBar, ActivityRow, SectionHead, SectionFoot, PageHero } from './components/patterns/index.js';
10
10
  export { Accordion, AccordionItem, Tabs, Table } from './components/data/index.js';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { Stack, Inline, Spread, Grid, Container, Rule, Prose } from './component
4
4
  export { Card, ProductCard, ProjectCard, NoteCard } from './components/cards/index.js';
5
5
  export { Nav, Breadcrumb } from './components/navigation/index.js';
6
6
  export { Input, Textarea, Select, InputWrap, Field, Checkbox, Radio, RadioGroup, Switch } from './components/forms/index.js';
7
- export { Modal, Toast, ToastRegion } from './components/feedback/index.js';
7
+ export { Alert, Modal, Toast, ToastRegion } from './components/feedback/index.js';
8
8
  export { toast } from './stores/toast.js';
9
- export { Alert, CtaBlock, StatCard, KvList, ProgressBar, ActivityRow, SectionHead, SectionFoot, PageHero } from './components/patterns/index.js';
9
+ export { CtaBlock, StatCard, KvList, ProgressBar, ActivityRow, SectionHead, SectionFoot, PageHero } from './components/patterns/index.js';
10
10
  export { Accordion, AccordionItem, Tabs, Table } from './components/data/index.js';
@@ -2,12 +2,14 @@ export type ToastVariant = "success" | "warning" | "error";
2
2
  export interface ToastItem {
3
3
  id: string;
4
4
  message: string;
5
+ title: string;
5
6
  variant: ToastVariant;
6
7
  duration: number;
7
8
  }
8
9
  export interface ToastOptions {
9
10
  variant?: ToastVariant;
10
11
  duration?: number;
12
+ title?: string;
11
13
  }
12
14
  declare function push(message: string, options?: ToastOptions): string;
13
15
  declare function dismiss(id: string): void;
@@ -6,6 +6,7 @@ function push(message, options) {
6
6
  const item = {
7
7
  id,
8
8
  message,
9
+ title: options?.title ?? '',
9
10
  variant: options?.variant ?? "success",
10
11
  duration: options?.duration ?? 5000,
11
12
  };
@@ -12,6 +12,7 @@
12
12
  --bg-rail: #0f1211; /* cards, nav, sidebars */
13
13
  --bg-elev: #141817; /* elevated surfaces, inline-code bg */
14
14
  --bg-sunken: #070908; /* code blocks, status bar, sunken wells */
15
+ --overlay: rgba(7, 9, 8, 0.85);
15
16
 
16
17
  /* Ink */
17
18
  --ink: #d6e2dc; /* primary text */
@@ -93,4 +94,5 @@
93
94
  --ok: #356b31;
94
95
  --shiki-token-string: #2d7a50;
95
96
  --shiki-token-string-expression: #2d7a50;
97
+ --overlay: rgba(7, 9, 8, 0.85);
96
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxlbnl/ui",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "svelte": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,63 +0,0 @@
1
- <script module lang="ts">
2
- import { defineMeta } from "@storybook/addon-svelte-csf";
3
- import { expect, within } from "storybook/test";
4
- import Alert from "./Alert.svelte";
5
- import { resolveTokenFgColor } from "../../storybook-utils.js";
6
-
7
- const { Story } = defineMeta({
8
- title: "Patterns/Alert",
9
- component: Alert,
10
- tags: ["autodocs"],
11
- });
12
- </script>
13
-
14
- <Story name="Ok" args={{ variant: "ok", title: "Build successful", message: "All rails nominal." }}
15
- play={async ({ canvasElement }) => {
16
- const canvas = within(canvasElement);
17
- const root = canvas.getByRole("status");
18
- await expect(root).toBeVisible();
19
- await expect(canvas.getByText("Build successful")).toBeVisible();
20
- await expect(canvas.getByText("All rails nominal.")).toBeVisible();
21
- const tag = canvas.getByText("ok");
22
- await expect(tag).toHaveAttribute("aria-hidden", "true");
23
- const okColor = resolveTokenFgColor("--ok");
24
- await expect(getComputedStyle(root).borderLeftColor).toBe(okColor);
25
- }} />
26
-
27
- <Story name="Amber" args={{ variant: "amber", title: "High load", message: "+12V rail at 88%." }}
28
- play={async ({ canvasElement }) => {
29
- const canvas = within(canvasElement);
30
- const root = canvas.getByRole("status");
31
- await expect(root).toBeVisible();
32
- await expect(canvas.getByText("High load")).toBeVisible();
33
- const amberColor = resolveTokenFgColor("--amber");
34
- await expect(getComputedStyle(root).borderLeftColor).toBe(amberColor);
35
- }} />
36
-
37
- <Story name="Danger" args={{ variant: "danger", title: "Thermal fault", message: "Over-temperature protection triggered." }}
38
- play={async ({ canvasElement }) => {
39
- const canvas = within(canvasElement);
40
- const root = canvas.getByRole("status");
41
- await expect(root).toBeVisible();
42
- await expect(canvas.getByText("Thermal fault")).toBeVisible();
43
- const dangerColor = resolveTokenFgColor("--danger");
44
- await expect(getComputedStyle(root).borderLeftColor).toBe(dangerColor);
45
- }} />
46
-
47
- <Story name="Info" args={{ variant: "info", title: "Firmware update available", message: "v2.1.0 → v2.2.0." }}
48
- play={async ({ canvasElement }) => {
49
- const canvas = within(canvasElement);
50
- const root = canvas.getByRole("status");
51
- await expect(root).toBeVisible();
52
- const cyanColor = resolveTokenFgColor("--cyan");
53
- await expect(getComputedStyle(root).borderLeftColor).toBe(cyanColor);
54
- }} />
55
-
56
- <Story name="No Message" args={{ variant: "ok", title: "Status OK" }}
57
- play={async ({ canvasElement }) => {
58
- const canvas = within(canvasElement);
59
- const root = canvas.getByRole("status");
60
- await expect(root).toBeVisible();
61
- await expect(canvas.getByText("Status OK")).toBeVisible();
62
- await expect(canvasElement.querySelector(".alert-msg")).toBeNull();
63
- }} />
@@ -1,91 +0,0 @@
1
- <script lang="ts">
2
- import type { HTMLAttributes } from 'svelte/elements'
3
- import type { Snippet } from 'svelte'
4
- import Stack from '../layout/Stack.svelte'
5
-
6
- type AlertVariant = 'ok' | 'amber' | 'danger' | 'info'
7
-
8
- interface Props extends HTMLAttributes<HTMLDivElement> {
9
- /** Colour variant — drives the tag glyph and border accent. @default 'info' */
10
- variant?: AlertVariant
11
- /** Bold title text shown in the alert header. */
12
- title: string
13
- /** Body message text. */
14
- message?: string
15
- children?: Snippet
16
- [key: string]: unknown
17
- }
18
-
19
- let {
20
- variant = 'info',
21
- title,
22
- message,
23
- children,
24
- ...rest
25
- }: Props = $props()
26
-
27
- const TAG_GLYPHS: Record<AlertVariant, string> = {
28
- ok: 'ok',
29
- amber: '!!',
30
- danger: 'err',
31
- info: 'inf',
32
- }
33
- </script>
34
-
35
- <div class="alert alert--{variant}" role="status" {...rest}>
36
- <span class="alert-tag" aria-hidden="true">{TAG_GLYPHS[variant]}</span>
37
- <Stack gap="sm">
38
- <span class="alert-title">{title}</span>
39
- {#if message}
40
- <span class="alert-msg">{message}</span>
41
- {/if}
42
- {@render children?.()}
43
- </Stack>
44
- </div>
45
-
46
- <style>
47
- .alert {
48
- display: flex;
49
- align-items: flex-start;
50
- gap: 12px;
51
- padding: 12px 16px;
52
- border-left: 2px solid;
53
- font-size: var(--t-body);
54
- line-height: 1.5;
55
- }
56
-
57
- .alert--ok { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 8%, transparent); }
58
- .alert--amber { border-color: var(--amber); background: color-mix(in srgb, var(--amber) 8%, transparent); }
59
- .alert--danger { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 8%, transparent); }
60
- .alert--info { border-color: var(--cyan); background: color-mix(in srgb, var(--cyan) 8%, transparent); }
61
-
62
- .alert-tag {
63
- font-family: var(--mono);
64
- font-size: var(--t-micro);
65
- letter-spacing: 0.08em;
66
- text-transform: uppercase;
67
- flex-shrink: 0;
68
- margin-top: 2px;
69
- }
70
- .alert--ok .alert-tag { color: var(--ok); }
71
- .alert--amber .alert-tag { color: var(--amber); }
72
- .alert--danger .alert-tag { color: var(--danger); }
73
- .alert--info .alert-tag { color: var(--cyan); }
74
-
75
- .alert-title {
76
- font-family: var(--mono);
77
- font-size: var(--t-micro);
78
- letter-spacing: 0.08em;
79
- text-transform: uppercase;
80
- }
81
- .alert--ok .alert-title { color: var(--ok); }
82
- .alert--amber .alert-title { color: var(--amber); }
83
- .alert--danger .alert-title { color: var(--danger); }
84
- .alert--info .alert-title { color: var(--cyan); }
85
-
86
- .alert-msg {
87
- font-size: var(--t-mono);
88
- color: var(--ink-dim);
89
- line-height: 1.5;
90
- }
91
- </style>