@fusion.dev/helix-templates 0.138.0 → 0.140.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/README.md +18 -6
- package/dist/index.js +24 -0
- package/package.json +1 -1
- package/templates/angular/angular.json +57 -0
- package/templates/angular/package.json +28 -0
- package/templates/angular/src/app/app.config.ts +8 -0
- package/templates/angular/src/app/app.html +19 -0
- package/templates/angular/src/app/app.ts +29 -0
- package/templates/angular/src/app/data.ts +81 -0
- package/templates/angular/src/app/docs.ts +29 -0
- package/templates/angular/src/app/landing.html +77 -0
- package/templates/angular/src/app/landing.ts +29 -0
- package/templates/angular/src/app/overview.html +49 -0
- package/templates/angular/src/app/overview.ts +25 -0
- package/templates/angular/src/app/pathname.ts +24 -0
- package/templates/angular/src/app/signup-form.html +90 -0
- package/templates/angular/src/app/signup-form.ts +65 -0
- package/templates/angular/src/app/site-footer.html +10 -0
- package/templates/angular/src/app/site-footer.ts +22 -0
- package/templates/angular/src/app/site-header.html +30 -0
- package/templates/angular/src/app/site-header.ts +57 -0
- package/templates/angular/src/app.css +355 -0
- package/templates/angular/src/diagnostics.ts +24 -0
- package/templates/angular/src/index.html +12 -0
- package/templates/angular/src/main.ts +21 -0
- package/templates/angular/tsconfig.app.json +9 -0
- package/templates/angular/tsconfig.json +34 -0
- package/templates/react-nextjs/app/app.css +144 -152
- package/templates/react-nextjs/app/data.ts +17 -17
- package/templates/react-nextjs/app/diagnostics.ts +19 -0
- package/templates/react-nextjs/app/docs.ts +9 -9
- package/templates/react-nextjs/app/landing.tsx +63 -121
- package/templates/react-nextjs/app/overview.tsx +25 -27
- package/templates/react-nextjs/app/signup-form.tsx +36 -39
- package/templates/react-nextjs/app/site-footer.tsx +7 -10
- package/templates/react-nextjs/app/site-header.tsx +17 -21
- package/templates/react-nextjs/helix-jsx.d.ts +5 -7
- package/templates/react-nextjs/instrumentation-client.ts +6 -0
- package/templates/react-nextjs/package.json +1 -0
- package/templates/react-nextjs/tsconfig.json +1 -0
- package/templates/react-vite/helix-jsx.d.ts +5 -7
- package/templates/react-vite/package.json +1 -0
- package/templates/react-vite/src/app.css +144 -152
- package/templates/react-vite/src/data.ts +17 -17
- package/templates/react-vite/src/diagnostics.ts +24 -0
- package/templates/react-vite/src/docs.ts +9 -9
- package/templates/react-vite/src/landing.tsx +63 -121
- package/templates/react-vite/src/main.tsx +6 -0
- package/templates/react-vite/src/overview.tsx +24 -26
- package/templates/react-vite/src/routes.tsx +11 -13
- package/templates/react-vite/src/signup-form.tsx +36 -39
- package/templates/react-vite/src/site-footer.tsx +7 -10
- package/templates/react-vite/src/site-header.tsx +15 -19
- package/templates/react-vite/tsconfig.json +1 -0
- package/templates/solid-vite/helix-solid.d.ts +14 -0
- package/templates/solid-vite/index.html +12 -0
- package/templates/solid-vite/package.json +22 -0
- package/templates/solid-vite/src/App.tsx +35 -0
- package/templates/solid-vite/src/app.css +355 -0
- package/templates/solid-vite/src/data.ts +81 -0
- package/templates/solid-vite/src/diagnostics.ts +24 -0
- package/templates/solid-vite/src/docs.ts +29 -0
- package/templates/solid-vite/src/landing.tsx +130 -0
- package/templates/solid-vite/src/main.tsx +23 -0
- package/templates/solid-vite/src/overview.tsx +78 -0
- package/templates/solid-vite/src/routes.ts +22 -0
- package/templates/solid-vite/src/signup-form.tsx +151 -0
- package/templates/solid-vite/src/site-footer.tsx +34 -0
- package/templates/solid-vite/src/site-header.tsx +76 -0
- package/templates/solid-vite/tsconfig.json +27 -0
- package/templates/solid-vite/vite.config.ts +7 -0
- package/templates/svelte-vite/helix-svelte.d.ts +9 -0
- package/templates/svelte-vite/index.html +12 -0
- package/templates/svelte-vite/package.json +23 -0
- package/templates/svelte-vite/src/App.svelte +35 -0
- package/templates/svelte-vite/src/Landing.svelte +100 -0
- package/templates/svelte-vite/src/Overview.svelte +67 -0
- package/templates/svelte-vite/src/SignupForm.svelte +147 -0
- package/templates/svelte-vite/src/SiteFooter.svelte +26 -0
- package/templates/svelte-vite/src/SiteHeader.svelte +71 -0
- package/templates/svelte-vite/src/app.css +355 -0
- package/templates/svelte-vite/src/data.ts +81 -0
- package/templates/svelte-vite/src/diagnostics.ts +24 -0
- package/templates/svelte-vite/src/docs.ts +29 -0
- package/templates/svelte-vite/src/main.ts +23 -0
- package/templates/svelte-vite/src/routes.svelte.ts +19 -0
- package/templates/svelte-vite/tsconfig.json +28 -0
- package/templates/svelte-vite/vite.config.ts +7 -0
- package/templates/vue-vite/helix-vue.d.ts +8 -0
- package/templates/vue-vite/index.html +12 -0
- package/templates/vue-vite/package.json +23 -0
- package/templates/vue-vite/src/App.vue +39 -0
- package/templates/vue-vite/src/LandingPage.vue +102 -0
- package/templates/vue-vite/src/OverviewPage.vue +69 -0
- package/templates/vue-vite/src/SignupForm.vue +149 -0
- package/templates/vue-vite/src/SiteFooter.vue +28 -0
- package/templates/vue-vite/src/SiteHeader.vue +71 -0
- package/templates/vue-vite/src/app.css +355 -0
- package/templates/vue-vite/src/data.ts +81 -0
- package/templates/vue-vite/src/diagnostics.ts +24 -0
- package/templates/vue-vite/src/docs.ts +29 -0
- package/templates/vue-vite/src/main.ts +23 -0
- package/templates/vue-vite/src/routes.ts +26 -0
- package/templates/vue-vite/tsconfig.json +26 -0
- package/templates/vue-vite/vite.config.ts +13 -0
- package/templates.json +41 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
The first screen, and the only page here about the starter itself.
|
|
3
|
+
|
|
4
|
+
Delete it once you know your way around: nothing else imports it, and the
|
|
5
|
+
route it answers becomes yours.
|
|
6
|
+
|
|
7
|
+
**The calls to action are anchors.** A button that navigates is a link
|
|
8
|
+
dressed as a button, and the two are different things to a keyboard and to
|
|
9
|
+
a screen reader. The base stylesheet styles an anchor that asks with
|
|
10
|
+
`data-button`, so a link can look like one and stay a link.
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { roster } from './data.ts'
|
|
14
|
+
import { DOCS } from './docs.ts'
|
|
15
|
+
|
|
16
|
+
/* **A locale named, so the number reads the same everywhere.**
|
|
17
|
+
`toLocaleString()` with no argument reads the machine's locale, so the same
|
|
18
|
+
number is grouped two ways on two machines. */
|
|
19
|
+
const figure = new Intl.NumberFormat('en-US')
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div class="stack">
|
|
23
|
+
<header class="first-screen">
|
|
24
|
+
<!-- **The light behind the first screen**: a helix, drawn by the library in
|
|
25
|
+
the theme's colors, so a new brand color relights it. It shows one still
|
|
26
|
+
frame where motion is reduced, and a gradient where the browser draws
|
|
27
|
+
without a GPU. -->
|
|
28
|
+
<helix-aura class="first-light" data-layer speed={0.4} terminus={0.8} variant="braid"></helix-aura>
|
|
29
|
+
<div class="hero">
|
|
30
|
+
<helix-tag emphasis="subtle" tone="primary">Starter</helix-tag>
|
|
31
|
+
<helix-text as="h1" variant="display">Helix design system</helix-text>
|
|
32
|
+
<helix-text as="p" class="lede" variant="body">
|
|
33
|
+
Web components in your brand's colors, drawn from one set of tokens.
|
|
34
|
+
</helix-text>
|
|
35
|
+
<div class="actions">
|
|
36
|
+
<a data-button="solid" data-button-tone="primary" href="#/overview">See the data view</a>
|
|
37
|
+
<a data-button href="#/join">Open the form</a>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<!-- **The library at work, on the first screen.** The figures are the roster
|
|
42
|
+
the data view draws, read from the same module, so the panel is the app
|
|
43
|
+
itself, on glass over the light. -->
|
|
44
|
+
<helix-card class="specimen" material="glass" separation="outlined">
|
|
45
|
+
<helix-text as="h2" slot="header" variant="title">The roster, in three figures</helix-text>
|
|
46
|
+
<helix-stat-group divided>
|
|
47
|
+
<helix-stat>
|
|
48
|
+
{roster.people}
|
|
49
|
+
<span slot="label">People</span>
|
|
50
|
+
</helix-stat>
|
|
51
|
+
<helix-stat>
|
|
52
|
+
{roster.teams}
|
|
53
|
+
<span slot="label">Teams</span>
|
|
54
|
+
</helix-stat>
|
|
55
|
+
<helix-stat>
|
|
56
|
+
{figure.format(roster.reviews)}
|
|
57
|
+
<span slot="label">Reviews</span>
|
|
58
|
+
</helix-stat>
|
|
59
|
+
</helix-stat-group>
|
|
60
|
+
<a data-link href="#/overview" slot="footer">Open the data view</a>
|
|
61
|
+
</helix-card>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
<section class="section">
|
|
65
|
+
<helix-text as="h2" variant="subheading">What you have</helix-text>
|
|
66
|
+
<!-- **Each tile is a link, which is what `data-card` is for.** The base layer
|
|
67
|
+
draws the ring, moves the card's border to the brand on hover, and leaves
|
|
68
|
+
focus alone. Nothing inside may be interactive, since a link inside a link
|
|
69
|
+
is neither valid nor reachable. -->
|
|
70
|
+
<div class="features">
|
|
71
|
+
<a class="feature" data-card href={DOCS.elements}>
|
|
72
|
+
<helix-card separation="outlined">
|
|
73
|
+
<span class="feature-mark"><helix-icon name="cube"></helix-icon></span>
|
|
74
|
+
<helix-text as="h3" variant="title">Custom elements</helix-text>
|
|
75
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
76
|
+
One import in <helix-code>src/main.ts</helix-code> registers every tag.
|
|
77
|
+
</helix-text>
|
|
78
|
+
</helix-card>
|
|
79
|
+
</a>
|
|
80
|
+
<a class="feature" data-card href={DOCS.tokens}>
|
|
81
|
+
<helix-card separation="outlined">
|
|
82
|
+
<span class="feature-mark"><helix-icon name="palette"></helix-icon></span>
|
|
83
|
+
<helix-text as="h3" variant="title">One set of tokens</helix-text>
|
|
84
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
85
|
+
Every color, size, and space, in light and dark.
|
|
86
|
+
</helix-text>
|
|
87
|
+
</helix-card>
|
|
88
|
+
</a>
|
|
89
|
+
<a class="feature" data-card href={DOCS.parts}>
|
|
90
|
+
<helix-card separation="outlined">
|
|
91
|
+
<span class="feature-mark"><helix-icon name="sliders-horizontal"></helix-icon></span>
|
|
92
|
+
<helix-text as="h3" variant="title">Parts to style</helix-text>
|
|
93
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
94
|
+
Reach inside a component with <helix-code>::part()</helix-code>.
|
|
95
|
+
</helix-text>
|
|
96
|
+
</helix-card>
|
|
97
|
+
</a>
|
|
98
|
+
</div>
|
|
99
|
+
</section>
|
|
100
|
+
</div>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
The data view.
|
|
3
|
+
|
|
4
|
+
**The schema is markup and the data is a property.** The columns are
|
|
5
|
+
elements you can read in the HTML, and `rows` is the whole result handed over
|
|
6
|
+
at once, since a table of ten thousand rows cannot be written out as
|
|
7
|
+
elements. The table sorts, pages, and selects out of that array on its own.
|
|
8
|
+
|
|
9
|
+
**Selection is keyed on `row-key`, so it survives a sort and a page.**
|
|
10
|
+
`app.css` paints the chosen row through the parts the table names for it.
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { members } from './data.ts'
|
|
14
|
+
import { DOCS } from './docs.ts'
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div class="stack">
|
|
18
|
+
<header class="hero">
|
|
19
|
+
<helix-text emphasis="muted" variant="label">Data view</helix-text>
|
|
20
|
+
<helix-text as="h1" variant="heading">Roster</helix-text>
|
|
21
|
+
<helix-text as="p" class="lede" emphasis="muted" variant="body">
|
|
22
|
+
Twelve people from a local module. Sort a column, turn a page, or tick a row and watch the
|
|
23
|
+
rule in <helix-code>src/app.css</helix-code> paint it.
|
|
24
|
+
</helix-text>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<section class="section">
|
|
28
|
+
<helix-data-table
|
|
29
|
+
default-sort="name:asc"
|
|
30
|
+
label="Roster"
|
|
31
|
+
page-size={6}
|
|
32
|
+
row-key="id"
|
|
33
|
+
rows={members}
|
|
34
|
+
selectable
|
|
35
|
+
>
|
|
36
|
+
<!-- **Every column declares what it may give up.** A floor and a share:
|
|
37
|
+
the floor is what the words need, and the share is how the slack is
|
|
38
|
+
divided. At 390 the four columns and the checkboxes fit the box, and
|
|
39
|
+
below 376 the table scrolls its own box. -->
|
|
40
|
+
<helix-table-column field="name" sortable width="minmax(5.5rem, 1.6fr)">
|
|
41
|
+
Name
|
|
42
|
+
</helix-table-column>
|
|
43
|
+
<helix-table-column field="team" sortable width="minmax(4.5rem, 1fr)">
|
|
44
|
+
Team
|
|
45
|
+
</helix-table-column>
|
|
46
|
+
<helix-table-column field="role" width="minmax(4.5rem, 1fr)">Role</helix-table-column>
|
|
47
|
+
<!-- `end` lines the digits up under each other. `max-content`, since a
|
|
48
|
+
column of numerals has one right width. -->
|
|
49
|
+
<helix-table-column field="reviews" sortable text-align="end" width="max-content">
|
|
50
|
+
Reviews
|
|
51
|
+
</helix-table-column>
|
|
52
|
+
|
|
53
|
+
<!-- The footer is a row across the table: anything you slot goes at the
|
|
54
|
+
start, and the table's own pagination sits at the end. -->
|
|
55
|
+
<helix-text emphasis="subtle" slot="footer" variant="caption">
|
|
56
|
+
{members.length} people
|
|
57
|
+
</helix-text>
|
|
58
|
+
</helix-data-table>
|
|
59
|
+
|
|
60
|
+
<!-- A footnote to the table, so it takes the section's gap. -->
|
|
61
|
+
<helix-text as="p" emphasis="muted" variant="caption">
|
|
62
|
+
Filtering, column reordering, resizing, virtualized rows, and a server mode for large results
|
|
63
|
+
are on
|
|
64
|
+
<a data-link href={DOCS.table}>the component's own page</a>.
|
|
65
|
+
</helix-text>
|
|
66
|
+
</section>
|
|
67
|
+
</div>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
The form.
|
|
3
|
+
|
|
4
|
+
**Every control here is a form-associated custom element**, so a `<form>`
|
|
5
|
+
collects them, resets them, and validates them the way it does an `<input>`.
|
|
6
|
+
The submit proves it: it reads the answers with `new FormData(form)`, and
|
|
7
|
+
nothing gathers them by hand.
|
|
8
|
+
|
|
9
|
+
There is no wrapping `Field` component. A label in one shadow root cannot
|
|
10
|
+
name a control in another, so every control renders its own `label`, `hint`,
|
|
11
|
+
and `feedback`.
|
|
12
|
+
-->
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import type { HelixInputHelixBeforeChangeElementEvent } from '@fusion.dev/helix/jsx'
|
|
15
|
+
import { teams } from './data.ts'
|
|
16
|
+
import { DOCS } from './docs.ts'
|
|
17
|
+
|
|
18
|
+
/** One message in the toast region, and what it says. */
|
|
19
|
+
interface Message {
|
|
20
|
+
readonly id: number
|
|
21
|
+
readonly text: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let handle = $state('')
|
|
25
|
+
let messages = $state<readonly Message[]>([])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The page takes the submit, so nothing navigates. Remove `preventDefault()`
|
|
29
|
+
* and the form posts to the server like any other, with the same names.
|
|
30
|
+
*/
|
|
31
|
+
const join = (event: SubmitEvent) => {
|
|
32
|
+
event.preventDefault()
|
|
33
|
+
const answers = [...new FormData(event.currentTarget as HTMLFormElement).entries()]
|
|
34
|
+
.map(([field, value]) => `${field}=${value}`)
|
|
35
|
+
.join(' ')
|
|
36
|
+
messages = [...messages, { id: Date.now(), text: answers }]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* **A controlled value, which is the one habit to unlearn.** A Helix element
|
|
41
|
+
* is not read-only: writing `value` seeds it, and the element goes on
|
|
42
|
+
* applying its own changes. So taking the decision means refusing the change
|
|
43
|
+
* with `preventDefault()`, then setting the value you want. Leaving `handle`
|
|
44
|
+
* as it was does not refuse anything.
|
|
45
|
+
*/
|
|
46
|
+
const takeHandle = (event: HelixInputHelixBeforeChangeElementEvent) => {
|
|
47
|
+
event.preventDefault()
|
|
48
|
+
handle = event.detail.value.toLowerCase().replaceAll(/[^a-z0-9-]/g, '')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const dismiss = (id: number) => {
|
|
52
|
+
messages = messages.filter((one) => one.id !== id)
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<div class="stack">
|
|
57
|
+
<header class="hero">
|
|
58
|
+
<helix-text emphasis="muted" variant="label">Form</helix-text>
|
|
59
|
+
<helix-text as="h1" variant="heading">Join the roster</helix-text>
|
|
60
|
+
<helix-text as="p" class="lede" emphasis="muted" variant="body">
|
|
61
|
+
Five controls, and a <helix-code><form></helix-code> reads every one of them the way
|
|
62
|
+
it reads an <helix-code>input</helix-code>.
|
|
63
|
+
</helix-text>
|
|
64
|
+
</header>
|
|
65
|
+
|
|
66
|
+
<!-- The form takes a column of its own and the notes take what is left. At
|
|
67
|
+
390 the two stack, and the notes read as a postscript. -->
|
|
68
|
+
<div class="split">
|
|
69
|
+
<form class="form" onsubmit={join}>
|
|
70
|
+
<helix-input autocomplete="name" label="Name" name="name" required></helix-input>
|
|
71
|
+
|
|
72
|
+
<!-- Validation is the platform's: `type` and `required` are set on the
|
|
73
|
+
real input inside, and its own message shows once the field has been
|
|
74
|
+
touched. Set `feedback` to say it in your own words. -->
|
|
75
|
+
<helix-input
|
|
76
|
+
hint="Where we send the welcome note."
|
|
77
|
+
label="Email"
|
|
78
|
+
name="email"
|
|
79
|
+
required
|
|
80
|
+
type="email"
|
|
81
|
+
></helix-input>
|
|
82
|
+
|
|
83
|
+
<!-- `onhelix-before-change` binds with `addEventListener`, so it takes
|
|
84
|
+
the event's own name. -->
|
|
85
|
+
<helix-input
|
|
86
|
+
hint="Lowercase and hyphens, whatever you type."
|
|
87
|
+
label="Handle"
|
|
88
|
+
name="handle"
|
|
89
|
+
value={handle}
|
|
90
|
+
onhelix-before-change={takeHandle}
|
|
91
|
+
></helix-input>
|
|
92
|
+
|
|
93
|
+
<helix-select label="Team" name="team" placeholder="Pick one" required>
|
|
94
|
+
{#each teams as team (team)}
|
|
95
|
+
<helix-option value={team}>{team}</helix-option>
|
|
96
|
+
{/each}
|
|
97
|
+
</helix-select>
|
|
98
|
+
|
|
99
|
+
<!-- A switch is a thing happening now, which is why it says on and off.
|
|
100
|
+
It still submits, as `on`. -->
|
|
101
|
+
<helix-switch label="Send me the weekly digest" name="digest"></helix-switch>
|
|
102
|
+
|
|
103
|
+
<helix-button emphasis="solid" tone="primary" type="submit">Join</helix-button>
|
|
104
|
+
</form>
|
|
105
|
+
|
|
106
|
+
<aside class="notes">
|
|
107
|
+
<!-- The one fact you need before typing your name into a page you have
|
|
108
|
+
had for five minutes, so it is a callout. `tone` is the meaning,
|
|
109
|
+
and the component draws it with a tint and a bar on the inline
|
|
110
|
+
start, so it survives being read in grayscale. -->
|
|
111
|
+
<helix-callout tone="info">
|
|
112
|
+
The form posts nothing. Submit it, and a message shows exactly what a server would receive.
|
|
113
|
+
</helix-callout>
|
|
114
|
+
<helix-text as="h2" variant="title">What the form shows</helix-text>
|
|
115
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
116
|
+
Every control is a form-associated custom element, so the submit reads them all with
|
|
117
|
+
<helix-code>new FormData(form)</helix-code>. Reset the form and they all go back,
|
|
118
|
+
the way an <helix-code>input</helix-code> does.
|
|
119
|
+
</helix-text>
|
|
120
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
121
|
+
Leave the email wrong and the browser shows its own message, in your language. Handle is the
|
|
122
|
+
controlled field: the page refuses each change and writes back a cleaned value.
|
|
123
|
+
</helix-text>
|
|
124
|
+
<a data-link href={DOCS.forms}>Forms</a>
|
|
125
|
+
</aside>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- **The region is rendered whether or not there is anything in it.**
|
|
129
|
+
Assistive technology subscribes to a live region, and one that arrives
|
|
130
|
+
already holding its message is often never announced. A dismissed
|
|
131
|
+
message asks to go through `helix-dismiss`. -->
|
|
132
|
+
<helix-toast>
|
|
133
|
+
{#each messages as message (message.id)}
|
|
134
|
+
<helix-toast-item
|
|
135
|
+
dismissible
|
|
136
|
+
duration={8000}
|
|
137
|
+
onhelix-dismiss={() => dismiss(message.id)}
|
|
138
|
+
tone="success"
|
|
139
|
+
>
|
|
140
|
+
<helix-flex direction="column" gap="2xs">
|
|
141
|
+
<helix-text variant="body-strong">Read by the form</helix-text>
|
|
142
|
+
<helix-text variant="code">{message.text}</helix-text>
|
|
143
|
+
</helix-flex>
|
|
144
|
+
</helix-toast-item>
|
|
145
|
+
{/each}
|
|
146
|
+
</helix-toast>
|
|
147
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
The bar's counterpart, on every route.
|
|
3
|
+
|
|
4
|
+
**It puts a floor under the content.** The overview is a heading, a table,
|
|
5
|
+
and a caption, which on a tall window left the bottom third of the screen
|
|
6
|
+
empty. The footer is also where the links you want once belong.
|
|
7
|
+
|
|
8
|
+
`data-link="subtle"` reads as body text until it is hovered, which suits a
|
|
9
|
+
row of links under a rule.
|
|
10
|
+
-->
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { DOCS } from './docs.ts'
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<footer class="site-footer">
|
|
16
|
+
<div class="footer-row">
|
|
17
|
+
<helix-text as="p" emphasis="subtle" variant="caption">
|
|
18
|
+
Every component here has a page, with its API, its parts, and its examples.
|
|
19
|
+
</helix-text>
|
|
20
|
+
<div class="footer-links">
|
|
21
|
+
<a data-link="subtle" href={DOCS.gallery}>Components</a>
|
|
22
|
+
<a data-link="subtle" href={DOCS.elements}>Documentation</a>
|
|
23
|
+
<a data-link="subtle" href={DOCS.svelte}>Svelte</a>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</footer>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<!-- The bar across the top: the wordmark, the destinations, and the color mode. -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import { route } from './routes.svelte.ts'
|
|
4
|
+
|
|
5
|
+
/** What the page can follow, in the order the control cycles them. */
|
|
6
|
+
const MODES = ['system', 'light', 'dark'] as const
|
|
7
|
+
|
|
8
|
+
type Mode = (typeof MODES)[number]
|
|
9
|
+
|
|
10
|
+
/** The glyph each mode is announced and drawn with. */
|
|
11
|
+
const MARKS: Record<Mode, string> = { dark: 'moon', light: 'sun', system: 'monitor' }
|
|
12
|
+
|
|
13
|
+
/* `system` is the resting state, the mode the page follows until the control is pressed. */
|
|
14
|
+
let mode = $state<Mode>('system')
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* One attribute, two modes, and no second stylesheet.
|
|
18
|
+
*
|
|
19
|
+
* Every Helix token is written once as a `light-dark()` pair, so
|
|
20
|
+
* `color-scheme` decides which half resolves. `data-color-mode` sets it, and
|
|
21
|
+
* since `color-scheme` inherits, the attribute works on any element: put it
|
|
22
|
+
* on a panel for a light preview inside a dark page.
|
|
23
|
+
*
|
|
24
|
+
* `system` removes the attribute. Writing out whichever mode is current would
|
|
25
|
+
* hold the page there when the system's own setting changed.
|
|
26
|
+
*/
|
|
27
|
+
const cycle = () => {
|
|
28
|
+
const next = MODES[(MODES.indexOf(mode) + 1) % MODES.length] ?? 'system'
|
|
29
|
+
mode = next
|
|
30
|
+
if (next === 'system') {
|
|
31
|
+
delete document.documentElement.dataset.colorMode
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
document.documentElement.dataset.colorMode = next
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<helix-app-header material="glass" sticky>
|
|
39
|
+
<!-- The wordmark is the way back to the first page, so there is no third
|
|
40
|
+
destination in the bar saying the same thing. It is also the only name of
|
|
41
|
+
the project on the screen. The mark is a placeholder for your own, drawn
|
|
42
|
+
in `--helix-color-brand-mark`, the token for the brand as a graphic. -->
|
|
43
|
+
<a class="wordmark" data-link="quiet" href="#/" slot="start">
|
|
44
|
+
<helix-icon name="cube"></helix-icon>
|
|
45
|
+
<helix-text variant="body-strong">__PROJECT_NAME__</helix-text>
|
|
46
|
+
</a>
|
|
47
|
+
|
|
48
|
+
<!-- The links are yours. Navigation owns the marker that travels between them
|
|
49
|
+
and renders no anchors of its own. The base layer styles them,
|
|
50
|
+
`aria-current="page"` included. -->
|
|
51
|
+
<helix-navigation label="Pages">
|
|
52
|
+
<a aria-current={route.path === '/overview' ? 'page' : undefined} href="#/overview">Overview</a>
|
|
53
|
+
<a aria-current={route.path === '/join' ? 'page' : undefined} href="#/join">Join</a>
|
|
54
|
+
</helix-navigation>
|
|
55
|
+
|
|
56
|
+
<!-- An icon with no words has no accessible name until it is given one, which
|
|
57
|
+
is why this is its own element.
|
|
58
|
+
|
|
59
|
+
Svelte's accessibility check reads the tag, and the button is inside the
|
|
60
|
+
element's shadow root, with its role and its keys. So the two warnings
|
|
61
|
+
about a click on an element with neither are ignored here. -->
|
|
62
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
63
|
+
<helix-icon-button
|
|
64
|
+
label={`Color mode: ${mode}`}
|
|
65
|
+
onclick={cycle}
|
|
66
|
+
slot="end"
|
|
67
|
+
title={`Color mode: ${mode}`}
|
|
68
|
+
>
|
|
69
|
+
<helix-icon name={MARKS[mode]}></helix-icon>
|
|
70
|
+
</helix-icon-button>
|
|
71
|
+
</helix-app-header>
|