@mystravil/hardpoint 0.2.0 → 0.3.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/CHANGELOG.md +51 -38
- package/LICENSE +25 -25
- package/README.md +67 -64
- package/dist/base.css +625 -625
- package/dist/components/AppShell.svelte +698 -698
- package/dist/components/Button.svelte +221 -174
- package/dist/components/Button.svelte.d.ts +18 -2
- package/dist/components/Checkbox.svelte +235 -235
- package/dist/components/CheckboxGroup.svelte +207 -207
- package/dist/components/Combobox.svelte +901 -901
- package/dist/components/Dialog.svelte +359 -359
- package/dist/components/Disclosure.svelte +201 -201
- package/dist/components/EmptyState.svelte +88 -88
- package/dist/components/ErrorState.svelte +135 -135
- package/dist/components/ImageViewer.svelte +764 -764
- package/dist/components/KeyValueList.svelte +114 -114
- package/dist/components/ListDetail.svelte +218 -218
- package/dist/components/LoadRegion.svelte +219 -219
- package/dist/components/MediaFrame.svelte +110 -110
- package/dist/components/MediaImage.svelte +256 -256
- package/dist/components/Menu.svelte +275 -258
- package/dist/components/MenuCheckboxItem.svelte +119 -119
- package/dist/components/MenuGroup.svelte +50 -50
- package/dist/components/MenuItem.svelte +94 -94
- package/dist/components/MenuRadioGroup.svelte +62 -62
- package/dist/components/MenuRadioItem.svelte +110 -110
- package/dist/components/MenuSeparator.svelte +28 -28
- package/dist/components/Meter.svelte +283 -283
- package/dist/components/NativeSelect.svelte +135 -135
- package/dist/components/Notice.svelte +262 -262
- package/dist/components/Popover.svelte +222 -222
- package/dist/components/Progress.svelte +157 -157
- package/dist/components/RadioGroup.svelte +440 -440
- package/dist/components/Select.svelte +471 -471
- package/dist/components/Skeleton.svelte +149 -149
- package/dist/components/Spinner.svelte +118 -118
- package/dist/components/Stat.svelte +253 -253
- package/dist/components/Steps.svelte +189 -189
- package/dist/components/Switch.svelte +222 -222
- package/dist/components/Table.svelte +504 -504
- package/dist/components/Tabs.svelte +246 -246
- package/dist/components/Tag.svelte +40 -40
- package/dist/components/TextArea.svelte +272 -272
- package/dist/components/TextField.svelte +396 -256
- package/dist/components/TextField.svelte.d.ts +16 -1
- package/dist/components/Timeline.svelte +156 -156
- package/dist/components/Toaster.svelte +411 -411
- package/dist/components/Tooltip.svelte +200 -200
- package/dist/components/TooltipGroup.svelte +46 -46
- package/dist/components/internal/Scrim.svelte +52 -52
- package/dist/components/internal/Sheet.svelte +263 -263
- package/dist/fonts.css +71 -71
- package/dist/tokens.css +283 -283
- package/package.json +81 -76
|
@@ -1,201 +1,201 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import type { Collapsible } from 'bits-ui';
|
|
4
|
-
|
|
5
|
-
export type DisclosureTitleElement = 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Attributes and handlers not listed here (`id`, `aria-*`, `data-*`,
|
|
9
|
-
* `onOpenChange`) go to the root element.
|
|
10
|
-
*/
|
|
11
|
-
export type DisclosureProps = Omit<Collapsible.RootProps, 'child' | 'children' | 'open' | 'ref' | 'title'> & {
|
|
12
|
-
/** The trigger's text, as written. It never takes caps. */
|
|
13
|
-
title: string;
|
|
14
|
-
/** Whether the content shows. Bindable. */
|
|
15
|
-
open?: boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Wraps the trigger in a heading of this level, so the section shows
|
|
18
|
-
* in a screen reader's heading list. Pick the level that fits the
|
|
19
|
-
* page's outline. Without it the trigger stands alone.
|
|
20
|
-
*/
|
|
21
|
-
titleAs?: DisclosureTitleElement;
|
|
22
|
-
disabled?: boolean;
|
|
23
|
-
/** The root element. */
|
|
24
|
-
ref?: HTMLDivElement | null;
|
|
25
|
-
/** The content. */
|
|
26
|
-
children?: Snippet;
|
|
27
|
-
};
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<script lang="ts">
|
|
31
|
-
import { untrack } from 'svelte';
|
|
32
|
-
import { Collapsible as BitsCollapsible } from 'bits-ui';
|
|
33
|
-
import { duration } from '../motion/transitions.js';
|
|
34
|
-
|
|
35
|
-
let {
|
|
36
|
-
title,
|
|
37
|
-
open = $bindable(false),
|
|
38
|
-
titleAs,
|
|
39
|
-
disabled = false,
|
|
40
|
-
ref = $bindable(null),
|
|
41
|
-
class: className,
|
|
42
|
-
children,
|
|
43
|
-
...rest
|
|
44
|
-
}: DisclosureProps = $props();
|
|
45
|
-
|
|
46
|
-
let body = $state<HTMLDivElement>();
|
|
47
|
-
/** The last `open` this component saw, so only a change fades. */
|
|
48
|
-
let was = untrack(() => open);
|
|
49
|
-
|
|
50
|
-
// Height snaps: the content is there at its full size on the first
|
|
51
|
-
// frame, and its text fades in. Opacity only, so it also runs under
|
|
52
|
-
// reduced motion. Never on first render: `was` starts at the first
|
|
53
|
-
// value of `open`. The duration and the curve are read now, so a token
|
|
54
|
-
// retuned on <html> applies. A fade that is still running when the
|
|
55
|
-
// section closes is cancelled, so closing is a cut.
|
|
56
|
-
$effect(() => {
|
|
57
|
-
const now = open;
|
|
58
|
-
if (now === was) return;
|
|
59
|
-
was = now;
|
|
60
|
-
untrack(() => {
|
|
61
|
-
if (!body) return;
|
|
62
|
-
for (const a of body.getAnimations()) a.cancel();
|
|
63
|
-
if (!now) return;
|
|
64
|
-
const ms = duration(body, '--dur-expand');
|
|
65
|
-
if (ms <= 0) return;
|
|
66
|
-
const settle = getComputedStyle(body).getPropertyValue('--settle').trim();
|
|
67
|
-
body.animate(
|
|
68
|
-
{ opacity: [0, 1] },
|
|
69
|
-
{ duration: ms, easing: CSS.supports('transition-timing-function', settle) ? settle : 'ease-out' }
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
</script>
|
|
74
|
-
|
|
75
|
-
{#snippet trigger()}
|
|
76
|
-
<BitsCollapsible.Trigger class="hp-disclosure-trigger hp-dense">{title}</BitsCollapsible.Trigger>
|
|
77
|
-
{/snippet}
|
|
78
|
-
|
|
79
|
-
<!--
|
|
80
|
-
One expandable section on Bits UI Collapsible. The trigger is a button
|
|
81
|
-
with aria-expanded and aria-controls (the WAI disclosure pattern); with
|
|
82
|
-
`titleAs` it sits inside a heading. Opening snaps the height and fades
|
|
83
|
-
the content in over --dur-expand; closing cuts. Closed content stays in
|
|
84
|
-
the document as hidden="until-found", so a browser that supports it finds
|
|
85
|
-
text in it and opens the section (Bits UI's beforematch handler). A browser
|
|
86
|
-
without it treats the attribute as plain hidden: the closed content is
|
|
87
|
-
not searchable, and the trigger is the only way in. The attribute is set
|
|
88
|
-
here, not by Bits UI, so closing hides the content in the same frame.
|
|
89
|
-
-->
|
|
90
|
-
<BitsCollapsible.Root bind:open bind:ref {disabled} class={['hp-disclosure', className]} {...rest}>
|
|
91
|
-
{#if titleAs}
|
|
92
|
-
<svelte:element this={titleAs} class="heading">{@render trigger()}</svelte:element>
|
|
93
|
-
{:else}
|
|
94
|
-
{@render trigger()}
|
|
95
|
-
{/if}
|
|
96
|
-
<BitsCollapsible.Content hiddenUntilFound>
|
|
97
|
-
{#snippet child({ props })}
|
|
98
|
-
<div {...props} hidden={open ? undefined : 'until-found'} class="hp-disclosure-content">
|
|
99
|
-
<div class="body" bind:this={body}>
|
|
100
|
-
{@render children?.()}
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
{/snippet}
|
|
104
|
-
</BitsCollapsible.Content>
|
|
105
|
-
</BitsCollapsible.Root>
|
|
106
|
-
|
|
107
|
-
<style>
|
|
108
|
-
/* Stacked disclosures sit 8px apart. No dividers: structure from
|
|
109
|
-
alignment. */
|
|
110
|
-
:global(.hp-disclosure + .hp-disclosure) {
|
|
111
|
-
margin-top: var(--space-2);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.heading {
|
|
115
|
-
margin: 0;
|
|
116
|
-
font: inherit;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/* A full-width row, title at the start, chevron at the end. The hover
|
|
120
|
-
tint and the focus contour reach --bleed past the text edge, as table
|
|
121
|
-
bands do, and the padding keeps the title on the page edge. It wraps
|
|
122
|
-
a long title; it never truncates. */
|
|
123
|
-
:global(.hp-disclosure-trigger) {
|
|
124
|
-
display: flex;
|
|
125
|
-
align-items: center;
|
|
126
|
-
justify-content: space-between;
|
|
127
|
-
gap: var(--space-4);
|
|
128
|
-
box-sizing: border-box;
|
|
129
|
-
width: calc(100% + 2 * var(--bleed));
|
|
130
|
-
min-height: var(--control-height);
|
|
131
|
-
margin: 0 calc(-1 * var(--bleed));
|
|
132
|
-
padding: var(--space-2) var(--bleed);
|
|
133
|
-
border: 0;
|
|
134
|
-
background: transparent;
|
|
135
|
-
color: var(--text);
|
|
136
|
-
font-family: var(--sans);
|
|
137
|
-
font-weight: 500;
|
|
138
|
-
text-align: start;
|
|
139
|
-
overflow-wrap: anywhere;
|
|
140
|
-
cursor: pointer;
|
|
141
|
-
touch-action: manipulation;
|
|
142
|
-
-webkit-tap-highlight-color: transparent;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/* The select well's chevron: the same mask, 16px, --muted; it points up
|
|
146
|
-
while open, with the list's timing (--dur-pop opening, --dur-pop-exit
|
|
147
|
-
closing) and no turn under reduced motion. */
|
|
148
|
-
:global(.hp-disclosure-trigger::after) {
|
|
149
|
-
content: '';
|
|
150
|
-
flex: none;
|
|
151
|
-
width: 16px;
|
|
152
|
-
height: 16px;
|
|
153
|
-
background-color: var(--muted);
|
|
154
|
-
mask: var(--select-chevron) center / 16px 16px no-repeat;
|
|
155
|
-
transition: transform var(--dur-pop-exit) var(--settle);
|
|
156
|
-
}
|
|
157
|
-
:global(.hp-disclosure-trigger[aria-expanded='true']::after) {
|
|
158
|
-
transform: rotate(180deg);
|
|
159
|
-
transition-duration: var(--dur-pop);
|
|
160
|
-
}
|
|
161
|
-
@media (prefers-reduced-motion: reduce) {
|
|
162
|
-
:global(.hp-disclosure-trigger::after) {
|
|
163
|
-
transition: none;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/* Hover: fine pointers only. The tint alone barely shows (1.03:1), so the
|
|
168
|
-
title also underlines, as a tertiary Button's does. */
|
|
169
|
-
@media (hover: hover) and (pointer: fine) {
|
|
170
|
-
:global(.hp-disclosure-trigger) {
|
|
171
|
-
transition: background-color var(--dur-hover) var(--settle);
|
|
172
|
-
}
|
|
173
|
-
:global(.hp-disclosure-trigger:active:not(:disabled)) {
|
|
174
|
-
transition-duration: var(--dur-press);
|
|
175
|
-
}
|
|
176
|
-
:global(.hp-disclosure-trigger:hover:not(:disabled)) {
|
|
177
|
-
background-color: var(--surface-hover);
|
|
178
|
-
text-decoration: underline;
|
|
179
|
-
text-decoration-thickness: 1px;
|
|
180
|
-
text-underline-offset: 0.3em;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
/* One step past the hover tint, so a press reads on a phone too. */
|
|
184
|
-
:global(.hp-disclosure-trigger:active:not(:disabled)) {
|
|
185
|
-
background-color: var(--surface-raised);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
:global(.hp-disclosure-trigger:disabled) {
|
|
189
|
-
color: var(--muted);
|
|
190
|
-
cursor: not-allowed;
|
|
191
|
-
}
|
|
192
|
-
:global(.hp-disclosure-trigger:disabled::after) {
|
|
193
|
-
background-color: var(--border);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/* The padding sits on the inner box: closed content is hidden, and a
|
|
197
|
-
hidden box still shows its own padding. */
|
|
198
|
-
.body {
|
|
199
|
-
padding: var(--space-1) 0 var(--space-3);
|
|
200
|
-
}
|
|
201
|
-
</style>
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { Collapsible } from 'bits-ui';
|
|
4
|
+
|
|
5
|
+
export type DisclosureTitleElement = 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Attributes and handlers not listed here (`id`, `aria-*`, `data-*`,
|
|
9
|
+
* `onOpenChange`) go to the root element.
|
|
10
|
+
*/
|
|
11
|
+
export type DisclosureProps = Omit<Collapsible.RootProps, 'child' | 'children' | 'open' | 'ref' | 'title'> & {
|
|
12
|
+
/** The trigger's text, as written. It never takes caps. */
|
|
13
|
+
title: string;
|
|
14
|
+
/** Whether the content shows. Bindable. */
|
|
15
|
+
open?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Wraps the trigger in a heading of this level, so the section shows
|
|
18
|
+
* in a screen reader's heading list. Pick the level that fits the
|
|
19
|
+
* page's outline. Without it the trigger stands alone.
|
|
20
|
+
*/
|
|
21
|
+
titleAs?: DisclosureTitleElement;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/** The root element. */
|
|
24
|
+
ref?: HTMLDivElement | null;
|
|
25
|
+
/** The content. */
|
|
26
|
+
children?: Snippet;
|
|
27
|
+
};
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<script lang="ts">
|
|
31
|
+
import { untrack } from 'svelte';
|
|
32
|
+
import { Collapsible as BitsCollapsible } from 'bits-ui';
|
|
33
|
+
import { duration } from '../motion/transitions.js';
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
title,
|
|
37
|
+
open = $bindable(false),
|
|
38
|
+
titleAs,
|
|
39
|
+
disabled = false,
|
|
40
|
+
ref = $bindable(null),
|
|
41
|
+
class: className,
|
|
42
|
+
children,
|
|
43
|
+
...rest
|
|
44
|
+
}: DisclosureProps = $props();
|
|
45
|
+
|
|
46
|
+
let body = $state<HTMLDivElement>();
|
|
47
|
+
/** The last `open` this component saw, so only a change fades. */
|
|
48
|
+
let was = untrack(() => open);
|
|
49
|
+
|
|
50
|
+
// Height snaps: the content is there at its full size on the first
|
|
51
|
+
// frame, and its text fades in. Opacity only, so it also runs under
|
|
52
|
+
// reduced motion. Never on first render: `was` starts at the first
|
|
53
|
+
// value of `open`. The duration and the curve are read now, so a token
|
|
54
|
+
// retuned on <html> applies. A fade that is still running when the
|
|
55
|
+
// section closes is cancelled, so closing is a cut.
|
|
56
|
+
$effect(() => {
|
|
57
|
+
const now = open;
|
|
58
|
+
if (now === was) return;
|
|
59
|
+
was = now;
|
|
60
|
+
untrack(() => {
|
|
61
|
+
if (!body) return;
|
|
62
|
+
for (const a of body.getAnimations()) a.cancel();
|
|
63
|
+
if (!now) return;
|
|
64
|
+
const ms = duration(body, '--dur-expand');
|
|
65
|
+
if (ms <= 0) return;
|
|
66
|
+
const settle = getComputedStyle(body).getPropertyValue('--settle').trim();
|
|
67
|
+
body.animate(
|
|
68
|
+
{ opacity: [0, 1] },
|
|
69
|
+
{ duration: ms, easing: CSS.supports('transition-timing-function', settle) ? settle : 'ease-out' }
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
{#snippet trigger()}
|
|
76
|
+
<BitsCollapsible.Trigger class="hp-disclosure-trigger hp-dense">{title}</BitsCollapsible.Trigger>
|
|
77
|
+
{/snippet}
|
|
78
|
+
|
|
79
|
+
<!--
|
|
80
|
+
One expandable section on Bits UI Collapsible. The trigger is a button
|
|
81
|
+
with aria-expanded and aria-controls (the WAI disclosure pattern); with
|
|
82
|
+
`titleAs` it sits inside a heading. Opening snaps the height and fades
|
|
83
|
+
the content in over --dur-expand; closing cuts. Closed content stays in
|
|
84
|
+
the document as hidden="until-found", so a browser that supports it finds
|
|
85
|
+
text in it and opens the section (Bits UI's beforematch handler). A browser
|
|
86
|
+
without it treats the attribute as plain hidden: the closed content is
|
|
87
|
+
not searchable, and the trigger is the only way in. The attribute is set
|
|
88
|
+
here, not by Bits UI, so closing hides the content in the same frame.
|
|
89
|
+
-->
|
|
90
|
+
<BitsCollapsible.Root bind:open bind:ref {disabled} class={['hp-disclosure', className]} {...rest}>
|
|
91
|
+
{#if titleAs}
|
|
92
|
+
<svelte:element this={titleAs} class="heading">{@render trigger()}</svelte:element>
|
|
93
|
+
{:else}
|
|
94
|
+
{@render trigger()}
|
|
95
|
+
{/if}
|
|
96
|
+
<BitsCollapsible.Content hiddenUntilFound>
|
|
97
|
+
{#snippet child({ props })}
|
|
98
|
+
<div {...props} hidden={open ? undefined : 'until-found'} class="hp-disclosure-content">
|
|
99
|
+
<div class="body" bind:this={body}>
|
|
100
|
+
{@render children?.()}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
{/snippet}
|
|
104
|
+
</BitsCollapsible.Content>
|
|
105
|
+
</BitsCollapsible.Root>
|
|
106
|
+
|
|
107
|
+
<style>
|
|
108
|
+
/* Stacked disclosures sit 8px apart. No dividers: structure from
|
|
109
|
+
alignment. */
|
|
110
|
+
:global(.hp-disclosure + .hp-disclosure) {
|
|
111
|
+
margin-top: var(--space-2);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.heading {
|
|
115
|
+
margin: 0;
|
|
116
|
+
font: inherit;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* A full-width row, title at the start, chevron at the end. The hover
|
|
120
|
+
tint and the focus contour reach --bleed past the text edge, as table
|
|
121
|
+
bands do, and the padding keeps the title on the page edge. It wraps
|
|
122
|
+
a long title; it never truncates. */
|
|
123
|
+
:global(.hp-disclosure-trigger) {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: space-between;
|
|
127
|
+
gap: var(--space-4);
|
|
128
|
+
box-sizing: border-box;
|
|
129
|
+
width: calc(100% + 2 * var(--bleed));
|
|
130
|
+
min-height: var(--control-height);
|
|
131
|
+
margin: 0 calc(-1 * var(--bleed));
|
|
132
|
+
padding: var(--space-2) var(--bleed);
|
|
133
|
+
border: 0;
|
|
134
|
+
background: transparent;
|
|
135
|
+
color: var(--text);
|
|
136
|
+
font-family: var(--sans);
|
|
137
|
+
font-weight: 500;
|
|
138
|
+
text-align: start;
|
|
139
|
+
overflow-wrap: anywhere;
|
|
140
|
+
cursor: pointer;
|
|
141
|
+
touch-action: manipulation;
|
|
142
|
+
-webkit-tap-highlight-color: transparent;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* The select well's chevron: the same mask, 16px, --muted; it points up
|
|
146
|
+
while open, with the list's timing (--dur-pop opening, --dur-pop-exit
|
|
147
|
+
closing) and no turn under reduced motion. */
|
|
148
|
+
:global(.hp-disclosure-trigger::after) {
|
|
149
|
+
content: '';
|
|
150
|
+
flex: none;
|
|
151
|
+
width: 16px;
|
|
152
|
+
height: 16px;
|
|
153
|
+
background-color: var(--muted);
|
|
154
|
+
mask: var(--select-chevron) center / 16px 16px no-repeat;
|
|
155
|
+
transition: transform var(--dur-pop-exit) var(--settle);
|
|
156
|
+
}
|
|
157
|
+
:global(.hp-disclosure-trigger[aria-expanded='true']::after) {
|
|
158
|
+
transform: rotate(180deg);
|
|
159
|
+
transition-duration: var(--dur-pop);
|
|
160
|
+
}
|
|
161
|
+
@media (prefers-reduced-motion: reduce) {
|
|
162
|
+
:global(.hp-disclosure-trigger::after) {
|
|
163
|
+
transition: none;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Hover: fine pointers only. The tint alone barely shows (1.03:1), so the
|
|
168
|
+
title also underlines, as a tertiary Button's does. */
|
|
169
|
+
@media (hover: hover) and (pointer: fine) {
|
|
170
|
+
:global(.hp-disclosure-trigger) {
|
|
171
|
+
transition: background-color var(--dur-hover) var(--settle);
|
|
172
|
+
}
|
|
173
|
+
:global(.hp-disclosure-trigger:active:not(:disabled)) {
|
|
174
|
+
transition-duration: var(--dur-press);
|
|
175
|
+
}
|
|
176
|
+
:global(.hp-disclosure-trigger:hover:not(:disabled)) {
|
|
177
|
+
background-color: var(--surface-hover);
|
|
178
|
+
text-decoration: underline;
|
|
179
|
+
text-decoration-thickness: 1px;
|
|
180
|
+
text-underline-offset: 0.3em;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/* One step past the hover tint, so a press reads on a phone too. */
|
|
184
|
+
:global(.hp-disclosure-trigger:active:not(:disabled)) {
|
|
185
|
+
background-color: var(--surface-raised);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
:global(.hp-disclosure-trigger:disabled) {
|
|
189
|
+
color: var(--muted);
|
|
190
|
+
cursor: not-allowed;
|
|
191
|
+
}
|
|
192
|
+
:global(.hp-disclosure-trigger:disabled::after) {
|
|
193
|
+
background-color: var(--border);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* The padding sits on the inner box: closed content is hidden, and a
|
|
197
|
+
hidden box still shows its own padding. */
|
|
198
|
+
.body {
|
|
199
|
+
padding: var(--space-1) 0 var(--space-3);
|
|
200
|
+
}
|
|
201
|
+
</style>
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* `standard` (default): the title at the section-title size. `display`:
|
|
7
|
-
* the title in Geist Pixel Square, as the screen's one display line.
|
|
8
|
-
*/
|
|
9
|
-
export type EmptyStateVariant = 'standard' | 'display';
|
|
10
|
-
|
|
11
|
-
/** The title's element: `p` by default, a heading where the state names a region of the page. */
|
|
12
|
-
export type EmptyStateTitleElement = 'p' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
13
|
-
|
|
14
|
-
export type EmptyStateProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
|
|
15
|
-
/** What is empty, as a short fixed line: `No projects yet`. Never transformed. */
|
|
16
|
-
title: string;
|
|
17
|
-
/** Why it is empty or what fills it, in the muted ink. */
|
|
18
|
-
description?: string;
|
|
19
|
-
/** The one clear next action: render a `Button` here. */
|
|
20
|
-
action?: Snippet;
|
|
21
|
-
titleAs?: EmptyStateTitleElement;
|
|
22
|
-
variant?: EmptyStateVariant;
|
|
23
|
-
};
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<script lang="ts">
|
|
27
|
-
let {
|
|
28
|
-
title,
|
|
29
|
-
description,
|
|
30
|
-
action,
|
|
31
|
-
titleAs = 'p',
|
|
32
|
-
variant = 'standard',
|
|
33
|
-
class: className,
|
|
34
|
-
...rest
|
|
35
|
-
}: EmptyStateProps = $props();
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
<!--
|
|
39
|
-
A data view with nothing to show (BRIEF, Apps and phones: States): a title,
|
|
40
|
-
an optional description and one clear next action, aligned to the text
|
|
41
|
-
edge with no box around them. It sets no outer margin; the page or the
|
|
42
|
-
Table's `empty` snippet places it. Extra attributes and `class` go to the
|
|
43
|
-
root `div.hp-empty-state`.
|
|
44
|
-
-->
|
|
45
|
-
<div {...rest} class={['hp-empty-state', className]} data-variant={variant}>
|
|
46
|
-
<svelte:element
|
|
47
|
-
this={titleAs}
|
|
48
|
-
class={['hp-empty-state-title', variant === 'display' ? 'hp-pixel' : 'hp-section-title']}
|
|
49
|
-
>{title}</svelte:element
|
|
50
|
-
>
|
|
51
|
-
{#if description}
|
|
52
|
-
<p class="hp-empty-state-description">{description}</p>
|
|
53
|
-
{/if}
|
|
54
|
-
{#if action}
|
|
55
|
-
<div class="hp-empty-state-action">{@render action()}</div>
|
|
56
|
-
{/if}
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<style>
|
|
60
|
-
.hp-empty-state {
|
|
61
|
-
display: grid;
|
|
62
|
-
justify-items: start;
|
|
63
|
-
min-width: 0;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.hp-empty-state-title {
|
|
67
|
-
max-width: 100%;
|
|
68
|
-
overflow-wrap: anywhere;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/* Reading text: 16px at every width, in the reading measure. */
|
|
72
|
-
.hp-empty-state-description {
|
|
73
|
-
max-width: var(--measure);
|
|
74
|
-
margin-top: var(--space-2);
|
|
75
|
-
color: var(--muted);
|
|
76
|
-
font-size: var(--type-body);
|
|
77
|
-
line-height: 24px;
|
|
78
|
-
overflow-wrap: anywhere;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/* The action is its own group: three times the gap inside the text. */
|
|
82
|
-
.hp-empty-state-action {
|
|
83
|
-
display: flex;
|
|
84
|
-
flex-wrap: wrap;
|
|
85
|
-
gap: var(--space-2);
|
|
86
|
-
margin-top: var(--space-6);
|
|
87
|
-
}
|
|
88
|
-
</style>
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `standard` (default): the title at the section-title size. `display`:
|
|
7
|
+
* the title in Geist Pixel Square, as the screen's one display line.
|
|
8
|
+
*/
|
|
9
|
+
export type EmptyStateVariant = 'standard' | 'display';
|
|
10
|
+
|
|
11
|
+
/** The title's element: `p` by default, a heading where the state names a region of the page. */
|
|
12
|
+
export type EmptyStateTitleElement = 'p' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
13
|
+
|
|
14
|
+
export type EmptyStateProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
|
|
15
|
+
/** What is empty, as a short fixed line: `No projects yet`. Never transformed. */
|
|
16
|
+
title: string;
|
|
17
|
+
/** Why it is empty or what fills it, in the muted ink. */
|
|
18
|
+
description?: string;
|
|
19
|
+
/** The one clear next action: render a `Button` here. */
|
|
20
|
+
action?: Snippet;
|
|
21
|
+
titleAs?: EmptyStateTitleElement;
|
|
22
|
+
variant?: EmptyStateVariant;
|
|
23
|
+
};
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script lang="ts">
|
|
27
|
+
let {
|
|
28
|
+
title,
|
|
29
|
+
description,
|
|
30
|
+
action,
|
|
31
|
+
titleAs = 'p',
|
|
32
|
+
variant = 'standard',
|
|
33
|
+
class: className,
|
|
34
|
+
...rest
|
|
35
|
+
}: EmptyStateProps = $props();
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<!--
|
|
39
|
+
A data view with nothing to show (BRIEF, Apps and phones: States): a title,
|
|
40
|
+
an optional description and one clear next action, aligned to the text
|
|
41
|
+
edge with no box around them. It sets no outer margin; the page or the
|
|
42
|
+
Table's `empty` snippet places it. Extra attributes and `class` go to the
|
|
43
|
+
root `div.hp-empty-state`.
|
|
44
|
+
-->
|
|
45
|
+
<div {...rest} class={['hp-empty-state', className]} data-variant={variant}>
|
|
46
|
+
<svelte:element
|
|
47
|
+
this={titleAs}
|
|
48
|
+
class={['hp-empty-state-title', variant === 'display' ? 'hp-pixel' : 'hp-section-title']}
|
|
49
|
+
>{title}</svelte:element
|
|
50
|
+
>
|
|
51
|
+
{#if description}
|
|
52
|
+
<p class="hp-empty-state-description">{description}</p>
|
|
53
|
+
{/if}
|
|
54
|
+
{#if action}
|
|
55
|
+
<div class="hp-empty-state-action">{@render action()}</div>
|
|
56
|
+
{/if}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
.hp-empty-state {
|
|
61
|
+
display: grid;
|
|
62
|
+
justify-items: start;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.hp-empty-state-title {
|
|
67
|
+
max-width: 100%;
|
|
68
|
+
overflow-wrap: anywhere;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Reading text: 16px at every width, in the reading measure. */
|
|
72
|
+
.hp-empty-state-description {
|
|
73
|
+
max-width: var(--measure);
|
|
74
|
+
margin-top: var(--space-2);
|
|
75
|
+
color: var(--muted);
|
|
76
|
+
font-size: var(--type-body);
|
|
77
|
+
line-height: 24px;
|
|
78
|
+
overflow-wrap: anywhere;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* The action is its own group: three times the gap inside the text. */
|
|
82
|
+
.hp-empty-state-action {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-wrap: wrap;
|
|
85
|
+
gap: var(--space-2);
|
|
86
|
+
margin-top: var(--space-6);
|
|
87
|
+
}
|
|
88
|
+
</style>
|