@mystravil/hardpoint 0.2.0 → 0.2.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 (52) hide show
  1. package/CHANGELOG.md +42 -38
  2. package/LICENSE +25 -25
  3. package/README.md +67 -64
  4. package/dist/base.css +625 -625
  5. package/dist/components/AppShell.svelte +698 -698
  6. package/dist/components/Button.svelte +174 -174
  7. package/dist/components/Checkbox.svelte +235 -235
  8. package/dist/components/CheckboxGroup.svelte +207 -207
  9. package/dist/components/Combobox.svelte +901 -901
  10. package/dist/components/Dialog.svelte +359 -359
  11. package/dist/components/Disclosure.svelte +201 -201
  12. package/dist/components/EmptyState.svelte +88 -88
  13. package/dist/components/ErrorState.svelte +135 -135
  14. package/dist/components/ImageViewer.svelte +764 -764
  15. package/dist/components/KeyValueList.svelte +114 -114
  16. package/dist/components/ListDetail.svelte +218 -218
  17. package/dist/components/LoadRegion.svelte +219 -219
  18. package/dist/components/MediaFrame.svelte +110 -110
  19. package/dist/components/MediaImage.svelte +256 -256
  20. package/dist/components/Menu.svelte +258 -258
  21. package/dist/components/MenuCheckboxItem.svelte +119 -119
  22. package/dist/components/MenuGroup.svelte +50 -50
  23. package/dist/components/MenuItem.svelte +94 -94
  24. package/dist/components/MenuRadioGroup.svelte +62 -62
  25. package/dist/components/MenuRadioItem.svelte +110 -110
  26. package/dist/components/MenuSeparator.svelte +28 -28
  27. package/dist/components/Meter.svelte +283 -283
  28. package/dist/components/NativeSelect.svelte +135 -135
  29. package/dist/components/Notice.svelte +262 -262
  30. package/dist/components/Popover.svelte +222 -222
  31. package/dist/components/Progress.svelte +157 -157
  32. package/dist/components/RadioGroup.svelte +440 -440
  33. package/dist/components/Select.svelte +471 -471
  34. package/dist/components/Skeleton.svelte +149 -149
  35. package/dist/components/Spinner.svelte +118 -118
  36. package/dist/components/Stat.svelte +253 -253
  37. package/dist/components/Steps.svelte +189 -189
  38. package/dist/components/Switch.svelte +222 -222
  39. package/dist/components/Table.svelte +504 -504
  40. package/dist/components/Tabs.svelte +246 -246
  41. package/dist/components/Tag.svelte +40 -40
  42. package/dist/components/TextArea.svelte +272 -272
  43. package/dist/components/TextField.svelte +256 -256
  44. package/dist/components/Timeline.svelte +156 -156
  45. package/dist/components/Toaster.svelte +411 -411
  46. package/dist/components/Tooltip.svelte +200 -200
  47. package/dist/components/TooltipGroup.svelte +46 -46
  48. package/dist/components/internal/Scrim.svelte +52 -52
  49. package/dist/components/internal/Sheet.svelte +263 -263
  50. package/dist/fonts.css +71 -71
  51. package/dist/tokens.css +283 -283
  52. package/package.json +81 -76
@@ -1,246 +1,246 @@
1
- <script lang="ts" module>
2
- import type { Snippet } from 'svelte';
3
- import { Tabs as BitsTabs } from 'bits-ui';
4
-
5
- export type TabsItem = {
6
- /** Identifies the tab; the bound `value` holds the selected one. */
7
- value: string;
8
- /** The caps label. A short fixed string the app defines, never user data. */
9
- label: string;
10
- /** Shown in `--muted`; it cannot be selected and arrow keys skip it. */
11
- disabled?: boolean;
12
- };
13
-
14
- /** Tabs may carry extra fields (a count, an icon name) that the `panel` snippet reads. */
15
- export type TabsProps<T extends TabsItem = TabsItem> = Omit<
16
- BitsTabs.RootProps,
17
- 'value' | 'orientation' | 'child' | 'children'
18
- > & {
19
- tabs: T[];
20
- /** The selected tab's `value`. Bindable; defaults to the first tab that is not disabled. */
21
- value?: string;
22
- /** The tab list's accessible name, for example "Project sections". */
23
- label: string;
24
- /** Renders one tab's panel. Every panel stays mounted; the hidden ones carry `hidden`. */
25
- panel: Snippet<[T]>;
26
- };
27
- </script>
28
-
29
- <script lang="ts" generics="T extends TabsItem = TabsItem">
30
- import { untrack } from 'svelte';
31
- import { fadeIn } from './internal/fade-in.js';
32
-
33
- let {
34
- tabs,
35
- value = $bindable(tabs.find((tab) => !tab.disabled)?.value ?? ''),
36
- label,
37
- panel,
38
- class: className,
39
- ref = $bindable(null),
40
- onValueChange,
41
- ...rest
42
- }: TabsProps<T> = $props();
43
-
44
- let list: HTMLElement | null = $state(null);
45
-
46
- // Shows the selected tab whole inside the list, with part of the next one
47
- // (the list's scroll-padding). It scrolls the list alone, never the page
48
- // (scrollIntoView would), and jumps: selection snaps (motion rule 3).
49
- function reveal(list: HTMLElement, selected: string) {
50
- const tab = Array.from(list.children).find(
51
- (el): el is HTMLElement => el instanceof HTMLElement && el.dataset.value === selected
52
- );
53
- if (!tab) return;
54
- const style = getComputedStyle(list);
55
- // The tab's edges in the list's scroll coordinates, from rects: offsetLeft
56
- // and offsetWidth round to whole pixels. The target rounds outward, so
57
- // the padding never comes up a fraction short.
58
- const listLeft = list.getBoundingClientRect().left + list.clientLeft;
59
- const rect = tab.getBoundingClientRect();
60
- const left = rect.left - listLeft + list.scrollLeft;
61
- const start = Math.floor(left - (parseFloat(style.scrollPaddingLeft) || 0));
62
- const end = Math.ceil(left + rect.width + (parseFloat(style.scrollPaddingRight) || 0));
63
- if (start < list.scrollLeft) {
64
- list.scrollTo({ left: start, behavior: 'instant' });
65
- } else if (end > list.scrollLeft + list.clientWidth) {
66
- list.scrollTo({ left: end - list.clientWidth, behavior: 'instant' });
67
- }
68
- }
69
-
70
- // On the first render and whenever the selection changes.
71
- $effect(() => {
72
- if (list) reveal(list, value);
73
- });
74
-
75
- // Again whenever the list or a tab changes size: the web font arrives, the
76
- // screen turns, or a hidden parent (another tab panel) shows the list. The
77
- // watcher restarts whenever the set of tabs changes, not only their number,
78
- // so a tab an app swaps in for another is watched too (each tab element is
79
- // keyed by its value).
80
- $effect(() => {
81
- const el = list;
82
- const values = tabs.map((tab) => tab.value);
83
- if (!el || values.length === 0) return;
84
- const observer = new ResizeObserver(() => reveal(el, value));
85
- observer.observe(el);
86
- for (const tab of el.children) observer.observe(tab);
87
- return () => observer.disconnect();
88
- });
89
-
90
- // ---- Short motion: the panel (BRIEF, --dur-panel) ----------------------
91
-
92
- // A panel chosen by a pointer or a touch fades in from 0 over
93
- // --dur-panel. A panel chosen by a key (arrows, Home, End, Enter, Space)
94
- // or by the app through `value` cuts, so holding an arrow key through the
95
- // tabs never starts a fade (the frequency check). The input: the tab list
96
- // keeps the last one it saw, from pointerdown and keydown in the capture
97
- // phase, before Bits UI acts on them. Bits UI calls onValueChange only for
98
- // a selection the user makes, never for a `value` the app sets, so the
99
- // change notes there whether a pointer made it, and the effect below
100
- // fades the new panel once it shows. A new selection cancels a fade that
101
- // still runs. 0ms is a cut, as before the token.
102
- let input: 'pointer' | 'key' | null = null;
103
- let fadeTo: string | null = null;
104
- let fade: Animation | null = null;
105
-
106
- function changed(next: string) {
107
- fadeTo = input === 'pointer' ? next : null;
108
- onValueChange?.(next);
109
- }
110
-
111
- $effect(() => {
112
- const selected = value;
113
- untrack(() => {
114
- fade?.cancel();
115
- fade = null;
116
- const chosen = fadeTo === selected;
117
- fadeTo = null;
118
- if (!chosen || !ref) return;
119
- const shown = Array.from(ref.children).find(
120
- (el): el is HTMLElement =>
121
- el instanceof HTMLElement && el.classList.contains('hp-tabs-panel') && el.dataset.value === selected
122
- );
123
- if (shown) fade = fadeIn(shown, '--dur-panel');
124
- });
125
- });
126
- </script>
127
-
128
- <!--
129
- Bits UI Tabs, rendered through `child` snippets so every element is this
130
- component's markup and its styles stay scoped. Extra attributes, `class`
131
- and Bits UI root props (`activationMode`, `loop`, `disabled`,
132
- `onValueChange`) go to the root `div.hp-tabs`. Parts: `.hp-tabs-list`
133
- (role tablist), `.hp-tabs-tab` (role tab, with Bits UI's `data-state` and
134
- `data-value`), `.hp-tabs-panel` (role tabpanel).
135
- -->
136
- <BitsTabs.Root bind:value bind:ref onValueChange={changed} {...rest}>
137
- {#snippet child({ props })}
138
- <div {...props} class={['hp-tabs', className]}>
139
- <BitsTabs.List bind:ref={list} aria-label={label}>
140
- {#snippet child({ props })}
141
- <div
142
- {...props}
143
- class="hp-tabs-list"
144
- onpointerdowncapture={() => (input = 'pointer')}
145
- onkeydowncapture={() => (input = 'key')}
146
- >
147
- {#each tabs as tab (tab.value)}
148
- <BitsTabs.Trigger value={tab.value} disabled={tab.disabled}>
149
- {#snippet child({ props })}
150
- <button {...props} class="hp-tabs-tab hp-label hp-focus-inset">{tab.label}</button>
151
- {/snippet}
152
- </BitsTabs.Trigger>
153
- {/each}
154
- </div>
155
- {/snippet}
156
- </BitsTabs.List>
157
- {#each tabs as tab (tab.value)}
158
- <BitsTabs.Content value={tab.value}>
159
- {#snippet child({ props })}
160
- <div {...props} class="hp-tabs-panel">{@render panel(tab)}</div>
161
- {/snippet}
162
- </BitsTabs.Content>
163
- {/each}
164
- </div>
165
- {/snippet}
166
- </BitsTabs.Root>
167
-
168
- <style>
169
- /* In a flex or grid parent the tabs never grow wider than their column:
170
- the list scrolls instead of widening the page. */
171
- .hp-tabs {
172
- min-width: 0;
173
- }
174
-
175
- /* One row that scrolls sideways when the tabs do not fit. It reaches
176
- --bleed past the text edge, as the table band does, and each tab pads
177
- by --bleed, so the first label keeps the page's text edge. */
178
- .hp-tabs-list {
179
- display: flex;
180
- margin-inline: calc(-1 * var(--bleed));
181
- overflow-x: auto;
182
- overscroll-behavior-x: contain;
183
- /* Scrolling a tab into view leaves part of the next one showing. */
184
- scroll-padding-inline: var(--space-8);
185
- /* A thin bar on fine pointers, where a wheel cannot scroll sideways
186
- without one; touch swipes. */
187
- scrollbar-width: thin;
188
- }
189
- @media (pointer: coarse) {
190
- .hp-tabs-list {
191
- scrollbar-width: none;
192
- }
193
- }
194
-
195
- /* The visible box is the hit area: --control-height high (44px on
196
- coarse pointers) and never narrower than --hit-target. */
197
- .hp-tabs-tab {
198
- display: inline-flex;
199
- flex: none;
200
- align-items: center;
201
- justify-content: center;
202
- box-sizing: border-box;
203
- min-width: var(--hit-target);
204
- min-height: var(--control-height);
205
- padding: 0 var(--bleed);
206
- border: 0;
207
- background: transparent;
208
- color: var(--text);
209
- white-space: nowrap;
210
- cursor: pointer;
211
- -webkit-tap-highlight-color: transparent;
212
- }
213
-
214
- /* Selected: a 3px titanium edge on the side facing the panel and the
215
- selected tint, text unchanged. Selection snaps, so nothing on a tab
216
- transitions: a fading hover tint would also animate the tab an arrow
217
- key just left while the pointer rests on it. */
218
- .hp-tabs-tab[data-state='active'] {
219
- background: var(--surface-selected);
220
- box-shadow: inset 0 -3px 0 var(--accent);
221
- }
222
-
223
- /* Hover: fine pointers only, never on the selected tab. */
224
- @media (hover: hover) and (pointer: fine) {
225
- .hp-tabs-tab[data-state='inactive']:hover:not(:disabled) {
226
- background: var(--surface-hover);
227
- }
228
- }
229
-
230
- /* Press: one step past hover, so it reads on a phone too. */
231
- .hp-tabs-tab[data-state='inactive']:active:not(:disabled) {
232
- background: var(--surface-raised);
233
- }
234
-
235
- .hp-tabs-tab:disabled {
236
- color: var(--muted);
237
- cursor: not-allowed;
238
- }
239
-
240
- /* A panel a pointer chose fades in over --dur-panel (see the script);
241
- otherwise it cuts. Margin, not padding, keeps the panel's focus
242
- contour clear of the tabs. */
243
- .hp-tabs-panel {
244
- margin-top: var(--space-6);
245
- }
246
- </style>
1
+ <script lang="ts" module>
2
+ import type { Snippet } from 'svelte';
3
+ import { Tabs as BitsTabs } from 'bits-ui';
4
+
5
+ export type TabsItem = {
6
+ /** Identifies the tab; the bound `value` holds the selected one. */
7
+ value: string;
8
+ /** The caps label. A short fixed string the app defines, never user data. */
9
+ label: string;
10
+ /** Shown in `--muted`; it cannot be selected and arrow keys skip it. */
11
+ disabled?: boolean;
12
+ };
13
+
14
+ /** Tabs may carry extra fields (a count, an icon name) that the `panel` snippet reads. */
15
+ export type TabsProps<T extends TabsItem = TabsItem> = Omit<
16
+ BitsTabs.RootProps,
17
+ 'value' | 'orientation' | 'child' | 'children'
18
+ > & {
19
+ tabs: T[];
20
+ /** The selected tab's `value`. Bindable; defaults to the first tab that is not disabled. */
21
+ value?: string;
22
+ /** The tab list's accessible name, for example "Project sections". */
23
+ label: string;
24
+ /** Renders one tab's panel. Every panel stays mounted; the hidden ones carry `hidden`. */
25
+ panel: Snippet<[T]>;
26
+ };
27
+ </script>
28
+
29
+ <script lang="ts" generics="T extends TabsItem = TabsItem">
30
+ import { untrack } from 'svelte';
31
+ import { fadeIn } from './internal/fade-in.js';
32
+
33
+ let {
34
+ tabs,
35
+ value = $bindable(tabs.find((tab) => !tab.disabled)?.value ?? ''),
36
+ label,
37
+ panel,
38
+ class: className,
39
+ ref = $bindable(null),
40
+ onValueChange,
41
+ ...rest
42
+ }: TabsProps<T> = $props();
43
+
44
+ let list: HTMLElement | null = $state(null);
45
+
46
+ // Shows the selected tab whole inside the list, with part of the next one
47
+ // (the list's scroll-padding). It scrolls the list alone, never the page
48
+ // (scrollIntoView would), and jumps: selection snaps (motion rule 3).
49
+ function reveal(list: HTMLElement, selected: string) {
50
+ const tab = Array.from(list.children).find(
51
+ (el): el is HTMLElement => el instanceof HTMLElement && el.dataset.value === selected
52
+ );
53
+ if (!tab) return;
54
+ const style = getComputedStyle(list);
55
+ // The tab's edges in the list's scroll coordinates, from rects: offsetLeft
56
+ // and offsetWidth round to whole pixels. The target rounds outward, so
57
+ // the padding never comes up a fraction short.
58
+ const listLeft = list.getBoundingClientRect().left + list.clientLeft;
59
+ const rect = tab.getBoundingClientRect();
60
+ const left = rect.left - listLeft + list.scrollLeft;
61
+ const start = Math.floor(left - (parseFloat(style.scrollPaddingLeft) || 0));
62
+ const end = Math.ceil(left + rect.width + (parseFloat(style.scrollPaddingRight) || 0));
63
+ if (start < list.scrollLeft) {
64
+ list.scrollTo({ left: start, behavior: 'instant' });
65
+ } else if (end > list.scrollLeft + list.clientWidth) {
66
+ list.scrollTo({ left: end - list.clientWidth, behavior: 'instant' });
67
+ }
68
+ }
69
+
70
+ // On the first render and whenever the selection changes.
71
+ $effect(() => {
72
+ if (list) reveal(list, value);
73
+ });
74
+
75
+ // Again whenever the list or a tab changes size: the web font arrives, the
76
+ // screen turns, or a hidden parent (another tab panel) shows the list. The
77
+ // watcher restarts whenever the set of tabs changes, not only their number,
78
+ // so a tab an app swaps in for another is watched too (each tab element is
79
+ // keyed by its value).
80
+ $effect(() => {
81
+ const el = list;
82
+ const values = tabs.map((tab) => tab.value);
83
+ if (!el || values.length === 0) return;
84
+ const observer = new ResizeObserver(() => reveal(el, value));
85
+ observer.observe(el);
86
+ for (const tab of el.children) observer.observe(tab);
87
+ return () => observer.disconnect();
88
+ });
89
+
90
+ // ---- Short motion: the panel (BRIEF, --dur-panel) ----------------------
91
+
92
+ // A panel chosen by a pointer or a touch fades in from 0 over
93
+ // --dur-panel. A panel chosen by a key (arrows, Home, End, Enter, Space)
94
+ // or by the app through `value` cuts, so holding an arrow key through the
95
+ // tabs never starts a fade (the frequency check). The input: the tab list
96
+ // keeps the last one it saw, from pointerdown and keydown in the capture
97
+ // phase, before Bits UI acts on them. Bits UI calls onValueChange only for
98
+ // a selection the user makes, never for a `value` the app sets, so the
99
+ // change notes there whether a pointer made it, and the effect below
100
+ // fades the new panel once it shows. A new selection cancels a fade that
101
+ // still runs. 0ms is a cut, as before the token.
102
+ let input: 'pointer' | 'key' | null = null;
103
+ let fadeTo: string | null = null;
104
+ let fade: Animation | null = null;
105
+
106
+ function changed(next: string) {
107
+ fadeTo = input === 'pointer' ? next : null;
108
+ onValueChange?.(next);
109
+ }
110
+
111
+ $effect(() => {
112
+ const selected = value;
113
+ untrack(() => {
114
+ fade?.cancel();
115
+ fade = null;
116
+ const chosen = fadeTo === selected;
117
+ fadeTo = null;
118
+ if (!chosen || !ref) return;
119
+ const shown = Array.from(ref.children).find(
120
+ (el): el is HTMLElement =>
121
+ el instanceof HTMLElement && el.classList.contains('hp-tabs-panel') && el.dataset.value === selected
122
+ );
123
+ if (shown) fade = fadeIn(shown, '--dur-panel');
124
+ });
125
+ });
126
+ </script>
127
+
128
+ <!--
129
+ Bits UI Tabs, rendered through `child` snippets so every element is this
130
+ component's markup and its styles stay scoped. Extra attributes, `class`
131
+ and Bits UI root props (`activationMode`, `loop`, `disabled`,
132
+ `onValueChange`) go to the root `div.hp-tabs`. Parts: `.hp-tabs-list`
133
+ (role tablist), `.hp-tabs-tab` (role tab, with Bits UI's `data-state` and
134
+ `data-value`), `.hp-tabs-panel` (role tabpanel).
135
+ -->
136
+ <BitsTabs.Root bind:value bind:ref onValueChange={changed} {...rest}>
137
+ {#snippet child({ props })}
138
+ <div {...props} class={['hp-tabs', className]}>
139
+ <BitsTabs.List bind:ref={list} aria-label={label}>
140
+ {#snippet child({ props })}
141
+ <div
142
+ {...props}
143
+ class="hp-tabs-list"
144
+ onpointerdowncapture={() => (input = 'pointer')}
145
+ onkeydowncapture={() => (input = 'key')}
146
+ >
147
+ {#each tabs as tab (tab.value)}
148
+ <BitsTabs.Trigger value={tab.value} disabled={tab.disabled}>
149
+ {#snippet child({ props })}
150
+ <button {...props} class="hp-tabs-tab hp-label hp-focus-inset">{tab.label}</button>
151
+ {/snippet}
152
+ </BitsTabs.Trigger>
153
+ {/each}
154
+ </div>
155
+ {/snippet}
156
+ </BitsTabs.List>
157
+ {#each tabs as tab (tab.value)}
158
+ <BitsTabs.Content value={tab.value}>
159
+ {#snippet child({ props })}
160
+ <div {...props} class="hp-tabs-panel">{@render panel(tab)}</div>
161
+ {/snippet}
162
+ </BitsTabs.Content>
163
+ {/each}
164
+ </div>
165
+ {/snippet}
166
+ </BitsTabs.Root>
167
+
168
+ <style>
169
+ /* In a flex or grid parent the tabs never grow wider than their column:
170
+ the list scrolls instead of widening the page. */
171
+ .hp-tabs {
172
+ min-width: 0;
173
+ }
174
+
175
+ /* One row that scrolls sideways when the tabs do not fit. It reaches
176
+ --bleed past the text edge, as the table band does, and each tab pads
177
+ by --bleed, so the first label keeps the page's text edge. */
178
+ .hp-tabs-list {
179
+ display: flex;
180
+ margin-inline: calc(-1 * var(--bleed));
181
+ overflow-x: auto;
182
+ overscroll-behavior-x: contain;
183
+ /* Scrolling a tab into view leaves part of the next one showing. */
184
+ scroll-padding-inline: var(--space-8);
185
+ /* A thin bar on fine pointers, where a wheel cannot scroll sideways
186
+ without one; touch swipes. */
187
+ scrollbar-width: thin;
188
+ }
189
+ @media (pointer: coarse) {
190
+ .hp-tabs-list {
191
+ scrollbar-width: none;
192
+ }
193
+ }
194
+
195
+ /* The visible box is the hit area: --control-height high (44px on
196
+ coarse pointers) and never narrower than --hit-target. */
197
+ .hp-tabs-tab {
198
+ display: inline-flex;
199
+ flex: none;
200
+ align-items: center;
201
+ justify-content: center;
202
+ box-sizing: border-box;
203
+ min-width: var(--hit-target);
204
+ min-height: var(--control-height);
205
+ padding: 0 var(--bleed);
206
+ border: 0;
207
+ background: transparent;
208
+ color: var(--text);
209
+ white-space: nowrap;
210
+ cursor: pointer;
211
+ -webkit-tap-highlight-color: transparent;
212
+ }
213
+
214
+ /* Selected: a 3px titanium edge on the side facing the panel and the
215
+ selected tint, text unchanged. Selection snaps, so nothing on a tab
216
+ transitions: a fading hover tint would also animate the tab an arrow
217
+ key just left while the pointer rests on it. */
218
+ .hp-tabs-tab[data-state='active'] {
219
+ background: var(--surface-selected);
220
+ box-shadow: inset 0 -3px 0 var(--accent);
221
+ }
222
+
223
+ /* Hover: fine pointers only, never on the selected tab. */
224
+ @media (hover: hover) and (pointer: fine) {
225
+ .hp-tabs-tab[data-state='inactive']:hover:not(:disabled) {
226
+ background: var(--surface-hover);
227
+ }
228
+ }
229
+
230
+ /* Press: one step past hover, so it reads on a phone too. */
231
+ .hp-tabs-tab[data-state='inactive']:active:not(:disabled) {
232
+ background: var(--surface-raised);
233
+ }
234
+
235
+ .hp-tabs-tab:disabled {
236
+ color: var(--muted);
237
+ cursor: not-allowed;
238
+ }
239
+
240
+ /* A panel a pointer chose fades in over --dur-panel (see the script);
241
+ otherwise it cuts. Margin, not padding, keeps the panel's focus
242
+ contour clear of the tabs. */
243
+ .hp-tabs-panel {
244
+ margin-top: var(--space-6);
245
+ }
246
+ </style>
@@ -1,40 +1,40 @@
1
- <script lang="ts" module>
2
- import type { Snippet } from 'svelte';
3
- import type { HTMLAttributes } from 'svelte/elements';
4
-
5
- /** The elements a tag renders as. Headings for a tag that names a section. */
6
- export type TagElement = 'p' | 'span' | 'div' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
7
-
8
- export type TagProps = HTMLAttributes<HTMLElement> & {
9
- /**
10
- * The element to render: `p` (default), or a heading when the tag names
11
- * a section, with an `id` the section's `aria-labelledby` points at.
12
- */
13
- as?: TagElement;
14
- children?: Snippet;
15
- };
16
- </script>
17
-
18
- <script lang="ts">
19
- let { as = 'p', class: className, children, ...rest }: TagProps = $props();
20
- </script>
21
-
22
- <!--
23
- A small solid titanium block with black caps (BRIEF, visual rule 2). A tag
24
- may sit above a block to name it (da3-51); it sets no outer margin, so the
25
- layout spaces it.
26
- -->
27
- <svelte:element this={as} {...rest} class={['hp-tag', 'hp-label', className]}>{@render children?.()}</svelte:element>
28
-
29
- <style>
30
- /* The caps type comes from .hp-label. The block never stretches to its
31
- container, in block, grid and flex layouts alike. */
32
- .hp-tag {
33
- justify-self: start;
34
- width: fit-content;
35
- /* matches the approved specimen */
36
- padding: 2px 6px;
37
- background: var(--accent);
38
- color: var(--canvas);
39
- }
40
- </style>
1
+ <script lang="ts" module>
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ /** The elements a tag renders as. Headings for a tag that names a section. */
6
+ export type TagElement = 'p' | 'span' | 'div' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
7
+
8
+ export type TagProps = HTMLAttributes<HTMLElement> & {
9
+ /**
10
+ * The element to render: `p` (default), or a heading when the tag names
11
+ * a section, with an `id` the section's `aria-labelledby` points at.
12
+ */
13
+ as?: TagElement;
14
+ children?: Snippet;
15
+ };
16
+ </script>
17
+
18
+ <script lang="ts">
19
+ let { as = 'p', class: className, children, ...rest }: TagProps = $props();
20
+ </script>
21
+
22
+ <!--
23
+ A small solid titanium block with black caps (BRIEF, visual rule 2). A tag
24
+ may sit above a block to name it (da3-51); it sets no outer margin, so the
25
+ layout spaces it.
26
+ -->
27
+ <svelte:element this={as} {...rest} class={['hp-tag', 'hp-label', className]}>{@render children?.()}</svelte:element>
28
+
29
+ <style>
30
+ /* The caps type comes from .hp-label. The block never stretches to its
31
+ container, in block, grid and flex layouts alike. */
32
+ .hp-tag {
33
+ justify-self: start;
34
+ width: fit-content;
35
+ /* matches the approved specimen */
36
+ padding: 2px 6px;
37
+ background: var(--accent);
38
+ color: var(--canvas);
39
+ }
40
+ </style>