@happyvertical/smrt-ui 0.54.4 → 0.55.1

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 (36) hide show
  1. package/AGENTS.md +1 -0
  2. package/README.md +38 -4
  3. package/dist/components/layout/ActionGroup.svelte +55 -0
  4. package/dist/components/layout/ActionGroup.svelte.d.ts +18 -0
  5. package/dist/components/layout/ActionGroup.svelte.d.ts.map +1 -0
  6. package/dist/components/layout/Container.svelte +1 -0
  7. package/dist/components/layout/Grid.svelte +2 -1
  8. package/dist/components/layout/PageHeader.svelte +3 -3
  9. package/dist/components/layout/PageHeader.svelte.d.ts +3 -3
  10. package/dist/components/layout/PageLayout.svelte +61 -0
  11. package/dist/components/layout/PageLayout.svelte.d.ts +18 -0
  12. package/dist/components/layout/PageLayout.svelte.d.ts.map +1 -0
  13. package/dist/components/layout/index.d.ts +2 -0
  14. package/dist/components/layout/index.d.ts.map +1 -1
  15. package/dist/components/layout/index.js +2 -0
  16. package/dist/components/permissions/PermissionPicker.svelte +213 -0
  17. package/dist/components/permissions/PermissionPicker.svelte.d.ts +27 -0
  18. package/dist/components/permissions/PermissionPicker.svelte.d.ts.map +1 -0
  19. package/dist/currency.d.ts +3 -0
  20. package/dist/currency.d.ts.map +1 -0
  21. package/dist/currency.js +2 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/svelte/playground/PermissionPickerPreview.svelte +27 -0
  26. package/dist/svelte/playground/PermissionPickerPreview.svelte.d.ts +19 -0
  27. package/dist/svelte/playground/PermissionPickerPreview.svelte.d.ts.map +1 -0
  28. package/dist/svelte/playground.d.ts +14 -2
  29. package/dist/svelte/playground.d.ts.map +1 -1
  30. package/dist/svelte/playground.js +10 -0
  31. package/dist/themes/README.md +5 -0
  32. package/dist/themes/styles/base.css +31 -0
  33. package/dist/themes/styles/index.d.ts +2 -0
  34. package/dist/themes/styles/index.d.ts.map +1 -1
  35. package/dist/themes/styles/index.js +2 -0
  36. package/package.json +7 -2
package/AGENTS.md CHANGED
@@ -53,6 +53,7 @@ components are exempt — they *are* the primitives.
53
53
  | `./calendar` | `CalendarView` (time-zone-aware month/week grids + phone agenda) and its Svelte-free date model (`toEntries`, `layoutMonth`, `layoutWeek`, `dateKeyInZone`, …); deprecated `Calendar`, `DayView` |
54
54
  | `./chat` | `MessageBubble`, `ReactionPicker`, `TypingIndicator` |
55
55
  | `./forms` | Provider-free fields, choice controls, sliders/ranges, combobox/listbox/multiselect/tags, date/time/file controls, `CameraCapture`/`SignaturePad` capture, plus the transport-neutral control interaction registry |
56
+ | `.` permissions | `PermissionCheck` and the domain-agnostic `PermissionPicker` for grouped, searchable native role-grant forms over host-supplied catalog records |
56
57
  | `./form-retry` | **Svelte-free** form retry (#3291): submission key, in-flight refusal, conditional reset, opt-in restore-after-reload, browser `runOnce()` digest. Also re-exported from `./forms`. Framework-free TS typed structurally against SvelteKit's `SubmitFunction` — never import `@sveltejs/kit` here. Guide: `docs/content/form-retry.md` |
57
58
  | `./i18n` | i18n **client**: `useI18n`, `<Trans>`, `defineMessages`, `renderTemplate` (no `smrt-languages` import — the server resolver stays in `smrt-svelte/i18n/server`) |
58
59
  | `./registry` | `ModuleUIRegistry` for cross-package component discovery |
package/README.md CHANGED
@@ -14,6 +14,8 @@ pnpm add @happyvertical/smrt-ui
14
14
  import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
15
15
  import { Button } from '@happyvertical/smrt-ui/ui';
16
16
  import { Form, FormGroup, Input, Switch } from '@happyvertical/smrt-ui/forms';
17
+ import '@happyvertical/smrt-ui/themes/styles/base.css';
18
+ import '@happyvertical/smrt-ui/themes/styles/material.css';
17
19
  </script>
18
20
 
19
21
  <ThemeProvider preset="material" colorScheme="system">
@@ -38,7 +40,7 @@ pnpm add @happyvertical/smrt-ui
38
40
  | Disclosure and overlays | `Popover`, `Disclosure`, `Accordion`/`AccordionItem`, `Modal`, `Drawer`/`Sheet`, `PhoneSheet`, `ConfirmDialog` |
39
41
  | Feedback | `Alert`, `ToastViewport`, `Progress`, `Meter`, `Spinner`, `LoadingOverlay`, `WorkingStrip` |
40
42
  | Collections | `CollectionToolbar`, `CollectionList`/`ContentList`, `DataTable`, `Pagination` |
41
- | Layout and navigation | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, `Tabs`, `FilterChips` |
43
+ | Layout and navigation | `Container`, `PageLayout`, `Grid`, `ActionGroup`, `Header`, `Footer`, `PageHeader`, `EmptyState`, `Tabs`, `FilterChips` |
42
44
  | Calendar | `CalendarView` (deprecated: `Calendar`, `DayView`) |
43
45
 
44
46
  ### Phone surfaces
@@ -93,11 +95,15 @@ under the phone top bar. Pair it with `useLinkSurface` (smrt-svelte) so
93
95
  agents can switch tabs too.
94
96
 
95
97
  Use the focused subpaths (`/forms`, `/form-retry`, `/ui`, `/feedback`, `/data`,
96
- `/data-surface`, `/layout`, `/themes`) to keep imports explicit. The
98
+ `/data-surface`, `/currency`, `/layout`, `/themes`) to keep imports explicit. The
97
99
  Svelte-free `/data-surface` entry exposes the registry contracts and shared
98
100
  protocol limits for server adapters; the Svelte-free `/form-retry` entry
99
- exposes the form-retry helper. The package root remains a compatibility
100
- barrel.
101
+ exposes the form-retry helper. The Svelte-free `/currency` entry exposes
102
+ `ISO_4217_MINOR_UNITS`, the shared ISO 4217 registry used for deterministic
103
+ minor-unit scaling. A missing code is unsupported; a `null` value is a valid
104
+ ISO fund, metal, test, or no-currency code without a defined minor unit, so
105
+ callers handling integer minor-unit money must reject both cases rather than
106
+ inventing a decimal precision. The package root remains a compatibility barrel.
101
107
 
102
108
  ### Calendar dates and shop time
103
109
 
@@ -944,6 +950,7 @@ hand-authored, and every text pairing clears WCAG AA.
944
950
  ```svelte
945
951
  <script>
946
952
  import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
953
+ import '@happyvertical/smrt-ui/themes/styles/base.css';
947
954
  import '@happyvertical/smrt-ui/themes/styles/all.css';
948
955
  import '@happyvertical/smrt-ui/themes/styles/fonts.css';
949
956
  </script>
@@ -953,6 +960,33 @@ hand-authored, and every text pairing clears WCAG AA.
953
960
  </ThemeProvider>
954
961
  ```
955
962
 
963
+ The application baseline is intentionally separate from theme tokens. Import
964
+ `base.css` once at the app entry point to remove the browser body margin, use
965
+ border-box sizing, apply the active theme to the document, and make native form
966
+ controls inherit the app typeface. It does not reset lists or content margins.
967
+
968
+ PageLayout keeps wide tables and tab rows within the available page width,
969
+ including when nested inside a semantic section. Their own scroll containers
970
+ remain scrollable. Place visual sections directly under PageLayout to use its
971
+ vertical gap.
972
+
973
+ Use `PageLayout` inside an application shell to give each route responsive
974
+ gutters, block padding, vertical rhythm, and the width appropriate to its
975
+ content. It never creates a scroll container or claims viewport height.
976
+ Use `ActionGroup` for adjacent page or card actions; it wraps with token spacing
977
+ and can justify actions at the start or end without form-specific behavior.
978
+
979
+ ```svelte
980
+ <script>
981
+ import { PageHeader, PageLayout } from '@happyvertical/smrt-ui/layout';
982
+ </script>
983
+
984
+ <PageLayout maxWidth="xl" gap="md">
985
+ <PageHeader title="Projects" subtitle="Plan and track active work" />
986
+ <!-- collection, cards, form, or detail content -->
987
+ </PageLayout>
988
+ ```
989
+
956
990
  Run the shared playground to inspect the full catalog under every preset and
957
991
  light/dark scheme.
958
992
 
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ type Gap = 'sm' | 'md';
6
+ type Justify = 'start' | 'end';
7
+
8
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class'> {
9
+ /** Space between actions. */
10
+ gap?: Gap;
11
+ /** Horizontal alignment when the row has spare space. */
12
+ justify?: Justify;
13
+ /** CSS classes appended to the action group. */
14
+ class?: string;
15
+ /** Buttons, button links, or other compact actions. */
16
+ children: Snippet;
17
+ }
18
+
19
+ const {
20
+ gap = 'sm',
21
+ justify = 'start',
22
+ class: className = '',
23
+ children,
24
+ ...rest
25
+ }: Props = $props();
26
+ </script>
27
+
28
+ <div class="action-group gap-{gap} justify-{justify} {className}" {...rest}>
29
+ {@render children()}
30
+ </div>
31
+
32
+ <style>
33
+ .action-group {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ align-items: center;
37
+ min-inline-size: 0;
38
+ }
39
+
40
+ .gap-sm {
41
+ gap: var(--smrt-spacing-2);
42
+ }
43
+
44
+ .gap-md {
45
+ gap: var(--smrt-spacing-3);
46
+ }
47
+
48
+ .justify-start {
49
+ justify-content: flex-start;
50
+ }
51
+
52
+ .justify-end {
53
+ justify-content: flex-end;
54
+ }
55
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Gap = 'sm' | 'md';
4
+ type Justify = 'start' | 'end';
5
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class'> {
6
+ /** Space between actions. */
7
+ gap?: Gap;
8
+ /** Horizontal alignment when the row has spare space. */
9
+ justify?: Justify;
10
+ /** CSS classes appended to the action group. */
11
+ class?: string;
12
+ /** Buttons, button links, or other compact actions. */
13
+ children: Snippet;
14
+ }
15
+ declare const ActionGroup: import("svelte").Component<Props, {}, "">;
16
+ type ActionGroup = ReturnType<typeof ActionGroup>;
17
+ export default ActionGroup;
18
+ //# sourceMappingURL=ActionGroup.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActionGroup.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/layout/ActionGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,KAAK,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AACvB,KAAK,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;AAE/B,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC1E,6BAA6B;IAC7B,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAyBD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -20,6 +20,7 @@ const { maxWidth = 'lg', children, ...rest }: Props = $props();
20
20
 
21
21
  <style>
22
22
  .container {
23
+ box-sizing: border-box;
23
24
  width: 100%;
24
25
  margin: 0 auto;
25
26
  padding-left: var(--smrt-spacing-4);
@@ -74,7 +74,7 @@ const isResponsive = $derived(
74
74
  const _gridColumns = $derived.by(() => {
75
75
  if (isResponsive) return undefined;
76
76
  if (columns === 'auto') {
77
- return 'repeat(auto-fill, minmax(300px, 1fr))';
77
+ return 'repeat(auto-fill, minmax(min(300px, 100%), 1fr))';
78
78
  }
79
79
  return `repeat(${columns}, 1fr)`;
80
80
  });
@@ -158,6 +158,7 @@ const gridClasses = $derived.by(() => {
158
158
  <style>
159
159
  .grid {
160
160
  display: grid;
161
+ min-width: 0;
161
162
  }
162
163
 
163
164
  .grid-header {
@@ -11,9 +11,9 @@
11
11
  * title (for its phone top bar). smrt-svelte's `shellPageTrailFor()`
12
12
  * computes such a trail from a nav.
13
13
  * - The crumb row carries `data-shell-breadcrumbs` and the title
14
- * `data-shell-page-title`: on phones AdminShell hides the crumbs and
15
- * visually hides the title, because its phone top bar shows the back arrow
16
- * and the title instead.
14
+ * `data-shell-page-title`: on phones AdminShell hides crumbs only when its
15
+ * top bar declares `data-shell-page-navigation-replacement`, and visually
16
+ * hides the title only with `data-shell-page-title-replacement`.
17
17
  * - No in-page back link: the crumbs (and the phone top bar) are the way
18
18
  * back. `backHref` is kept only for pages outside such a shell.
19
19
  * - An editable title (`titleField`, e.g. an article's headline) renders
@@ -10,9 +10,9 @@
10
10
  * title (for its phone top bar). smrt-svelte's `shellPageTrailFor()`
11
11
  * computes such a trail from a nav.
12
12
  * - The crumb row carries `data-shell-breadcrumbs` and the title
13
- * `data-shell-page-title`: on phones AdminShell hides the crumbs and
14
- * visually hides the title, because its phone top bar shows the back arrow
15
- * and the title instead.
13
+ * `data-shell-page-title`: on phones AdminShell hides crumbs only when its
14
+ * top bar declares `data-shell-page-navigation-replacement`, and visually
15
+ * hides the title only with `data-shell-page-title-replacement`.
16
16
  * - No in-page back link: the crumbs (and the phone top bar) are the way
17
17
  * back. `backHref` is kept only for pages outside such a shell.
18
18
  * - An editable title (`titleField`, e.g. an article's headline) renders
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import Container from './Container.svelte';
5
+
6
+ type MaxWidth = 'sm' | 'md' | 'lg' | 'xl' | 'full';
7
+ type Gap = 'sm' | 'md' | 'lg';
8
+
9
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class'> {
10
+ /** Maximum width of the page content. */
11
+ maxWidth?: MaxWidth;
12
+ /** Vertical space between the page's direct children. */
13
+ gap?: Gap;
14
+ /** CSS classes appended to the page content element. */
15
+ class?: string;
16
+ /** Page content. */
17
+ children: Snippet;
18
+ }
19
+
20
+ const {
21
+ maxWidth = 'lg',
22
+ gap = 'md',
23
+ class: className = '',
24
+ children,
25
+ ...rest
26
+ }: Props = $props();
27
+ </script>
28
+
29
+ <Container {maxWidth} data-page-layout-container>
30
+ <div class="page-layout gap-{gap} {className}" data-page-layout {...rest}>
31
+ {@render children()}
32
+ </div>
33
+ </Container>
34
+
35
+ <style>
36
+ .page-layout {
37
+ display: grid;
38
+ grid-template-columns: minmax(0, 1fr);
39
+ min-inline-size: 0;
40
+ padding-block: var(--smrt-spacing-5) var(--smrt-spacing-8);
41
+ overflow-wrap: anywhere;
42
+ }
43
+
44
+ .gap-sm {
45
+ gap: var(--smrt-spacing-3);
46
+ }
47
+
48
+ .gap-md {
49
+ gap: var(--smrt-spacing-5);
50
+ }
51
+
52
+ .gap-lg {
53
+ gap: var(--smrt-spacing-8);
54
+ }
55
+
56
+ @media (min-width: 640px) {
57
+ .page-layout {
58
+ padding-block: var(--smrt-spacing-6) var(--smrt-spacing-10);
59
+ }
60
+ }
61
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type MaxWidth = 'sm' | 'md' | 'lg' | 'xl' | 'full';
4
+ type Gap = 'sm' | 'md' | 'lg';
5
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class'> {
6
+ /** Maximum width of the page content. */
7
+ maxWidth?: MaxWidth;
8
+ /** Vertical space between the page's direct children. */
9
+ gap?: Gap;
10
+ /** CSS classes appended to the page content element. */
11
+ class?: string;
12
+ /** Page content. */
13
+ children: Snippet;
14
+ }
15
+ declare const PageLayout: import("svelte").Component<Props, {}, "">;
16
+ type PageLayout = ReturnType<typeof PageLayout>;
17
+ export default PageLayout;
18
+ //# sourceMappingURL=PageLayout.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageLayout.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageLayout.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AACnD,KAAK,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9B,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC1E,yCAAyC;IACzC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yDAAyD;IACzD,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AA4BD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Layout components - Page structure and containers
3
3
  */
4
+ export { default as ActionGroup } from './ActionGroup.svelte';
4
5
  export { default as Container } from './Container.svelte';
5
6
  export { default as EmptyState } from './EmptyState.svelte';
6
7
  export { default as Footer } from './Footer.svelte';
@@ -8,6 +9,7 @@ export { default as Grid } from './Grid.svelte';
8
9
  export { default as Header } from './Header.svelte';
9
10
  export { default as Masthead } from './Masthead.svelte';
10
11
  export { default as PageHeader } from './PageHeader.svelte';
12
+ export { default as PageLayout } from './PageLayout.svelte';
11
13
  export { getPageHeaderContext, type PageHeaderContext, type PageHeaderCrumb, type PageHeaderReport, setPageHeaderContext, } from './page-header-context.js';
12
14
  export { default as SummaryCard } from './SummaryCard.svelte';
13
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EACL,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EACL,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Layout components - Page structure and containers
3
3
  */
4
+ export { default as ActionGroup } from './ActionGroup.svelte';
4
5
  export { default as Container } from './Container.svelte';
5
6
  export { default as EmptyState } from './EmptyState.svelte';
6
7
  export { default as Footer } from './Footer.svelte';
@@ -8,5 +9,6 @@ export { default as Grid } from './Grid.svelte';
8
9
  export { default as Header } from './Header.svelte';
9
10
  export { default as Masthead } from './Masthead.svelte';
10
11
  export { default as PageHeader } from './PageHeader.svelte';
12
+ export { default as PageLayout } from './PageLayout.svelte';
11
13
  export { getPageHeaderContext, setPageHeaderContext, } from './page-header-context.js';
12
14
  export { default as SummaryCard } from './SummaryCard.svelte';
@@ -0,0 +1,213 @@
1
+ <script lang="ts">
2
+ import Checkbox from '../forms/Checkbox.svelte';
3
+ import Input from '../forms/Input.svelte';
4
+
5
+ export interface PermissionPickerItem {
6
+ id?: string;
7
+ slug: string;
8
+ name?: string;
9
+ description?: string;
10
+ category?: string;
11
+ }
12
+
13
+ export interface Props {
14
+ /** Permission catalog the host has authorized this actor to assign. */
15
+ permissions: readonly PermissionPickerItem[];
16
+ /** Permission slugs checked on first render or a server-validation retry. */
17
+ selected?: readonly string[];
18
+ /** Repeated native form field name. */
19
+ name?: string;
20
+ /** Visible label for the catalog. */
21
+ label?: string;
22
+ /** Supporting text below the label. */
23
+ description?: string;
24
+ /** Accessible label and placeholder for the progressive search. */
25
+ searchLabel?: string;
26
+ /** Prevents changes and native form submission while retaining readable state. */
27
+ disabled?: boolean;
28
+ }
29
+
30
+ interface PermissionGroup {
31
+ id: string;
32
+ label: string;
33
+ items: readonly PermissionPickerItem[];
34
+ }
35
+
36
+ let {
37
+ permissions,
38
+ selected = [],
39
+ name = 'permission',
40
+ label = 'Permissions',
41
+ description,
42
+ searchLabel = 'Search permissions',
43
+ disabled = false,
44
+ }: Props = $props();
45
+
46
+ let query = $state('');
47
+ let localSelection = $state.raw<{
48
+ source: readonly string[];
49
+ slugs: Set<string>;
50
+ } | null>(null);
51
+ const instanceId = $props.id();
52
+ const labelId = `permission-picker-label-${instanceId}`;
53
+ const selectedSlugs = $derived(new Set(selected));
54
+ const currentSelectedSlugs = $derived(
55
+ localSelection?.source === selected ? localSelection.slugs : selectedSlugs,
56
+ );
57
+ const normalizedQuery = $derived(normalize(query));
58
+ const groups = $derived(groupPermissions(permissions));
59
+ const visibleCount = $derived(
60
+ groups.reduce(
61
+ (count, group) =>
62
+ count +
63
+ group.items.filter((permission) => matches(permission, group.label))
64
+ .length,
65
+ 0,
66
+ ),
67
+ );
68
+
69
+ function updateSelection(slug: string, checked: boolean): void {
70
+ const next = new Set(currentSelectedSlugs);
71
+ if (checked) next.add(slug);
72
+ else next.delete(slug);
73
+ localSelection = { source: selected, slugs: next };
74
+ }
75
+
76
+ function normalize(value: string): string {
77
+ return value.trim().replace(/\s+/g, ' ').toLocaleLowerCase();
78
+ }
79
+
80
+ function words(value: string): string {
81
+ const spaced = value
82
+ .replace(/[._-]+/g, ' ')
83
+ .replace(/([a-z])([A-Z])/g, '$1 $2');
84
+ return spaced
85
+ ? spaced.charAt(0).toLocaleUpperCase() + spaced.slice(1)
86
+ : 'Other';
87
+ }
88
+
89
+ function categoryOf(permission: PermissionPickerItem): string {
90
+ const category = permission.category?.trim();
91
+ if (category) return category;
92
+ return words(permission.slug.split('.')[0] ?? '');
93
+ }
94
+
95
+ function groupPermissions(
96
+ catalog: readonly PermissionPickerItem[],
97
+ ): PermissionGroup[] {
98
+ const byCategory = new Map<string, PermissionPickerItem[]>();
99
+ for (const permission of catalog) {
100
+ const category = categoryOf(permission);
101
+ const group = byCategory.get(category) ?? [];
102
+ group.push(permission);
103
+ byCategory.set(category, group);
104
+ }
105
+ return [...byCategory.entries()]
106
+ .sort(([left], [right]) => left.localeCompare(right))
107
+ .map(([category, items], index) => ({
108
+ id: `permission-group-${index}`,
109
+ label: category,
110
+ items: [...items].sort((left, right) =>
111
+ left.slug.localeCompare(right.slug),
112
+ ),
113
+ }));
114
+ }
115
+
116
+ function displayName(permission: PermissionPickerItem): string {
117
+ return permission.name?.trim() || words(permission.slug);
118
+ }
119
+
120
+ function matches(permission: PermissionPickerItem, category: string): boolean {
121
+ if (!normalizedQuery) return true;
122
+ return normalize(
123
+ [
124
+ displayName(permission),
125
+ permission.slug,
126
+ permission.description ?? '',
127
+ category,
128
+ ].join(' '),
129
+ ).includes(normalizedQuery);
130
+ }
131
+ </script>
132
+
133
+ <section class="permission-picker" aria-labelledby={labelId}>
134
+ <div class="heading">
135
+ <div>
136
+ <h2 id={labelId}>{label}</h2>
137
+ {#if description}<p>{description}</p>{/if}
138
+ </div>
139
+ <label class="search">
140
+ <span>{searchLabel}</span>
141
+ <Input
142
+ type="search"
143
+ value={query}
144
+ oninput={(event) => (query = event.currentTarget.value)}
145
+ placeholder={searchLabel}
146
+ autocomplete="off"
147
+ />
148
+ </label>
149
+ </div>
150
+
151
+ {#key selected}
152
+ <fieldset {disabled}>
153
+ <legend class="visually-hidden">{label}</legend>
154
+ <div class="groups">
155
+ {#each groups as group (group.label)}
156
+ {@const groupVisible = group.items.some((permission) => matches(permission, group.label))}
157
+ {@const selectedCount = group.items.filter((permission) => currentSelectedSlugs.has(permission.slug)).length}
158
+ <details class="group" open={normalizedQuery ? groupVisible : undefined} hidden={!groupVisible}>
159
+ <summary id={group.id}>
160
+ <span>{group.label}</span>
161
+ <span class="selected-count">{selectedCount} selected</span>
162
+ </summary>
163
+ <div class="choices">
164
+ {#each group.items as permission (permission.id ?? permission.slug)}
165
+ <div class="choice" hidden={!matches(permission, group.label)}>
166
+ <Checkbox
167
+ {name}
168
+ value={permission.slug}
169
+ checked={selectedSlugs.has(permission.slug)}
170
+ onchange={(event) => updateSelection(permission.slug, event.currentTarget.checked)}
171
+ label={displayName(permission)}
172
+ {disabled}
173
+ />
174
+ <code>{permission.slug}</code>
175
+ {#if permission.description}<p>{permission.description}</p>{/if}
176
+ </div>
177
+ {/each}
178
+ </div>
179
+ </details>
180
+ {/each}
181
+ </div>
182
+ </fieldset>
183
+ {/key}
184
+
185
+ {#if normalizedQuery && visibleCount === 0}
186
+ <p class="no-results" role="status">No permissions match “{query.trim()}”.</p>
187
+ {/if}
188
+ </section>
189
+
190
+ <style>
191
+ .permission-picker { display: grid; gap: var(--smrt-spacing-4); min-inline-size: 0; }
192
+ .heading { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: var(--smrt-spacing-4); }
193
+ h2, p { margin: 0; }
194
+ h2 { color: var(--smrt-color-on-surface); font: var(--smrt-typography-title-large-font); }
195
+ .heading p, .choice p, code, .no-results { color: var(--smrt-color-on-surface-variant); font: var(--smrt-typography-body-small-font); }
196
+ .heading p { margin-block-start: var(--smrt-spacing-1); }
197
+ .search { display: grid; flex: 1 1 16rem; max-inline-size: 28rem; gap: var(--smrt-spacing-1); color: var(--smrt-color-on-surface); font: var(--smrt-typography-label-medium-font); }
198
+ fieldset { min-inline-size: 0; margin: 0; padding: 0; border: 0; }
199
+ .groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); gap: var(--smrt-spacing-4); align-items: start; }
200
+ .group { min-inline-size: 0; border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface); }
201
+ summary { display: flex; min-block-size: 2.75rem; align-items: center; justify-content: space-between; gap: var(--smrt-spacing-3); padding: var(--smrt-spacing-3) var(--smrt-spacing-4); color: var(--smrt-color-on-surface); font: var(--smrt-typography-title-medium-font); cursor: pointer; }
202
+ summary:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
203
+ .selected-count { flex: 0 0 auto; color: var(--smrt-color-on-surface-variant); font: var(--smrt-typography-label-medium-font); }
204
+ .choices { display: grid; gap: var(--smrt-spacing-4); padding: 0 var(--smrt-spacing-4) var(--smrt-spacing-4); border-block-start: 1px solid var(--smrt-color-outline-variant); padding-block-start: var(--smrt-spacing-4); }
205
+ .choice { display: grid; min-inline-size: 0; gap: var(--smrt-spacing-1); }
206
+ .choice :global(.checkbox) { min-inline-size: 0; }
207
+ .choice :global(.checkbox__label), code, .choice p { overflow-wrap: anywhere; }
208
+ code { padding-inline-start: calc(1.125rem + var(--smrt-spacing-2)); }
209
+ .choice p { padding-inline-start: calc(1.125rem + var(--smrt-spacing-2)); }
210
+ .no-results { padding: var(--smrt-spacing-4); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface-container-low); }
211
+ .visually-hidden { position: absolute; inline-size: 1px; block-size: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
212
+ [hidden] { display: none; }
213
+ </style>
@@ -0,0 +1,27 @@
1
+ export interface PermissionPickerItem {
2
+ id?: string;
3
+ slug: string;
4
+ name?: string;
5
+ description?: string;
6
+ category?: string;
7
+ }
8
+ export interface Props {
9
+ /** Permission catalog the host has authorized this actor to assign. */
10
+ permissions: readonly PermissionPickerItem[];
11
+ /** Permission slugs checked on first render or a server-validation retry. */
12
+ selected?: readonly string[];
13
+ /** Repeated native form field name. */
14
+ name?: string;
15
+ /** Visible label for the catalog. */
16
+ label?: string;
17
+ /** Supporting text below the label. */
18
+ description?: string;
19
+ /** Accessible label and placeholder for the progressive search. */
20
+ searchLabel?: string;
21
+ /** Prevents changes and native form submission while retaining readable state. */
22
+ disabled?: boolean;
23
+ }
24
+ declare const PermissionPicker: import("svelte").Component<Props, {}, "">;
25
+ type PermissionPicker = ReturnType<typeof PermissionPicker>;
26
+ export default PermissionPicker;
27
+ //# sourceMappingURL=PermissionPicker.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionPicker.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/permissions/PermissionPicker.svelte.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,uEAAuE;IACvE,WAAW,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA+JD,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** ISO 4217 currency metadata shared by domain packages and UI components. */
2
+ export { ISO_4217_MINOR_UNITS } from './components/display/currency-metadata.js';
3
+ //# sourceMappingURL=currency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currency.d.ts","sourceRoot":"","sources":["../src/currency.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** ISO 4217 currency metadata shared by domain packages and UI components. */
2
+ export { ISO_4217_MINOR_UNITS } from './components/display/currency-metadata.js';
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { default as MembershipCard } from './components/memberships/MembershipCa
18
18
  export { default as MembershipList } from './components/memberships/MembershipList.svelte';
19
19
  export * from './components/nav/index.js';
20
20
  export { default as PermissionCheck } from './components/permissions/PermissionCheck.svelte';
21
+ export { default as PermissionPicker } from './components/permissions/PermissionPicker.svelte';
21
22
  export { default as RoleBadge } from './components/roles/RoleBadge.svelte';
22
23
  export { default as RoleSelector } from './components/roles/RoleSelector.svelte';
23
24
  export * from './components/ui/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,UAAU,GACX,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,cAAc,gCAAgC,CAAC;AAE/C,cAAc,4BAA4B,CAAC;AAE3C,cAAc,+BAA+B,CAAC;AAE9C,cAAc,gCAAgC,CAAC;AAE/C,cAAc,8BAA8B,CAAC;AAE7C,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,gDAAgD,CAAC;AAC3F,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,gDAAgD,CAAC;AAE3F,cAAc,2BAA2B,CAAC;AAE1C,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAE7F,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAEjF,cAAc,0BAA0B,CAAC;AAEzC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AAEjC,YAAY,EAAE,WAAW,IAAI,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAElE,mBAAmB,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,UAAU,GACX,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,cAAc,gCAAgC,CAAC;AAE/C,cAAc,4BAA4B,CAAC;AAE3C,cAAc,+BAA+B,CAAC;AAE9C,cAAc,gCAAgC,CAAC;AAE/C,cAAc,8BAA8B,CAAC;AAE7C,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,gDAAgD,CAAC;AAC3F,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,gDAAgD,CAAC;AAE3F,cAAc,2BAA2B,CAAC;AAE1C,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAC7F,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,kDAAkD,CAAC;AAE/F,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAEjF,cAAc,0BAA0B,CAAC;AAEzC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AAEjC,YAAY,EAAE,WAAW,IAAI,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAElE,mBAAmB,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ export { default as MembershipList } from './components/memberships/MembershipLi
27
27
  export * from './components/nav/index.js';
28
28
  // Permission components
29
29
  export { default as PermissionCheck } from './components/permissions/PermissionCheck.svelte';
30
+ export { default as PermissionPicker } from './components/permissions/PermissionPicker.svelte';
30
31
  // Role components (domain-agnostic — typed against smrt-types contracts)
31
32
  export { default as RoleBadge } from './components/roles/RoleBadge.svelte';
32
33
  export { default as RoleSelector } from './components/roles/RoleSelector.svelte';
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import PermissionPicker from '../../components/permissions/PermissionPicker.svelte';
3
+
4
+ const permissions = [
5
+ { slug: 'projects.read', name: 'View projects', category: 'Projects' },
6
+ { slug: 'projects.update', name: 'Update projects', category: 'Projects' },
7
+ {
8
+ slug: 'tenant.settings.read',
9
+ name: 'View workspace settings',
10
+ description: 'Review the active workspace configuration.',
11
+ category: 'Workspace',
12
+ },
13
+ {
14
+ slug: 'tenant.settings.manage',
15
+ name: 'Manage workspace settings',
16
+ category: 'Workspace',
17
+ },
18
+ ];
19
+ </script>
20
+
21
+ <form>
22
+ <PermissionPicker
23
+ {permissions}
24
+ selected={['projects.read', 'tenant.settings.read']}
25
+ description="Choose the capabilities assigned to this role."
26
+ />
27
+ </form>
@@ -0,0 +1,19 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const PermissionPickerPreview: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type PermissionPickerPreview = InstanceType<typeof PermissionPickerPreview>;
18
+ export default PermissionPickerPreview;
19
+ //# sourceMappingURL=PermissionPickerPreview.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionPickerPreview.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/playground/PermissionPickerPreview.svelte.ts"],"names":[],"mappings":"AA+BA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,uBAAuB;;kBAA+E,CAAC;AAC3F,KAAK,uBAAuB,GAAG,YAAY,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAC9E,eAAe,uBAAuB,CAAC"}
@@ -2,7 +2,19 @@ declare const _default: {
2
2
  packageName: string;
3
3
  displayName: string;
4
4
  description: string;
5
- entries: {
5
+ entries: ({
6
+ id: string;
7
+ title: string;
8
+ description: string;
9
+ loadComponent: () => Promise<typeof import("./playground/PermissionPickerPreview.svelte")>;
10
+ order: number;
11
+ tags: string[];
12
+ modes: {
13
+ mock: {
14
+ label: string;
15
+ };
16
+ };
17
+ } | {
6
18
  id: string;
7
19
  title: string;
8
20
  description: string;
@@ -14,7 +26,7 @@ declare const _default: {
14
26
  label: string;
15
27
  };
16
28
  };
17
- }[];
29
+ })[];
18
30
  };
19
31
  export default _default;
20
32
  //# sourceMappingURL=playground.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"playground.d.ts","sourceRoot":"","sources":["../../src/svelte/playground.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAUA,wBA+FE"}
1
+ {"version":3,"file":"playground.d.ts","sourceRoot":"","sources":["../../src/svelte/playground.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,wBAyGE"}
@@ -5,11 +5,21 @@ const loadFeedback = () => import('./playground/FeedbackPreview.svelte');
5
5
  const loadCollections = () => import('./playground/CollectionsPreview.svelte');
6
6
  const loadCalendar = () => import('./playground/CalendarPreview.svelte');
7
7
  const loadCapture = () => import('./playground/CapturePreview.svelte');
8
+ const loadPermissionPicker = () => import('./playground/PermissionPickerPreview.svelte');
8
9
  export default {
9
10
  packageName: '@happyvertical/smrt-ui',
10
11
  displayName: 'UI Foundation',
11
12
  description: 'Provider-free controls and data primitives rendered in the active s-m-r-t theme.',
12
13
  entries: [
14
+ {
15
+ id: 'permission-picker',
16
+ title: 'Permission Picker',
17
+ description: 'Grouped, searchable native-form permission grants with retained server retry selections.',
18
+ loadComponent: loadPermissionPicker,
19
+ order: 9,
20
+ tags: ['forms', 'permissions', 'roles', 'search'],
21
+ modes: { mock: { label: 'Interactive' } },
22
+ },
13
23
  {
14
24
  id: 'code-selectors',
15
25
  title: 'Currency, Country & Province',
@@ -55,6 +55,7 @@ export { brandTheme };
55
55
  <!-- +layout.svelte -->
56
56
  <script>
57
57
  import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
58
+ import '@happyvertical/smrt-ui/themes/styles/base.css';
58
59
  import '@happyvertical/smrt-ui/themes/styles/all.css';
59
60
  import '$lib/themes/brand'; // Import to register
60
61
  </script>
@@ -244,6 +245,7 @@ Best for static sites or when you want minimal JavaScript overhead.
244
245
  <!-- +layout.svelte -->
245
246
  <script>
246
247
  // Import only the theme you need
248
+ import '@happyvertical/smrt-ui/themes/styles/base.css';
247
249
  import '@happyvertical/smrt-ui/themes/styles/material.css';
248
250
  </script>
249
251
 
@@ -276,6 +278,9 @@ For maximum performance, use CSS only with media queries:
276
278
  Import the theme styles in your app entry point:
277
279
 
278
280
  ```ts
281
+ // Recommended application sizing, surface, and typography defaults
282
+ import '@happyvertical/smrt-ui/themes/styles/base.css';
283
+
279
284
  // Import all themes (for runtime switching)
280
285
  import '@happyvertical/smrt-ui/themes/styles/all.css';
281
286
 
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Application baseline for SMRT interfaces.
3
+ *
4
+ * Import this once at an application entry point, alongside the selected
5
+ * theme stylesheet. Components remain independently usable without it.
6
+ */
7
+
8
+ *,
9
+ *::before,
10
+ *::after {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ html,
15
+ body {
16
+ min-block-size: 100%;
17
+ margin: 0;
18
+ }
19
+
20
+ body {
21
+ background: var(--smrt-color-background);
22
+ color: var(--smrt-color-on-background);
23
+ font-family: var(--smrt-font-family, Inter, system-ui, sans-serif);
24
+ }
25
+
26
+ button,
27
+ input,
28
+ select,
29
+ textarea {
30
+ font: inherit;
31
+ }
@@ -14,6 +14,8 @@
14
14
  * ```
15
15
  */
16
16
  export declare const themeStyles: {
17
+ /** Application sizing, surface, typography, and form-control baseline. */
18
+ readonly base: "./base.css";
17
19
  readonly material: "./material.css";
18
20
  readonly glass: "./glass.css";
19
21
  readonly studio: "./studio.css";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/styles/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,eAAO,MAAM,WAAW;;;;;;IAMtB,2FAA2F;;IAE3F,wFAAwF;;;CAGhF,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/styles/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,eAAO,MAAM,WAAW;IACtB,0EAA0E;;;;;;;IAO1E,2FAA2F;;IAE3F,wFAAwF;;;CAGhF,CAAC"}
@@ -16,6 +16,8 @@
16
16
  // Note: CSS imports are handled by your bundler (Vite, Webpack, etc.)
17
17
  // These exports are for TypeScript module resolution
18
18
  export const themeStyles = {
19
+ /** Application sizing, surface, typography, and form-control baseline. */
20
+ base: './base.css',
19
21
  material: './material.css',
20
22
  glass: './glass.css',
21
23
  studio: './studio.css',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-ui",
3
- "version": "0.54.4",
3
+ "version": "0.55.1",
4
4
  "smrtJsdoc": "strict",
5
5
  "description": "Domain-agnostic Svelte 5 UI runtime for SMRT: primitives, i18n client, theme system, and module UI registry",
6
6
  "type": "module",
@@ -70,6 +70,11 @@
70
70
  "import": "./dist/components/forms/form-retry/index.js",
71
71
  "default": "./dist/components/forms/form-retry/index.js"
72
72
  },
73
+ "./currency": {
74
+ "types": "./dist/currency.d.ts",
75
+ "import": "./dist/currency.js",
76
+ "default": "./dist/currency.js"
77
+ },
73
78
  "./i18n": {
74
79
  "types": "./dist/i18n/index.d.ts",
75
80
  "svelte": "./dist/i18n/index.js",
@@ -134,7 +139,7 @@
134
139
  "access": "public"
135
140
  },
136
141
  "dependencies": {
137
- "@happyvertical/smrt-types": "0.54.4",
142
+ "@happyvertical/smrt-types": "0.55.1",
138
143
  "esm-env": "^1.2.2"
139
144
  },
140
145
  "peerDependencies": {