@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.
- package/AGENTS.md +1 -0
- package/README.md +38 -4
- package/dist/components/layout/ActionGroup.svelte +55 -0
- package/dist/components/layout/ActionGroup.svelte.d.ts +18 -0
- package/dist/components/layout/ActionGroup.svelte.d.ts.map +1 -0
- package/dist/components/layout/Container.svelte +1 -0
- package/dist/components/layout/Grid.svelte +2 -1
- package/dist/components/layout/PageHeader.svelte +3 -3
- package/dist/components/layout/PageHeader.svelte.d.ts +3 -3
- package/dist/components/layout/PageLayout.svelte +61 -0
- package/dist/components/layout/PageLayout.svelte.d.ts +18 -0
- package/dist/components/layout/PageLayout.svelte.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +2 -0
- package/dist/components/permissions/PermissionPicker.svelte +213 -0
- package/dist/components/permissions/PermissionPicker.svelte.d.ts +27 -0
- package/dist/components/permissions/PermissionPicker.svelte.d.ts.map +1 -0
- package/dist/currency.d.ts +3 -0
- package/dist/currency.d.ts.map +1 -0
- package/dist/currency.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/svelte/playground/PermissionPickerPreview.svelte +27 -0
- package/dist/svelte/playground/PermissionPickerPreview.svelte.d.ts +19 -0
- package/dist/svelte/playground/PermissionPickerPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts +14 -2
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +10 -0
- package/dist/themes/README.md +5 -0
- package/dist/themes/styles/base.css +31 -0
- package/dist/themes/styles/index.d.ts +2 -0
- package/dist/themes/styles/index.d.ts.map +1 -1
- package/dist/themes/styles/index.js +2 -0
- 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
|
|
100
|
-
|
|
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"}
|
|
@@ -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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"currency.d.ts","sourceRoot":"","sources":["../src/currency.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC"}
|
package/dist/currency.js
ADDED
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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":"
|
|
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',
|
package/dist/themes/README.md
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
142
|
+
"@happyvertical/smrt-types": "0.55.1",
|
|
138
143
|
"esm-env": "^1.2.2"
|
|
139
144
|
},
|
|
140
145
|
"peerDependencies": {
|