@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
package/README.md
CHANGED
|
@@ -16,13 +16,25 @@ written against.
|
|
|
16
16
|
|---|---|
|
|
17
17
|
| `react-vite` | React and TypeScript on Vite. The shortest path to a running app. |
|
|
18
18
|
| `react-nextjs` | React and TypeScript on the Next.js App Router, the three pages as real routes. |
|
|
19
|
+
| `vue-vite` | Vue and TypeScript on Vite. |
|
|
20
|
+
| `angular` | Angular and TypeScript on the Angular CLI. |
|
|
21
|
+
| `svelte-vite` | Svelte and TypeScript on Vite. |
|
|
22
|
+
| `solid-vite` | Solid and TypeScript on Vite. |
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
Every template lays down the same three pages, in the same order and with the
|
|
25
|
+
same copy:
|
|
26
|
+
|
|
27
|
+
- a landing page saying what is installed and where it is documented;
|
|
28
|
+
- an overview holding a sorted, paged, selectable data table;
|
|
29
|
+
- a form whose controls a `<form>` reads with `FormData`, because they are real
|
|
30
|
+
form participants.
|
|
31
|
+
|
|
32
|
+
What differs between them is what each framework requires. `bun run
|
|
33
|
+
smoke:scaffold` builds every template and holds what each renders to React on
|
|
34
|
+
Vite's, in markup and in pictures, so a template that drifts fails CI.
|
|
35
|
+
|
|
36
|
+
Each one starts Helix diagnostics in development, checked against the lint data
|
|
37
|
+
of the Helix it installed, and a production build leaves it out.
|
|
26
38
|
|
|
27
39
|
**There is no build-free HTML template**, and that is a fact about the packages
|
|
28
40
|
rather than an omission. `@fusion.dev/helix`'s published entry re-exports bare
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,30 @@ export const templates = [
|
|
|
11
11
|
named: ['app/layout.tsx', 'app/site-header.tsx', 'package.json'],
|
|
12
12
|
title: 'React (Next.js)',
|
|
13
13
|
},
|
|
14
|
+
{
|
|
15
|
+
description: 'Vue and TypeScript on Vite.',
|
|
16
|
+
id: 'vue-vite',
|
|
17
|
+
named: ['index.html', 'package.json', 'src/SiteHeader.vue'],
|
|
18
|
+
title: 'Vue (Vite)',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
description: 'Angular and TypeScript on the Angular CLI.',
|
|
22
|
+
id: 'angular',
|
|
23
|
+
named: ['angular.json', 'package.json', 'src/app/site-header.html', 'src/index.html'],
|
|
24
|
+
title: 'Angular',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
description: 'Svelte and TypeScript on Vite.',
|
|
28
|
+
id: 'svelte-vite',
|
|
29
|
+
named: ['index.html', 'package.json', 'src/SiteHeader.svelte'],
|
|
30
|
+
title: 'Svelte (Vite)',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
description: 'Solid and TypeScript on Vite.',
|
|
34
|
+
id: 'solid-vite',
|
|
35
|
+
named: ['index.html', 'package.json', 'src/site-header.tsx'],
|
|
36
|
+
title: 'Solid (Vite)',
|
|
37
|
+
},
|
|
14
38
|
];
|
|
15
39
|
export const PROJECT_NAME = '__PROJECT_NAME__';
|
|
16
40
|
export const HELIX_VERSION = '__HELIX_VERSION__';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-templates",
|
|
3
3
|
"description": "Starter templates for Helix, as data. Read by `helix new` and by `npm create @fusion.dev/helix-app`.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.140.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"newProjectRoot": "projects",
|
|
5
|
+
"projects": {
|
|
6
|
+
"__PROJECT_NAME__": {
|
|
7
|
+
"projectType": "application",
|
|
8
|
+
"root": "",
|
|
9
|
+
"sourceRoot": "src",
|
|
10
|
+
"prefix": "app",
|
|
11
|
+
"architect": {
|
|
12
|
+
"build": {
|
|
13
|
+
"builder": "@angular/build:application",
|
|
14
|
+
"options": {
|
|
15
|
+
"browser": "src/main.ts",
|
|
16
|
+
"outputPath": "dist",
|
|
17
|
+
"styles": [
|
|
18
|
+
"@fusion.dev/helix-styles/helix.css",
|
|
19
|
+
"src/app.css"
|
|
20
|
+
],
|
|
21
|
+
"tsConfig": "tsconfig.app.json"
|
|
22
|
+
},
|
|
23
|
+
"configurations": {
|
|
24
|
+
"production": {
|
|
25
|
+
"budgets": [
|
|
26
|
+
{
|
|
27
|
+
"maximumError": "5MB",
|
|
28
|
+
"maximumWarning": "2MB",
|
|
29
|
+
"type": "initial"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"outputHashing": "all"
|
|
33
|
+
},
|
|
34
|
+
"development": {
|
|
35
|
+
"extractLicenses": false,
|
|
36
|
+
"optimization": false,
|
|
37
|
+
"sourceMap": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"defaultConfiguration": "production"
|
|
41
|
+
},
|
|
42
|
+
"serve": {
|
|
43
|
+
"builder": "@angular/build:dev-server",
|
|
44
|
+
"configurations": {
|
|
45
|
+
"production": {
|
|
46
|
+
"buildTarget": "__PROJECT_NAME__:build:production"
|
|
47
|
+
},
|
|
48
|
+
"development": {
|
|
49
|
+
"buildTarget": "__PROJECT_NAME__:build:development"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"defaultConfiguration": "development"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "ng build",
|
|
7
|
+
"dev": "ng serve",
|
|
8
|
+
"ng": "ng",
|
|
9
|
+
"start": "ng serve"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@angular/common": "^22.2.1",
|
|
13
|
+
"@angular/compiler": "^22.2.1",
|
|
14
|
+
"@angular/core": "^22.2.1",
|
|
15
|
+
"@angular/platform-browser": "^22.2.1",
|
|
16
|
+
"@fusion.dev/helix": "^__HELIX_VERSION__",
|
|
17
|
+
"@fusion.dev/helix-styles": "^__HELIX_VERSION__",
|
|
18
|
+
"rxjs": "~7.8.0",
|
|
19
|
+
"tslib": "^2.3.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@angular/build": "^22.2.1",
|
|
23
|
+
"@angular/cli": "^22.2.1",
|
|
24
|
+
"@angular/compiler-cli": "^22.2.1",
|
|
25
|
+
"@fusion.dev/helix-diagnostics": "^__HELIX_VERSION__",
|
|
26
|
+
"typescript": "~6.0.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
/* No zone.js, which Angular no longer needs: a signal that changes is what
|
|
4
|
+
updates a view, so the Helix elements' own events and timers start no
|
|
5
|
+
change detection of their own. */
|
|
6
|
+
export const appConfig: ApplicationConfig = {
|
|
7
|
+
providers: [provideBrowserGlobalErrorListeners()],
|
|
8
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!-- The bar, the route, and the footer, in a column the height of the window.
|
|
2
|
+
`app.css` says why the footer is worth having and why the column is flex. -->
|
|
3
|
+
<div class="shell">
|
|
4
|
+
<app-site-header></app-site-header>
|
|
5
|
+
<main class="page">
|
|
6
|
+
@switch (path()) {
|
|
7
|
+
@case ('/overview') {
|
|
8
|
+
<div app-overview></div>
|
|
9
|
+
}
|
|
10
|
+
@case ('/join') {
|
|
11
|
+
<div app-signup-form></div>
|
|
12
|
+
}
|
|
13
|
+
@default {
|
|
14
|
+
<div app-landing></div>
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
</main>
|
|
18
|
+
<footer app-site-footer></footer>
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Component, inject } from '@angular/core'
|
|
2
|
+
import { Landing } from './landing'
|
|
3
|
+
import { Overview } from './overview'
|
|
4
|
+
import { Pathname } from './pathname'
|
|
5
|
+
import { SignupForm } from './signup-form'
|
|
6
|
+
import { SiteFooter } from './site-footer'
|
|
7
|
+
import { SiteHeader } from './site-header'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The shell, and the switch between the three pages.
|
|
11
|
+
*
|
|
12
|
+
* Helix components are custom elements, so you write the tags directly. There
|
|
13
|
+
* is no wrapper package and nothing to import per component: `src/main.ts`
|
|
14
|
+
* registers them once, and every tag in the app is real from then on.
|
|
15
|
+
*
|
|
16
|
+
* **A page and the footer are each a component on the element it draws**,
|
|
17
|
+
* selected by an attribute: `footer[app-site-footer]`, and a `div` for each
|
|
18
|
+
* page. A tag of their own would put an element between the shell and what
|
|
19
|
+
* they draw, which the shell's column and `app.css` would then have to know
|
|
20
|
+
* about. The header is the exception, and `site-header.ts` says why.
|
|
21
|
+
*/
|
|
22
|
+
@Component({
|
|
23
|
+
imports: [Landing, Overview, SignupForm, SiteFooter, SiteHeader],
|
|
24
|
+
selector: 'app-root',
|
|
25
|
+
templateUrl: './app.html',
|
|
26
|
+
})
|
|
27
|
+
export class App {
|
|
28
|
+
protected readonly path = inject(Pathname).current
|
|
29
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rows the overview draws.
|
|
3
|
+
*
|
|
4
|
+
* A local module and no network, so the starter runs with nothing behind it.
|
|
5
|
+
* Point `rows` at whatever you fetch and the table stays the same: it takes the
|
|
6
|
+
* whole result and does the sorting, the filtering, and the paging itself. Past
|
|
7
|
+
* the size a browser can hold, `mode="server"` hands the paging to you, one page
|
|
8
|
+
* at a time.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One person on the roster.
|
|
13
|
+
*
|
|
14
|
+
* **A type alias, which TypeScript makes load-bearing here.** `rows` takes
|
|
15
|
+
* `Readonly<Record<string, unknown>>`, and an interface has no implicit index
|
|
16
|
+
* signature, so an interface of exactly these fields is not assignable to it.
|
|
17
|
+
* The error names the index signature, so it reads as though the data were
|
|
18
|
+
* wrong.
|
|
19
|
+
*/
|
|
20
|
+
export type Member = {
|
|
21
|
+
/**
|
|
22
|
+
* A stable identity, which is what `row-key` names.
|
|
23
|
+
*
|
|
24
|
+
* With one, a sort moves each row's element whole, so focus and anything
|
|
25
|
+
* attached to a row go with it. Without one, a sort rewrites the cells in
|
|
26
|
+
* place and loses both.
|
|
27
|
+
*/
|
|
28
|
+
readonly id: string
|
|
29
|
+
/**
|
|
30
|
+
* The name a row is found by.
|
|
31
|
+
*/
|
|
32
|
+
readonly name: string
|
|
33
|
+
/**
|
|
34
|
+
* A number, so the column sorts by size.
|
|
35
|
+
*
|
|
36
|
+
* A cell showing `2.4k` sorts as text unless the column's `compare` says what
|
|
37
|
+
* the value underneath is, which is cheap to get right now and expensive to
|
|
38
|
+
* add later.
|
|
39
|
+
*/
|
|
40
|
+
readonly reviews: number
|
|
41
|
+
/**
|
|
42
|
+
* What they do.
|
|
43
|
+
*/
|
|
44
|
+
readonly role: string
|
|
45
|
+
/**
|
|
46
|
+
* Who they do it with.
|
|
47
|
+
*/
|
|
48
|
+
readonly team: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const members: readonly Member[] = [
|
|
52
|
+
{ id: 'm-01', name: 'Ada Okonkwo', reviews: 184, role: 'Staff', team: 'Platform' },
|
|
53
|
+
{ id: 'm-02', name: 'Bruno Salas', reviews: 96, role: 'Senior', team: 'Payments' },
|
|
54
|
+
{ id: 'm-03', name: 'Cai Wen', reviews: 231, role: 'Principal', team: 'Platform' },
|
|
55
|
+
{ id: 'm-04', name: 'Dilara Yilmaz', reviews: 64, role: 'Senior', team: 'Growth' },
|
|
56
|
+
{ id: 'm-05', name: 'Esi Mensah', reviews: 148, role: 'Staff', team: 'Payments' },
|
|
57
|
+
{ id: 'm-06', name: 'Felix Braun', reviews: 39, role: 'Engineer', team: 'Growth' },
|
|
58
|
+
{ id: 'm-07', name: 'Gita Raman', reviews: 112, role: 'Senior', team: 'Platform' },
|
|
59
|
+
{ id: 'm-08', name: 'Hana Sato', reviews: 57, role: 'Engineer', team: 'Design' },
|
|
60
|
+
{ id: 'm-09', name: 'Ivan Petrov', reviews: 88, role: 'Senior', team: 'Payments' },
|
|
61
|
+
{ id: 'm-10', name: 'Jo Lindqvist', reviews: 21, role: 'Engineer', team: 'Design' },
|
|
62
|
+
{ id: 'm-11', name: 'Kwame Asante', reviews: 73, role: 'Staff', team: 'Growth' },
|
|
63
|
+
{ id: 'm-12', name: 'Lena Fischer', reviews: 12, role: 'Engineer', team: 'Design' },
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
/** The teams the form offers, read from the roster so the two cannot disagree. */
|
|
67
|
+
export const teams: readonly string[] = [...new Set(members.map((one) => one.team))].toSorted()
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The same roster as three figures, for the panel on the first screen.
|
|
71
|
+
*
|
|
72
|
+
* Derived from the rows, so the panel and the table always agree.
|
|
73
|
+
*/
|
|
74
|
+
export const roster = {
|
|
75
|
+
/** How many rows the data view has. */
|
|
76
|
+
people: members.length,
|
|
77
|
+
/** Every review on the roster, added up. */
|
|
78
|
+
reviews: members.reduce((total, one) => total + one.reviews, 0),
|
|
79
|
+
/** How many teams they are spread across. */
|
|
80
|
+
teams: teams.length,
|
|
81
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The documentation the starter links to.
|
|
3
|
+
*
|
|
4
|
+
* **One module, because links are what rots first in a starter.** A page that
|
|
5
|
+
* composes its own URLs ships a plausible-looking 404 to every project
|
|
6
|
+
* scaffolded from it, and it shows only when the link is followed. Here they
|
|
7
|
+
* are in one list you can open and check, and pointing the starter at a mirror
|
|
8
|
+
* or an internal copy is one edit.
|
|
9
|
+
*
|
|
10
|
+
* Checked October 4, 2026: every entry answers 200.
|
|
11
|
+
*/
|
|
12
|
+
export const DOCS = {
|
|
13
|
+
/** Angular specifically: the schema, property bindings, and events. */
|
|
14
|
+
angular: 'https://fusion.dev/helix/docs/angular',
|
|
15
|
+
/** How the elements are registered and written, which is the first page to read. */
|
|
16
|
+
elements: 'https://fusion.dev/helix/docs/using-the-elements',
|
|
17
|
+
/** What a `<form>` does with these controls, and what it cannot do. */
|
|
18
|
+
forms: 'https://fusion.dev/helix/docs/forms',
|
|
19
|
+
/** Every component, with its API, its parts, and its examples. */
|
|
20
|
+
gallery: 'https://fusion.dev/helix/components',
|
|
21
|
+
/** The light and dark pair, and the attribute that sets which one shows. */
|
|
22
|
+
modes: 'https://fusion.dev/helix/docs/color-modes',
|
|
23
|
+
/** Reaching a component's parts from a page, which `app.css` does once. */
|
|
24
|
+
parts: 'https://fusion.dev/helix/docs/overriding-a-component',
|
|
25
|
+
/** The data view's own page, where the rest of its API is. */
|
|
26
|
+
table: 'https://fusion.dev/helix/components/data-table',
|
|
27
|
+
/** Every token, and what each one is for. */
|
|
28
|
+
tokens: 'https://fusion.dev/helix/docs/tokens',
|
|
29
|
+
} as const
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<header class="first-screen">
|
|
2
|
+
<!-- **The light behind the first screen**: a helix, drawn by the library in
|
|
3
|
+
the theme's colors, so a new brand color relights it. It shows one still
|
|
4
|
+
frame where motion is reduced, and a gradient where the browser draws
|
|
5
|
+
without a GPU. -->
|
|
6
|
+
<helix-aura class="first-light" data-layer [speed]="0.4" [terminus]="0.8" variant="braid"></helix-aura>
|
|
7
|
+
<div class="hero">
|
|
8
|
+
<helix-tag emphasis="subtle" tone="primary">Starter</helix-tag>
|
|
9
|
+
<helix-text as="h1" variant="display">Helix design system</helix-text>
|
|
10
|
+
<helix-text as="p" class="lede" variant="body">
|
|
11
|
+
Web components in your brand's colors, drawn from one set of tokens.
|
|
12
|
+
</helix-text>
|
|
13
|
+
<div class="actions">
|
|
14
|
+
<a data-button="solid" data-button-tone="primary" href="#/overview">See the data view</a>
|
|
15
|
+
<a data-button href="#/join">Open the form</a>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<!-- **The library at work, on the first screen.** The figures are the roster
|
|
20
|
+
the data view draws, read from the same module, so the panel is the app
|
|
21
|
+
itself, on glass over the light. -->
|
|
22
|
+
<helix-card class="specimen" material="glass" separation="outlined">
|
|
23
|
+
<helix-text as="h2" slot="header" variant="title">The roster, in three figures</helix-text>
|
|
24
|
+
<helix-stat-group divided>
|
|
25
|
+
<helix-stat>
|
|
26
|
+
{{ roster.people }}
|
|
27
|
+
<span slot="label">People</span>
|
|
28
|
+
</helix-stat>
|
|
29
|
+
<helix-stat>
|
|
30
|
+
{{ roster.teams }}
|
|
31
|
+
<span slot="label">Teams</span>
|
|
32
|
+
</helix-stat>
|
|
33
|
+
<helix-stat>
|
|
34
|
+
{{ figure.format(roster.reviews) }}
|
|
35
|
+
<span slot="label">Reviews</span>
|
|
36
|
+
</helix-stat>
|
|
37
|
+
</helix-stat-group>
|
|
38
|
+
<a data-link href="#/overview" slot="footer">Open the data view</a>
|
|
39
|
+
</helix-card>
|
|
40
|
+
</header>
|
|
41
|
+
|
|
42
|
+
<section class="section">
|
|
43
|
+
<helix-text as="h2" variant="subheading">What you have</helix-text>
|
|
44
|
+
<!-- **Each tile is a link, which is what `data-card` is for.** The base layer
|
|
45
|
+
draws the ring, moves the card's border to the brand on hover, and leaves
|
|
46
|
+
focus alone. Nothing inside may be interactive, since a link inside a link
|
|
47
|
+
is neither valid nor reachable. -->
|
|
48
|
+
<div class="features">
|
|
49
|
+
<a class="feature" data-card [href]="docs.elements">
|
|
50
|
+
<helix-card separation="outlined">
|
|
51
|
+
<span class="feature-mark"><helix-icon name="cube"></helix-icon></span>
|
|
52
|
+
<helix-text as="h3" variant="title">Custom elements</helix-text>
|
|
53
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
54
|
+
One import in <helix-code>src/main.ts</helix-code> registers every tag.
|
|
55
|
+
</helix-text>
|
|
56
|
+
</helix-card>
|
|
57
|
+
</a>
|
|
58
|
+
<a class="feature" data-card [href]="docs.tokens">
|
|
59
|
+
<helix-card separation="outlined">
|
|
60
|
+
<span class="feature-mark"><helix-icon name="palette"></helix-icon></span>
|
|
61
|
+
<helix-text as="h3" variant="title">One set of tokens</helix-text>
|
|
62
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
63
|
+
Every color, size, and space, in light and dark.
|
|
64
|
+
</helix-text>
|
|
65
|
+
</helix-card>
|
|
66
|
+
</a>
|
|
67
|
+
<a class="feature" data-card [href]="docs.parts">
|
|
68
|
+
<helix-card separation="outlined">
|
|
69
|
+
<span class="feature-mark"><helix-icon name="sliders-horizontal"></helix-icon></span>
|
|
70
|
+
<helix-text as="h3" variant="title">Parts to style</helix-text>
|
|
71
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
72
|
+
Reach inside a component with <helix-code>::part()</helix-code>.
|
|
73
|
+
</helix-text>
|
|
74
|
+
</helix-card>
|
|
75
|
+
</a>
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
2
|
+
import { roster } from './data'
|
|
3
|
+
import { DOCS } from './docs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The first screen, and the only page here about the starter itself.
|
|
7
|
+
*
|
|
8
|
+
* Delete it once you know your way around: nothing else imports it, and the
|
|
9
|
+
* route it answers becomes yours.
|
|
10
|
+
*
|
|
11
|
+
* **The calls to action are anchors.** A button that navigates is a link
|
|
12
|
+
* dressed as a button, and the two are different things to a keyboard and to
|
|
13
|
+
* a screen reader. The base stylesheet styles an anchor that asks with
|
|
14
|
+
* `data-button`, so a link can look like one and stay a link.
|
|
15
|
+
*/
|
|
16
|
+
@Component({
|
|
17
|
+
host: { class: 'stack' },
|
|
18
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
19
|
+
selector: 'div[app-landing]',
|
|
20
|
+
templateUrl: './landing.html',
|
|
21
|
+
})
|
|
22
|
+
export class Landing {
|
|
23
|
+
protected readonly docs = DOCS
|
|
24
|
+
protected readonly roster = roster
|
|
25
|
+
/* **A locale named, so the number reads the same everywhere.**
|
|
26
|
+
`toLocaleString()` with no argument reads the machine's locale, so the same
|
|
27
|
+
number is grouped two ways on two machines. */
|
|
28
|
+
protected readonly figure = new Intl.NumberFormat('en-US')
|
|
29
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<header class="hero">
|
|
2
|
+
<helix-text emphasis="muted" variant="label">Data view</helix-text>
|
|
3
|
+
<helix-text as="h1" variant="heading">Roster</helix-text>
|
|
4
|
+
<helix-text as="p" class="lede" emphasis="muted" variant="body">
|
|
5
|
+
Twelve people from a local module. Sort a column, turn a page, or tick a row and watch the
|
|
6
|
+
rule in <helix-code>src/app.css</helix-code> paint it.
|
|
7
|
+
</helix-text>
|
|
8
|
+
</header>
|
|
9
|
+
|
|
10
|
+
<section class="section">
|
|
11
|
+
<helix-data-table
|
|
12
|
+
default-sort="name:asc"
|
|
13
|
+
label="Roster"
|
|
14
|
+
page-size="6"
|
|
15
|
+
row-key="id"
|
|
16
|
+
[rows]="members"
|
|
17
|
+
selectable
|
|
18
|
+
>
|
|
19
|
+
<!-- **Every column declares what it may give up.** A floor and a share:
|
|
20
|
+
the floor is what the words need, and the share is how the slack is
|
|
21
|
+
divided. At 390 the four columns and the checkboxes fit the box, and
|
|
22
|
+
below 376 the table scrolls its own box. -->
|
|
23
|
+
<helix-table-column field="name" sortable width="minmax(5.5rem, 1.6fr)">
|
|
24
|
+
Name
|
|
25
|
+
</helix-table-column>
|
|
26
|
+
<helix-table-column field="team" sortable width="minmax(4.5rem, 1fr)">
|
|
27
|
+
Team
|
|
28
|
+
</helix-table-column>
|
|
29
|
+
<helix-table-column field="role" width="minmax(4.5rem, 1fr)">Role</helix-table-column>
|
|
30
|
+
<!-- `end` lines the digits up under each other. `max-content`, since a
|
|
31
|
+
column of numerals has one right width. -->
|
|
32
|
+
<helix-table-column field="reviews" sortable text-align="end" width="max-content">
|
|
33
|
+
Reviews
|
|
34
|
+
</helix-table-column>
|
|
35
|
+
|
|
36
|
+
<!-- The footer is a row across the table: anything you slot goes at the
|
|
37
|
+
start, and the table's own pagination sits at the end. -->
|
|
38
|
+
<helix-text emphasis="subtle" slot="footer" variant="caption">
|
|
39
|
+
{{ members.length }} people
|
|
40
|
+
</helix-text>
|
|
41
|
+
</helix-data-table>
|
|
42
|
+
|
|
43
|
+
<!-- A footnote to the table, so it takes the section's gap. -->
|
|
44
|
+
<helix-text as="p" emphasis="muted" variant="caption">
|
|
45
|
+
Filtering, column reordering, resizing, virtualized rows, and a server mode for large results
|
|
46
|
+
are on
|
|
47
|
+
<a data-link [href]="docs.table">the component's own page</a>.
|
|
48
|
+
</helix-text>
|
|
49
|
+
</section>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
2
|
+
import { members } from './data'
|
|
3
|
+
import { DOCS } from './docs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The data view.
|
|
7
|
+
*
|
|
8
|
+
* **The schema is markup and the data is a property.** The columns are
|
|
9
|
+
* elements you can read in the HTML, and `[rows]` hands over the whole result
|
|
10
|
+
* at once, since a table of ten thousand rows cannot be written out as
|
|
11
|
+
* elements. The table sorts, pages, and selects out of that array on its own.
|
|
12
|
+
*
|
|
13
|
+
* **Selection is keyed on `row-key`, so it survives a sort and a page.**
|
|
14
|
+
* `app.css` paints the chosen row through the parts the table names for it.
|
|
15
|
+
*/
|
|
16
|
+
@Component({
|
|
17
|
+
host: { class: 'stack' },
|
|
18
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
19
|
+
selector: 'div[app-overview]',
|
|
20
|
+
templateUrl: './overview.html',
|
|
21
|
+
})
|
|
22
|
+
export class Overview {
|
|
23
|
+
protected readonly docs = DOCS
|
|
24
|
+
protected readonly members = members
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Injectable, signal } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
/* **The whole router, which is a hash and a listener.**
|
|
4
|
+
|
|
5
|
+
Three pages need `location.hash` and one event. Swap this file for the
|
|
6
|
+
Angular router when you want lazy routes or guards: `withHashLocation()`
|
|
7
|
+
keeps these addresses.
|
|
8
|
+
|
|
9
|
+
A hash, because the History API wants a server that answers every path with
|
|
10
|
+
the same document, and wherever you deploy has to be told to. A hash asks
|
|
11
|
+
nothing of it. Each link in the app is a plain anchor to `#/` and a path. */
|
|
12
|
+
|
|
13
|
+
/** The current path, read from the hash, so the first page is `/`. */
|
|
14
|
+
const read = (): string => window.location.hash.slice(1) || '/'
|
|
15
|
+
|
|
16
|
+
/** The current path, kept current as the hash changes. */
|
|
17
|
+
@Injectable({ providedIn: 'root' })
|
|
18
|
+
export class Pathname {
|
|
19
|
+
readonly current = signal(read())
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
window.addEventListener('hashchange', () => this.current.set(read()))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<header class="hero">
|
|
2
|
+
<helix-text emphasis="muted" variant="label">Form</helix-text>
|
|
3
|
+
<helix-text as="h1" variant="heading">Join the roster</helix-text>
|
|
4
|
+
<helix-text as="p" class="lede" emphasis="muted" variant="body">
|
|
5
|
+
Five controls, and a <helix-code><form></helix-code> reads every one of them the way
|
|
6
|
+
it reads an <helix-code>input</helix-code>.
|
|
7
|
+
</helix-text>
|
|
8
|
+
</header>
|
|
9
|
+
|
|
10
|
+
<!-- The form takes a column of its own and the notes take what is left. At
|
|
11
|
+
390 the two stack, and the notes read as a postscript. -->
|
|
12
|
+
<div class="split">
|
|
13
|
+
<form class="form" (submit)="join($event)">
|
|
14
|
+
<helix-input autocomplete="name" label="Name" name="name" required></helix-input>
|
|
15
|
+
|
|
16
|
+
<!-- Validation is the platform's: `type` and `required` are set on the
|
|
17
|
+
real input inside, and its own message shows once the field has been
|
|
18
|
+
touched. Set `feedback` to say it in your own words. -->
|
|
19
|
+
<helix-input
|
|
20
|
+
hint="Where we send the welcome note."
|
|
21
|
+
label="Email"
|
|
22
|
+
name="email"
|
|
23
|
+
required
|
|
24
|
+
type="email"
|
|
25
|
+
></helix-input>
|
|
26
|
+
|
|
27
|
+
<!-- `(helix-before-change)` binds with `addEventListener`, so it takes
|
|
28
|
+
the event's own name. -->
|
|
29
|
+
<helix-input
|
|
30
|
+
hint="Lowercase and hyphens, whatever you type."
|
|
31
|
+
label="Handle"
|
|
32
|
+
name="handle"
|
|
33
|
+
[value]="handle()"
|
|
34
|
+
(helix-before-change)="takeHandle($event)"
|
|
35
|
+
></helix-input>
|
|
36
|
+
|
|
37
|
+
<helix-select label="Team" name="team" placeholder="Pick one" required>
|
|
38
|
+
@for (team of teams; track team) {
|
|
39
|
+
<helix-option [value]="team">{{ team }}</helix-option>
|
|
40
|
+
}
|
|
41
|
+
</helix-select>
|
|
42
|
+
|
|
43
|
+
<!-- A switch is a thing happening now, which is why it says on and off.
|
|
44
|
+
It still submits, as `on`. -->
|
|
45
|
+
<helix-switch label="Send me the weekly digest" name="digest"></helix-switch>
|
|
46
|
+
|
|
47
|
+
<helix-button emphasis="solid" tone="primary" type="submit">Join</helix-button>
|
|
48
|
+
</form>
|
|
49
|
+
|
|
50
|
+
<aside class="notes">
|
|
51
|
+
<!-- The one fact you need before typing your name into a page you have
|
|
52
|
+
had for five minutes, so it is a callout. `tone` is the meaning,
|
|
53
|
+
and the component draws it with a tint and a bar on the inline
|
|
54
|
+
start, so it survives being read in grayscale. -->
|
|
55
|
+
<helix-callout tone="info">
|
|
56
|
+
The form posts nothing. Submit it, and a message shows exactly what a server would receive.
|
|
57
|
+
</helix-callout>
|
|
58
|
+
<helix-text as="h2" variant="title">What the form shows</helix-text>
|
|
59
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
60
|
+
Every control is a form-associated custom element, so the submit reads them all with
|
|
61
|
+
<helix-code>new FormData(form)</helix-code>. Reset the form and they all go back,
|
|
62
|
+
the way an <helix-code>input</helix-code> does.
|
|
63
|
+
</helix-text>
|
|
64
|
+
<helix-text as="p" emphasis="muted" variant="body">
|
|
65
|
+
Leave the email wrong and the browser shows its own message, in your language. Handle is the
|
|
66
|
+
controlled field: the page refuses each change and writes back a cleaned value.
|
|
67
|
+
</helix-text>
|
|
68
|
+
<a data-link [href]="docs.forms">Forms</a>
|
|
69
|
+
</aside>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- **The region is rendered whether or not there is anything in it.**
|
|
73
|
+
Assistive technology subscribes to a live region, and one that arrives
|
|
74
|
+
already holding its message is often never announced. A dismissed
|
|
75
|
+
message asks to go through `helix-dismiss`. -->
|
|
76
|
+
<helix-toast>
|
|
77
|
+
@for (message of messages(); track message.id) {
|
|
78
|
+
<helix-toast-item
|
|
79
|
+
dismissible
|
|
80
|
+
[duration]="8000"
|
|
81
|
+
tone="success"
|
|
82
|
+
(helix-dismiss)="dismiss(message.id)"
|
|
83
|
+
>
|
|
84
|
+
<helix-flex direction="column" gap="2xs">
|
|
85
|
+
<helix-text variant="body-strong">Read by the form</helix-text>
|
|
86
|
+
<helix-text variant="code">{{ message.text }}</helix-text>
|
|
87
|
+
</helix-flex>
|
|
88
|
+
</helix-toast-item>
|
|
89
|
+
}
|
|
90
|
+
</helix-toast>
|