@skra/ui 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +39 -0
- package/consumer-guide/README.md +84 -0
- package/consumer-guide/accessible-names.md +80 -0
- package/consumer-guide/forms-and-binding.md +149 -0
- package/consumer-guide/layout.md +86 -0
- package/consumer-guide/overlays.md +100 -0
- package/consumer-guide/setup-and-gotchas.md +103 -0
- package/consumer-guide/theming-and-encapsulation.md +115 -0
- package/consumer-guide/wrapping-patterns.md +90 -0
- package/fesm2022/skra-ui-accordion.mjs +333 -0
- package/fesm2022/skra-ui-accordion.mjs.map +1 -0
- package/fesm2022/skra-ui-alert.mjs +110 -0
- package/fesm2022/skra-ui-alert.mjs.map +1 -0
- package/fesm2022/skra-ui-avatar.mjs +83 -0
- package/fesm2022/skra-ui-avatar.mjs.map +1 -0
- package/fesm2022/skra-ui-badge.mjs +91 -0
- package/fesm2022/skra-ui-badge.mjs.map +1 -0
- package/fesm2022/skra-ui-button.mjs +67 -0
- package/fesm2022/skra-ui-button.mjs.map +1 -0
- package/fesm2022/skra-ui-checkbox.mjs +98 -0
- package/fesm2022/skra-ui-checkbox.mjs.map +1 -0
- package/fesm2022/skra-ui-chip.mjs +82 -0
- package/fesm2022/skra-ui-chip.mjs.map +1 -0
- package/fesm2022/skra-ui-combobox.mjs +335 -0
- package/fesm2022/skra-ui-combobox.mjs.map +1 -0
- package/fesm2022/skra-ui-core.mjs +529 -0
- package/fesm2022/skra-ui-core.mjs.map +1 -0
- package/fesm2022/skra-ui-counter.mjs +53 -0
- package/fesm2022/skra-ui-counter.mjs.map +1 -0
- package/fesm2022/skra-ui-datepicker.mjs +867 -0
- package/fesm2022/skra-ui-datepicker.mjs.map +1 -0
- package/fesm2022/skra-ui-dialog.mjs +133 -0
- package/fesm2022/skra-ui-dialog.mjs.map +1 -0
- package/fesm2022/skra-ui-field.mjs +293 -0
- package/fesm2022/skra-ui-field.mjs.map +1 -0
- package/fesm2022/skra-ui-icon.mjs +54 -0
- package/fesm2022/skra-ui-icon.mjs.map +1 -0
- package/fesm2022/skra-ui-input.mjs +42 -0
- package/fesm2022/skra-ui-input.mjs.map +1 -0
- package/fesm2022/skra-ui-kbd.mjs +44 -0
- package/fesm2022/skra-ui-kbd.mjs.map +1 -0
- package/fesm2022/skra-ui-layout.mjs +119 -0
- package/fesm2022/skra-ui-layout.mjs.map +1 -0
- package/fesm2022/skra-ui-link.mjs +29 -0
- package/fesm2022/skra-ui-link.mjs.map +1 -0
- package/fesm2022/skra-ui-loading-indicator.mjs +24 -0
- package/fesm2022/skra-ui-loading-indicator.mjs.map +1 -0
- package/fesm2022/skra-ui-menu.mjs +334 -0
- package/fesm2022/skra-ui-menu.mjs.map +1 -0
- package/fesm2022/skra-ui-multi-select.mjs +179 -0
- package/fesm2022/skra-ui-multi-select.mjs.map +1 -0
- package/fesm2022/skra-ui-native-select.mjs +123 -0
- package/fesm2022/skra-ui-native-select.mjs.map +1 -0
- package/fesm2022/skra-ui-popover.mjs +158 -0
- package/fesm2022/skra-ui-popover.mjs.map +1 -0
- package/fesm2022/skra-ui-progress.mjs +57 -0
- package/fesm2022/skra-ui-progress.mjs.map +1 -0
- package/fesm2022/skra-ui-radio.mjs +92 -0
- package/fesm2022/skra-ui-radio.mjs.map +1 -0
- package/fesm2022/skra-ui-rating-display.mjs +81 -0
- package/fesm2022/skra-ui-rating-display.mjs.map +1 -0
- package/fesm2022/skra-ui-select.mjs +151 -0
- package/fesm2022/skra-ui-select.mjs.map +1 -0
- package/fesm2022/skra-ui-skeleton.mjs +65 -0
- package/fesm2022/skra-ui-skeleton.mjs.map +1 -0
- package/fesm2022/skra-ui-star-rating.mjs +110 -0
- package/fesm2022/skra-ui-star-rating.mjs.map +1 -0
- package/fesm2022/skra-ui-status-dot.mjs +61 -0
- package/fesm2022/skra-ui-status-dot.mjs.map +1 -0
- package/fesm2022/skra-ui-switch.mjs +94 -0
- package/fesm2022/skra-ui-switch.mjs.map +1 -0
- package/fesm2022/skra-ui-tabs.mjs +356 -0
- package/fesm2022/skra-ui-tabs.mjs.map +1 -0
- package/fesm2022/skra-ui-textarea.mjs +81 -0
- package/fesm2022/skra-ui-textarea.mjs.map +1 -0
- package/fesm2022/skra-ui-toast.mjs +208 -0
- package/fesm2022/skra-ui-toast.mjs.map +1 -0
- package/fesm2022/skra-ui-tooltip.mjs +252 -0
- package/fesm2022/skra-ui-tooltip.mjs.map +1 -0
- package/fesm2022/skra-ui-typography.mjs +35 -0
- package/fesm2022/skra-ui-typography.mjs.map +1 -0
- package/fesm2022/skra-ui.mjs +4 -0
- package/fesm2022/skra-ui.mjs.map +1 -0
- package/package.json +175 -0
- package/types/skra-ui-accordion.d.ts +196 -0
- package/types/skra-ui-accordion.d.ts.map +1 -0
- package/types/skra-ui-alert.d.ts +81 -0
- package/types/skra-ui-alert.d.ts.map +1 -0
- package/types/skra-ui-avatar.d.ts +71 -0
- package/types/skra-ui-avatar.d.ts.map +1 -0
- package/types/skra-ui-badge.d.ts +73 -0
- package/types/skra-ui-badge.d.ts.map +1 -0
- package/types/skra-ui-button.d.ts +45 -0
- package/types/skra-ui-button.d.ts.map +1 -0
- package/types/skra-ui-checkbox.d.ts +52 -0
- package/types/skra-ui-checkbox.d.ts.map +1 -0
- package/types/skra-ui-chip.d.ts +60 -0
- package/types/skra-ui-chip.d.ts.map +1 -0
- package/types/skra-ui-combobox.d.ts +95 -0
- package/types/skra-ui-combobox.d.ts.map +1 -0
- package/types/skra-ui-core.d.ts +314 -0
- package/types/skra-ui-core.d.ts.map +1 -0
- package/types/skra-ui-counter.d.ts +33 -0
- package/types/skra-ui-counter.d.ts.map +1 -0
- package/types/skra-ui-datepicker.d.ts +164 -0
- package/types/skra-ui-datepicker.d.ts.map +1 -0
- package/types/skra-ui-dialog.d.ts +59 -0
- package/types/skra-ui-dialog.d.ts.map +1 -0
- package/types/skra-ui-field.d.ts +181 -0
- package/types/skra-ui-field.d.ts.map +1 -0
- package/types/skra-ui-icon.d.ts +38 -0
- package/types/skra-ui-icon.d.ts.map +1 -0
- package/types/skra-ui-input.d.ts +22 -0
- package/types/skra-ui-input.d.ts.map +1 -0
- package/types/skra-ui-kbd.d.ts +33 -0
- package/types/skra-ui-kbd.d.ts.map +1 -0
- package/types/skra-ui-layout.d.ts +86 -0
- package/types/skra-ui-layout.d.ts.map +1 -0
- package/types/skra-ui-link.d.ts +20 -0
- package/types/skra-ui-link.d.ts.map +1 -0
- package/types/skra-ui-loading-indicator.d.ts +9 -0
- package/types/skra-ui-loading-indicator.d.ts.map +1 -0
- package/types/skra-ui-menu.d.ts +116 -0
- package/types/skra-ui-menu.d.ts.map +1 -0
- package/types/skra-ui-multi-select.d.ts +47 -0
- package/types/skra-ui-multi-select.d.ts.map +1 -0
- package/types/skra-ui-native-select.d.ts +74 -0
- package/types/skra-ui-native-select.d.ts.map +1 -0
- package/types/skra-ui-popover.d.ts +57 -0
- package/types/skra-ui-popover.d.ts.map +1 -0
- package/types/skra-ui-progress.d.ts +41 -0
- package/types/skra-ui-progress.d.ts.map +1 -0
- package/types/skra-ui-radio.d.ts +54 -0
- package/types/skra-ui-radio.d.ts.map +1 -0
- package/types/skra-ui-rating-display.d.ts +56 -0
- package/types/skra-ui-rating-display.d.ts.map +1 -0
- package/types/skra-ui-select.d.ts +43 -0
- package/types/skra-ui-select.d.ts.map +1 -0
- package/types/skra-ui-skeleton.d.ts +42 -0
- package/types/skra-ui-skeleton.d.ts.map +1 -0
- package/types/skra-ui-star-rating.d.ts +71 -0
- package/types/skra-ui-star-rating.d.ts.map +1 -0
- package/types/skra-ui-status-dot.d.ts +62 -0
- package/types/skra-ui-status-dot.d.ts.map +1 -0
- package/types/skra-ui-switch.d.ts +54 -0
- package/types/skra-ui-switch.d.ts.map +1 -0
- package/types/skra-ui-tabs.d.ts +191 -0
- package/types/skra-ui-tabs.d.ts.map +1 -0
- package/types/skra-ui-textarea.d.ts +55 -0
- package/types/skra-ui-textarea.d.ts.map +1 -0
- package/types/skra-ui-toast.d.ts +140 -0
- package/types/skra-ui-toast.d.ts.map +1 -0
- package/types/skra-ui-tooltip.d.ts +90 -0
- package/types/skra-ui-tooltip.d.ts.map +1 -0
- package/types/skra-ui-typography.d.ts +17 -0
- package/types/skra-ui-typography.d.ts.map +1 -0
- package/types/skra-ui.d.ts +3 -0
- package/types/skra-ui.d.ts.map +1 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Overlays
|
|
2
|
+
|
|
3
|
+
> Load this for: opening a dialog, tooltip, popover, or menu in the native top layer, and the
|
|
4
|
+
> shadow/iframe limits. Governed by ADR 0044 (native top-layer substrate — no CDK portal, no
|
|
5
|
+
> z-index). For `select` / `combobox` / `datepicker` **value binding**, see `forms-and-binding.md`
|
|
6
|
+
> (they are value-owned overlays).
|
|
7
|
+
|
|
8
|
+
## Shared model
|
|
9
|
+
|
|
10
|
+
All Skra overlays render in the **browser top layer** via native platform APIs (`<dialog>` +
|
|
11
|
+
the Popover API), not a `document.body` portal. Consequences for you:
|
|
12
|
+
|
|
13
|
+
- **No z-index management.** Stacking is invocation-order; nesting is always correct.
|
|
14
|
+
- **Escape, light-dismiss, `::backdrop`** are platform-owned (per overlay, below).
|
|
15
|
+
- They open correctly **from inside a Shadow DOM root**; per-context limits at the bottom.
|
|
16
|
+
|
|
17
|
+
## Dialog (modal) — `dialog[skraDialog]`
|
|
18
|
+
|
|
19
|
+
A Component on the native `<dialog>`, opened with the **native** `showModal()` and closed with
|
|
20
|
+
`close()`. `role="dialog"`, `aria-modal`, Escape, the `::backdrop` scrim, scroll-lock and focus
|
|
21
|
+
containment are native:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<button skraButton (click)="dlg.showModal()">Delete…</button>
|
|
25
|
+
|
|
26
|
+
<dialog skraDialog #dlg aria-labelledby="confirm-title" size="sm">
|
|
27
|
+
<h2 id="confirm-title">Delete item?</h2>
|
|
28
|
+
<p>This can't be undone.</p>
|
|
29
|
+
<button skraButton autofocus (click)="dlg.close()">Cancel</button>
|
|
30
|
+
<button skraButton (click)="confirm(); dlg.close()">Delete</button>
|
|
31
|
+
</dialog>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- **Name it** with `aria-labelledby` → a projected heading (no `ariaLabel` input — ADR 033).
|
|
35
|
+
- **Initial focus** is the consumer-placed native `autofocus` attribute.
|
|
36
|
+
- `size` is `sm | md | lg` (ADR 035).
|
|
37
|
+
|
|
38
|
+
## Tooltip — `[skraTooltip]` on your own trigger
|
|
39
|
+
|
|
40
|
+
A supplementary, non-interactive **description** shown on hover AND focus. It is **not** the
|
|
41
|
+
accessible name — it merges into the trigger's `aria-describedby`:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<button skraButton [skraTooltip]="'Open settings'" aria-label="Settings">⚙</button>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
An icon-only trigger **still needs its own accessible name** (Path 3 — see
|
|
48
|
+
`accessible-names.md`); the tooltip never substitutes for it.
|
|
49
|
+
|
|
50
|
+
## Popover (non-modal panel) — `skra-popover`
|
|
51
|
+
|
|
52
|
+
A Component on the panel with projected interactive content. The trigger is **your own button**
|
|
53
|
+
linked by the native `popovertarget`; toggle, light-dismiss, Escape, and stacking are
|
|
54
|
+
platform-owned. **You MUST give the panel an `id`:**
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<button skraButton popovertarget="account-menu">Account</button>
|
|
58
|
+
<skra-popover id="account-menu" aria-labelledby="account-menu-title">
|
|
59
|
+
<h3 id="account-menu-title">Signed in as…</h3>
|
|
60
|
+
<!-- interactive content -->
|
|
61
|
+
</skra-popover>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Focus is **not** trapped/force-moved (non-modal). Name via `aria-labelledby` → projected
|
|
65
|
+
heading.
|
|
66
|
+
|
|
67
|
+
## Menu (commands) — `skra-menu` + `button[skraMenuItem]`
|
|
68
|
+
|
|
69
|
+
A WAI-ARIA APG menu-button: a homogeneous command set, arrow-key navigated, activating an item
|
|
70
|
+
closes it. Trigger is your own button with `popovertarget` + `aria-haspopup="menu"`; **give the
|
|
71
|
+
panel an `id`**. Focus moves **into** the menu on open:
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<button skraButton popovertarget="row-actions" aria-haspopup="menu">Actions</button>
|
|
75
|
+
<skra-menu id="row-actions">
|
|
76
|
+
<button skraMenuItem (click)="edit()">Edit</button>
|
|
77
|
+
<button skraMenuItem disabled (click)="archive()">Archive</button>
|
|
78
|
+
<button skraMenuItem (click)="remove()">Delete</button>
|
|
79
|
+
</skra-menu>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- **Side effects on open/close:** bind the **native `(toggle)`** event — there is no Skra
|
|
83
|
+
output (native-first).
|
|
84
|
+
- A right-click context menu is **not** a menu trigger mode — use a visible button (SRP +
|
|
85
|
+
WCAG 2.1.1).
|
|
86
|
+
|
|
87
|
+
## Selection overlays — value binding lives elsewhere
|
|
88
|
+
|
|
89
|
+
`skra-select`, `skra-multi-select`, `skra-combobox`, `skra-datepicker` render their listbox
|
|
90
|
+
in the top layer like the above, **but their value is consumer-owned** — bind with the
|
|
91
|
+
`[value]`/`(valueChange)` bridge documented in `forms-and-binding.md` (class B). The combobox
|
|
92
|
+
filters with consumer-owned data (ADR 0049): you own the filtered option list.
|
|
93
|
+
|
|
94
|
+
## Limits across boundaries (same as theming — recap)
|
|
95
|
+
|
|
96
|
+
- Overlays **open** from inside a Shadow DOM root (top layer + positioning cross the boundary,
|
|
97
|
+
F-10). But the global-slice pieces — `::backdrop` scrim and `html:has()` scroll-lock — need
|
|
98
|
+
the bridge / don't pierce the boundary (F-6/F-7). See `theming-and-encapsulation.md`.
|
|
99
|
+
- **iframe**: overlays are contained to the frame's own top layer — they cannot overlay the
|
|
100
|
+
parent app (F-11).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Setup & Gotchas
|
|
2
|
+
|
|
3
|
+
> Load this for: first integration, import errors, "which package exports what", and the
|
|
4
|
+
> boolean-attribute / inline-style pitfalls. Core rules live in `README.md` — this is the detail.
|
|
5
|
+
|
|
6
|
+
## Public entry points (`@skra/ui/*`)
|
|
7
|
+
|
|
8
|
+
Import **only** from these. Never a deep `libs/ui/...` path (not part of the public API;
|
|
9
|
+
will break on any internal refactor). Skra ships as **one package, `@skra/ui`**, with a
|
|
10
|
+
secondary entry point per component (ADR 0054 / O-31 consolidation). The primary `@skra/ui`
|
|
11
|
+
entry exports nothing — always import from a `@skra/ui/<entry>` subpath.
|
|
12
|
+
|
|
13
|
+
| Entry point | Exports (representative) |
|
|
14
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `@skra/ui/button` | `button[skraButton]` |
|
|
16
|
+
| `@skra/ui/link` | `a[skraLink]` |
|
|
17
|
+
| `@skra/ui/icon` | icon container atom |
|
|
18
|
+
| `@skra/ui/typography` | `[skraTypography]` |
|
|
19
|
+
| `@skra/ui/layout` | `[skraStack]`, `[skraBox]`, `[skraGrid]` |
|
|
20
|
+
| `@skra/ui/kbd` | `kbd[skraKbd]` |
|
|
21
|
+
| `@skra/ui/field` | `<skra-field>`, `[skraLabel]`, `[skraHint]`, `[skraError]`, `fieldset[skraFieldset]`, `legend[skraLegend]`, `SkraFieldContext` |
|
|
22
|
+
| `@skra/ui/input` | `input[skraInput]` |
|
|
23
|
+
| `@skra/ui/textarea` | `textarea[skraTextarea]`, auto-resize |
|
|
24
|
+
| `@skra/ui/checkbox` | `input[skraCheckbox]` |
|
|
25
|
+
| `@skra/ui/radio` | `input[skraRadio]` |
|
|
26
|
+
| `@skra/ui/switch` | `input[skraSwitch]` |
|
|
27
|
+
| `@skra/ui/counter` | `skra-counter` |
|
|
28
|
+
| `@skra/ui/star-rating` | `skra-star-rating` |
|
|
29
|
+
| `@skra/ui/native-select` | `select[skraSelect]` (`SkraNativeSelect`) |
|
|
30
|
+
| `@skra/ui/dialog` | `dialog[skraDialog]` |
|
|
31
|
+
| `@skra/ui/tooltip` | `[skraTooltip]` |
|
|
32
|
+
| `@skra/ui/popover` | `skra-popover` |
|
|
33
|
+
| `@skra/ui/menu` | `skra-menu`, `skra-menu-item` |
|
|
34
|
+
| `@skra/ui/select` | `skra-select` (+ `SkraOption` & option directives, re-exported) |
|
|
35
|
+
| `@skra/ui/multi-select` | `skra-multi-select` (+ `SkraOption` & option directives, re-exported) |
|
|
36
|
+
| `@skra/ui/combobox` | `skra-combobox` (+ `SkraOption` & option directives, re-exported) |
|
|
37
|
+
| `@skra/ui/datepicker` | `skra-datepicker` |
|
|
38
|
+
| `@skra/ui/alert` | `skra-alert` |
|
|
39
|
+
| `@skra/ui/badge` | `skra-badge` |
|
|
40
|
+
| `@skra/ui/chip` | `skra-chip` |
|
|
41
|
+
| `@skra/ui/toast` | `skra-toast`, `skra-toast-region` |
|
|
42
|
+
| `@skra/ui/tabs` | `[skraTabs]`, `[skraTablist]`, `button[skraTab]`, `[skraTabPanel]` |
|
|
43
|
+
| `@skra/ui/accordion` | `[skraAccordion]` family |
|
|
44
|
+
| `@skra/ui/progress` | `progress[skraProgress]` |
|
|
45
|
+
| `@skra/ui/skeleton` | `skra-skeleton` |
|
|
46
|
+
| `@skra/ui/loading-indicator` | `[skraLoadingIndicator]` |
|
|
47
|
+
| `@skra/ui/avatar` | `skra-avatar` |
|
|
48
|
+
| `@skra/ui/status-dot` | `skra-status-dot` |
|
|
49
|
+
| `@skra/ui/rating-display` | `skra-rating-display` |
|
|
50
|
+
|
|
51
|
+
> `@skra/ui/core` exists for cross-component shared satellites (`SkraOption` & option
|
|
52
|
+
> directives, `SkraSelectTrigger`, `SKRA_LISTBOX_PANEL`). Prefer the re-exports from
|
|
53
|
+
> `select` / `multi-select` / `combobox`; you rarely import `core` directly.
|
|
54
|
+
>
|
|
55
|
+
> `@skra/shadow-dom-bridge` is a delivery utility, not a component — see
|
|
56
|
+
> `theming-and-encapsulation.md`.
|
|
57
|
+
|
|
58
|
+
## Gotcha 1 — boolean attributes: write the bare form
|
|
59
|
+
|
|
60
|
+
Skra boolean inputs carry `{ transform: booleanAttribute }` (lint-enforced across `libs/ui`
|
|
61
|
+
by `boolean-input-validator.mjs`), so the **native bare attribute works** — write it the
|
|
62
|
+
HTML-idiomatic way:
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<skra-toast dismissible>…</skra-toast>
|
|
66
|
+
<!-- ✓ -->
|
|
67
|
+
<div skraStack wrap>…</div>
|
|
68
|
+
<!-- ✓ -->
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`[dismissible]="true"` also compiles but is unnecessary. (Historically the transform was
|
|
72
|
+
missing and the bare form broke under `strictTemplates` — F-18; closed library-wide in
|
|
73
|
+
session 10, so the workaround is no longer needed.)
|
|
74
|
+
|
|
75
|
+
## Gotcha 2 — never style with inline `style` or `[style.--var]`
|
|
76
|
+
|
|
77
|
+
Skra reflects state as `data-skra-*` attributes and maps them to tokens in CSS (ADR 021,
|
|
78
|
+
CSP-safe). To override a token for a subtree, scope it with a **CSS class or a data-attribute
|
|
79
|
+
on an ancestor**, never inline:
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<!-- ✗ CSP-violating, and not how the token cascade is meant to work -->
|
|
83
|
+
<skra-badge style="--skra-comp-badge-background: red">…</skra-badge>
|
|
84
|
+
|
|
85
|
+
<!-- ✓ scope on an ancestor, in a stylesheet -->
|
|
86
|
+
<div class="danger-zone"><skra-badge>…</skra-badge></div>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```css
|
|
90
|
+
.danger-zone {
|
|
91
|
+
--skra-comp-badge-background: var(--skra-sys-color-danger-base);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Gotcha 3 — components carry `margin: 0` (ADR 022)
|
|
96
|
+
|
|
97
|
+
Spacing between components is **never** a margin on the component. Use a layout atom
|
|
98
|
+
(`[skraStack]`/`[skraBox]`/`[skraGrid]`) — see `layout.md`.
|
|
99
|
+
|
|
100
|
+
## Gotcha 4 — don't reach for relative imports as an escape hatch
|
|
101
|
+
|
|
102
|
+
If something you need isn't on a public `@skra/ui/*` entry point, that is a finding (raise it),
|
|
103
|
+
not a reason to import from `libs/`. The public surface is the contract.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Theming & Encapsulation
|
|
2
|
+
|
|
3
|
+
> Load this for: loading the token layers, switching skin / dark mode / density at runtime,
|
|
4
|
+
> and mounting Skra under Shadow DOM / a microfrontend / an iframe. Governed by ADR 002
|
|
5
|
+
> (token cascade), ADR 042 §3.4 (value/structure split), ADR 012 §5 (delivery), ADR 036 §3.7
|
|
6
|
+
> (baseline hook), ADR 0047 (runtime-scoped skins).
|
|
7
|
+
|
|
8
|
+
## Mental model — values inherit, structure does not
|
|
9
|
+
|
|
10
|
+
Skra styling has two layers, and they behave differently across DOM boundaries:
|
|
11
|
+
|
|
12
|
+
- **Token VALUES** (`--skra-*` custom properties) — set by the theme/comp/skin artifacts.
|
|
13
|
+
They **inherit** through the DOM, **including across a Shadow DOM boundary**. This is what
|
|
14
|
+
lets a runtime skin swap re-paint a shadow subtree with zero component changes (F-9).
|
|
15
|
+
- **STRUCTURE** (selector-bearing rules: `:host`, `data-skra-*` state, slices, the variant
|
|
16
|
+
matrix) — injected as document-scoped `<style>` (Emulated, ADR 001). These do **not** cross
|
|
17
|
+
into a shadow root; inside one you need the bridge (below).
|
|
18
|
+
|
|
19
|
+
## Loading the token layers (global stylesheet)
|
|
20
|
+
|
|
21
|
+
A consumer imports the built token artifacts once, globally. **Order matters** (F-14): base →
|
|
22
|
+
comp → dark → slices → scoped skins → overlay presets (overlays last so they keep overriding
|
|
23
|
+
a skin at equal specificity):
|
|
24
|
+
|
|
25
|
+
```scss
|
|
26
|
+
/* paths shown as in the repo; an external consumer imports the equivalent published artifacts */
|
|
27
|
+
@import 'skra/_theme.scss'; // ref + sys token values (:root)
|
|
28
|
+
@import 'skra/_comp.scss'; // component token defaults
|
|
29
|
+
@import 'skra/_theme.dark.scss'; // dark-scheme value block
|
|
30
|
+
@import 'skra/_baseline.scss'; // typography/canvas baseline (needs the hook — below)
|
|
31
|
+
@import 'skra/_form-controls.scss'; // native control skin slice
|
|
32
|
+
@import 'skra/_overlay.scss'; // ::backdrop scrim + modal scroll-lock slice
|
|
33
|
+
@import 'skra/utilities.scss'; // skra-sr-only etc.
|
|
34
|
+
@import 'skra/_density.compact.scss';
|
|
35
|
+
@import 'skra/_density.comfortable.scss';
|
|
36
|
+
|
|
37
|
+
/* additional skins ship as runtime-scoped artifacts (ADR 0047), keyed to [data-skra-skin] */
|
|
38
|
+
@import 'brutal/_theme.scoped.scss';
|
|
39
|
+
@import 'brutal/_theme.dark.scoped.scss';
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> Exact import specifiers depend on how the tokens are distributed to your project (npm
|
|
43
|
+
> package vs vendored `dist/`). The layer set and order are the contract; the paths are not.
|
|
44
|
+
|
|
45
|
+
## Switching skin / scheme / density at runtime
|
|
46
|
+
|
|
47
|
+
These are **consumer-scope attributes** on an ancestor element — NOT component inputs and NOT
|
|
48
|
+
state reflection. Any subtree carrying them re-resolves its token values:
|
|
49
|
+
|
|
50
|
+
| Attribute | Values | Default (omit the attribute) |
|
|
51
|
+
| ------------------------- | -------------------------------------------------- | ---------------------------- |
|
|
52
|
+
| `data-skra-skin` | `brutal`, … (whatever scoped artifacts you loaded) | the base skin |
|
|
53
|
+
| `data-skra-color-scheme` | `dark` | light |
|
|
54
|
+
| `data-skra-density` | `compact`, `comfortable` | default density |
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<div [attr.data-skra-skin]="skin()" [attr.data-skra-color-scheme]="scheme() === 'dark' ? 'dark' : null" [attr.data-skra-density]="density() === 'default' ? null : density()">
|
|
58
|
+
<!-- Skra subtree re-paints when any of these change -->
|
|
59
|
+
</div>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Bind `null` for the default so you don't emit `data-skra-color-scheme="light"` etc. — the
|
|
63
|
+
base values live at `:root`, the attribute only _overrides_.
|
|
64
|
+
|
|
65
|
+
### The baseline hook (`data-skra-baseline`)
|
|
66
|
+
|
|
67
|
+
`_baseline.scss` (document typography + canvas background) is **inert until you add the
|
|
68
|
+
`data-skra-baseline` attribute** to the scope that should own it (ADR 036 §3.7). It is
|
|
69
|
+
**static** — always present, not toggled like the design-context attributes above:
|
|
70
|
+
|
|
71
|
+
- `<html data-skra-baseline>` — Skra owns the page (canvas bg reaches the viewport, sets the
|
|
72
|
+
rem root). Use when Skra is your whole app.
|
|
73
|
+
- `<div data-skra-baseline>` — guest/microfrontend: scopes typography + canvas to the subtree
|
|
74
|
+
only, leaving the host shell's `<html>` untouched.
|
|
75
|
+
|
|
76
|
+
## Mounting Skra inside a Shadow DOM root (microfrontend isolation)
|
|
77
|
+
|
|
78
|
+
Inside a shadow root, token **values still inherit** (skins work), but the document-scoped
|
|
79
|
+
STRUCTURE does not reach in. Adopt it with the bridge:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { registerSkraStyles } from '@skra/shadow-dom-bridge';
|
|
83
|
+
|
|
84
|
+
// after the Skra content inside the shadow root has rendered at least once
|
|
85
|
+
registerSkraStyles(shadowRoot);
|
|
86
|
+
// rebuild the cached aggregate if more Skra styles were injected since:
|
|
87
|
+
registerSkraStyles(shadowRoot, { refresh: true });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`registerSkraStyles(shadowRoot, options?)` adopts Skra's **structural** stylesheets
|
|
91
|
+
(per-component Emulated rules incl. the variant matrix, `_baseline`, `_form-controls`,
|
|
92
|
+
`utilities`) into the root. It deliberately **skips the token value layers** — those inherit,
|
|
93
|
+
and re-declaring them inside would freeze shell-driven skin morph (ADR 042 §3.4). Options:
|
|
94
|
+
`marker` (default `/skra/i`), `fallback` (`<style>` injection when constructable stylesheets
|
|
95
|
+
are unavailable, default `true`), `refresh`. There is also `unregisterSkraStyles(shadowRoot)`.
|
|
96
|
+
|
|
97
|
+
**This applies to Angular `ViewEncapsulation.ShadowDom` too**, not only foreign `attachShadow()`
|
|
98
|
+
roots: Angular replicates _component_ styles into a ShadowDom root but NOT the global slices
|
|
99
|
+
(F-12) — so without the bridge, `skra-sr-only` content renders visible (e.g. the datepicker's
|
|
100
|
+
per-day SR labels overflow the grid — F-15), `::backdrop`/scroll-lock are missing (F-7), etc.
|
|
101
|
+
|
|
102
|
+
## Limits that do NOT cross a boundary (by design)
|
|
103
|
+
|
|
104
|
+
The native top-layer substrate (ADR 0044) has per-context limits — these are architecture, not
|
|
105
|
+
bugs:
|
|
106
|
+
|
|
107
|
+
- **Modal scroll-lock** uses `html:has(dialog[skraDialog]:modal)`, and `:has()` does not pierce
|
|
108
|
+
a shadow boundary — a dialog opened from inside a shadow root does **not** lock the outer page
|
|
109
|
+
scroll (F-6). For shift-free locking when Skra owns the page, set
|
|
110
|
+
`html { scrollbar-gutter: stable }` globally.
|
|
111
|
+
- **`::backdrop` scrim** lives in the `_overlay.scss` global slice; inside a shadow root it
|
|
112
|
+
needs the bridge (F-7).
|
|
113
|
+
- **iframe** — overlays open in the **iframe's own top layer, contained to the frame** (F-11);
|
|
114
|
+
they cannot overlay the parent app. Pass design context into the frame via query params /
|
|
115
|
+
reload. This is a fundamental limit of the per-document top layer, not a defect.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Wrapping Skra in Your Own Components
|
|
2
|
+
|
|
3
|
+
> Load this for: building project-specific components on top of Skra atoms to simplify the
|
|
4
|
+
> API down to the inputs your app actually needs. Governed by the *atoms-agnostic* doctrine +
|
|
5
|
+
> ADR 037 (consumer owns value/ergonomics).
|
|
6
|
+
|
|
7
|
+
## Why wrap, and where the wrapper belongs
|
|
8
|
+
|
|
9
|
+
Skra atoms are deliberately low-level and semantic-only — they ship a11y + tokens + binding
|
|
10
|
+
seams, not app ergonomics. The intended consumption shape is: **your app wraps Skra atoms in
|
|
11
|
+
higher-level components** that expose just the inputs your domain needs.
|
|
12
|
+
|
|
13
|
+
The hard rule: **the wrapper lives in YOUR app (or a consumer library), never back inside
|
|
14
|
+
`@skra/*`.** Pulling app ergonomics into the atom breaks the agnostic contract (ADR 037). If a
|
|
15
|
+
wrapper becomes reusable across several of your apps, promote it to **your own library built ON
|
|
16
|
+
Skra** (`@yourorg/ui`), not into Skra core.
|
|
17
|
+
|
|
18
|
+
## Pattern 1 — a field wrapper (collapse the field boilerplate)
|
|
19
|
+
|
|
20
|
+
The `<skra-field>` + label + control + hint + error + binding cluster is verbose to repeat.
|
|
21
|
+
Wrap it once:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// app-text-field.component.ts (YOUR code)
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'app-text-field',
|
|
27
|
+
standalone: true,
|
|
28
|
+
imports: [SkraField, SkraInput, SkraLabelFor, SkraHint, SkraError],
|
|
29
|
+
template: `
|
|
30
|
+
<skra-field>
|
|
31
|
+
<label skraLabelFor>{{ label() }}</label>
|
|
32
|
+
<input skraInput [formField]="field()" />
|
|
33
|
+
@if (hint()) { <skra-hint>{{ hint() }}</skra-hint> }
|
|
34
|
+
@if (errorText()) { <skra-error>{{ errorText() }}</skra-error> }
|
|
35
|
+
</skra-field>
|
|
36
|
+
`,
|
|
37
|
+
})
|
|
38
|
+
export class AppTextField {
|
|
39
|
+
readonly label = input.required<string>();
|
|
40
|
+
readonly field = input.required<Field<string>>(); // your Signal Forms node type
|
|
41
|
+
readonly hint = input<string>();
|
|
42
|
+
readonly errorText = input<string>();
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<!-- call site: domain-minimal API -->
|
|
48
|
+
<app-text-field label="Email" [field]="form().email" hint="We never share it." />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**What NOT to break when wrapping:**
|
|
52
|
+
|
|
53
|
+
- Don't re-implement a11y — keep the control inside `<skra-field>` so the DI context still
|
|
54
|
+
wires `id`/`for`/`aria-describedby` (ADR 013). Don't add `aria-label` (ADR 033).
|
|
55
|
+
- Don't introduce inline styles to theme — scope tokens with a class (ADR 021).
|
|
56
|
+
- Keep the accessible-name path intact (the projected `<label skraLabelFor>` IS the name).
|
|
57
|
+
|
|
58
|
+
## Pattern 2 — a value-bridge adapter for class-B overlays
|
|
59
|
+
|
|
60
|
+
The class-B overlays (`skra-select`, `skra-datepicker`, `skra-star-rating`, …) need the
|
|
61
|
+
two-binding `[value]`/`(valueChange)` bridge (see `forms-and-binding.md`). If that recurs, the
|
|
62
|
+
sanctioned shape is a **consumer-side adapter directive** that maps a form node ↔
|
|
63
|
+
value/valueChange, so the call site goes back to one binding:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// app-control.directive.ts (YOUR interop layer — a sketch)
|
|
67
|
+
@Directive({ selector: '[appControl]', standalone: true })
|
|
68
|
+
export class AppControl<T> {
|
|
69
|
+
readonly appControl = input.required<Field<T>>();
|
|
70
|
+
// read [value] from the host element + write back on (valueChange),
|
|
71
|
+
// syncing this.appControl().value() ↔ the field node
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<skra-select [appControl]="form().country">…</skra-select>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This adapter is **yours**, in your interop layer — it must not live in the atom (ADR 037). The
|
|
80
|
+
friction journal calls this candidate `[skraControl]` (F-3).
|
|
81
|
+
|
|
82
|
+
## The open question — `@skra/angular-interop`? (resolved: not now)
|
|
83
|
+
|
|
84
|
+
If *every* Angular consumer needs the same value-bridge adapter, there is an argument that
|
|
85
|
+
Skra could ship an **optional, opt-in** `@skra/angular-interop` package — separate from the
|
|
86
|
+
agnostic atoms (atoms stay pure; interop is a voluntary layer on top). **Resolved 2026-06-25
|
|
87
|
+
(ADR 037 §7): DECLINED for now** — with one consumer and a still-iterating Signal Forms API,
|
|
88
|
+
the bridge stays consumer-owned (Pattern 2). The trigger to revisit, then via its own ADR:
|
|
89
|
+
several real consumers feeling the friction AND a stabilised Angular forms API (that ADR must
|
|
90
|
+
first resolve strategy-agnosticism — Signal Forms AND Reactive via a seam, never bless one).
|