@happyvertical/smrt-ui 0.32.0 → 0.32.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.
package/AGENTS.md CHANGED
@@ -8,6 +8,26 @@ can consume UI primitives, i18n, the theme system, and the module UI registry
8
8
  integration layer. Extracting this leaf is what breaks the domain ⇄ smrt-svelte
9
9
  peer cycle (see happyvertical/smrt#1582).
10
10
 
11
+ ## The UI split — primitive-adoption contract (#1589)
12
+
13
+ SMRT has one shared set of UI primitives, split across two packages by concern:
14
+
15
+ - **`smrt-ui` (here) owns the domain-agnostic VISUAL primitives** — `Button`,
16
+ `Card`, `Modal`/`ConfirmDialog`, `Badge`, `Avatar`, `Chip`, `Dropdown`,
17
+ `Tooltip`, `Skeleton`, `Tree`, `Pagination`, `DataTable`, … (it has **no**
18
+ form-input components — those carry i18n/voice logic and belong above the leaf).
19
+ - **`smrt-svelte` owns the FORM primitives** — `Input`, `Textarea`, `Select`,
20
+ `Checkbox`/`Toggle`, `Form`, and the specialized date/measurement/address/file
21
+ inputs.
22
+
23
+ **Domain packages import visual primitives from `smrt-ui` and form primitives
24
+ from `smrt-svelte`, and must not hand-roll raw `<button>` / `<input>` /
25
+ `<select>` / `<textarea>` / `<form>` markup** — re-rolling them re-introduces the
26
+ inconsistent a11y / focus / disabled-state behavior the primitives exist to fix.
27
+ Enforced by `scripts/check-raw-primitives.mjs` (report-only during the #1589
28
+ migration; flips strict per package as it adopts the primitives). smrt-ui's own
29
+ components are exempt — they *are* the primitives.
30
+
11
31
  ## What lives here
12
32
 
13
33
  | Subpath | Contents |
@@ -26,6 +26,12 @@ export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
26
26
  fullWidth?: boolean;
27
27
  /** Loading state */
28
28
  loading?: boolean;
29
+ /**
30
+ * Extra class(es) appended after the base `button {variant} {size}` styling.
31
+ * Lets callers adopt Button for custom-styled buttons without losing their
32
+ * own CSS (issue #1589) — the base button styling still applies.
33
+ */
34
+ class?: string;
29
35
  }
30
36
 
31
37
  const {
@@ -36,6 +42,7 @@ const {
36
42
  loading = false,
37
43
  type = 'button',
38
44
  fullWidth = false,
45
+ class: className = '',
39
46
  onclick,
40
47
  children,
41
48
  ...rest
@@ -67,7 +74,7 @@ const linkProps = $derived(() => {
67
74
  {#if isLink}
68
75
  <a
69
76
  {href}
70
- class="button {variant} {size}"
77
+ class="button {variant} {size} {className}"
71
78
  class:disabled={isDisabled}
72
79
  class:full-width={fullWidth}
73
80
  class:loading
@@ -88,7 +95,7 @@ const linkProps = $derived(() => {
88
95
  {type}
89
96
  disabled={isDisabled}
90
97
  aria-busy={loading}
91
- class="button {variant} {size}"
98
+ class="button {variant} {size} {className}"
92
99
  class:full-width={fullWidth}
93
100
  class:loading
94
101
  {onclick}
@@ -21,6 +21,12 @@ export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
21
21
  fullWidth?: boolean;
22
22
  /** Loading state */
23
23
  loading?: boolean;
24
+ /**
25
+ * Extra class(es) appended after the base `button {variant} {size}` styling.
26
+ * Lets callers adopt Button for custom-styled buttons without losing their
27
+ * own CSS (issue #1589) — the base button styling still applies.
28
+ */
29
+ class?: string;
24
30
  }
25
31
  declare const Button: import("svelte").Component<Props, {}, "">;
26
32
  type Button = ReturnType<typeof Button>;
@@ -1 +1 @@
1
- {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAEV,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAoED,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAEV,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqED,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -60,6 +60,29 @@ describe('Button', () => {
60
60
  const link = screen.getByRole('link', { name: 'Home' });
61
61
  expect(link).toHaveAttribute('href', '/home');
62
62
  });
63
+ it('appends a custom class while keeping the base button styling (#1589)', () => {
64
+ render(Button, {
65
+ props: { children: textSnippet('Save'), class: 'topic-action-btn' },
66
+ });
67
+ const button = screen.getByRole('button', { name: 'Save' });
68
+ // Caller's class is applied (so custom-styled buttons can adopt Button)...
69
+ expect(button).toHaveClass('topic-action-btn');
70
+ // ...without dropping the primitive's own base/variant styling.
71
+ expect(button).toHaveClass('button');
72
+ expect(button).toHaveClass('primary');
73
+ });
74
+ it('appends a custom class in link mode too', () => {
75
+ render(Button, {
76
+ props: {
77
+ children: textSnippet('Home'),
78
+ href: '/home',
79
+ class: 'nav-link',
80
+ },
81
+ });
82
+ const link = screen.getByRole('link', { name: 'Home' });
83
+ expect(link).toHaveClass('nav-link');
84
+ expect(link).toHaveClass('button');
85
+ });
63
86
  it('is axe-clean', async () => {
64
87
  const { container } = render(Button, {
65
88
  props: { children: textSnippet('Accessible') },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-ui",
3
- "version": "0.32.0",
3
+ "version": "0.32.2",
4
4
  "description": "Domain-agnostic Svelte 5 UI runtime for SMRT: primitives, i18n client, theme system, and module UI registry",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -108,7 +108,7 @@
108
108
  },
109
109
  "dependencies": {
110
110
  "esm-env": "^1.2.2",
111
- "@happyvertical/smrt-types": "0.32.0"
111
+ "@happyvertical/smrt-types": "0.32.2"
112
112
  },
113
113
  "peerDependencies": {
114
114
  "svelte": "^5.18.2"