create-bestax 2.2.0 → 3.1.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +27 -6
- package/dist/project-creator.d.ts +2 -0
- package/dist/project-creator.d.ts.map +1 -1
- package/dist/project-creator.js +27 -3
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +9 -0
- package/package.json +4 -3
- package/templates/skills/bestax-custom-component/SKILL.md +389 -0
- package/templates/skills/bestax-custom-component/references/api.md +77 -0
- package/templates/skills/bestax-custom-component/references/patterns.md +133 -0
- package/templates/skills/bestax-form/SKILL.md +209 -0
- package/templates/skills/bestax-form/references/api.md +102 -0
- package/templates/skills/bestax-form/references/patterns.md +210 -0
- package/templates/skills/bestax-layout-scaffold/SKILL.md +66 -0
- package/templates/skills/bestax-layout-scaffold/examples/app-shell.tsx +80 -0
- package/templates/skills/bestax-layout-scaffold/examples/card-grid.tsx +98 -0
- package/templates/skills/bestax-layout-scaffold/examples/centered.tsx +56 -0
- package/templates/skills/bestax-layout-scaffold/examples/landing.tsx +77 -0
- package/templates/skills/bestax-layout-scaffold/references/archetypes.md +183 -0
- package/templates/skills/bestax-layout-scaffold/references/layout-components.md +181 -0
- package/templates/skills/bestax-theming/SKILL.md +73 -0
- package/templates/skills/bestax-theming/examples/dark-mode.tsx +38 -0
- package/templates/skills/bestax-theming/examples/theme-config.tsx +58 -0
- package/templates/skills/bestax-theming/references/css-variables.md +130 -0
- package/templates/skills/bestax-theming/references/themeable-components.md +74 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Reference: form patterns
|
|
2
|
+
|
|
3
|
+
## A complete multi-field form (controlled + manual validation)
|
|
4
|
+
|
|
5
|
+
No form library — state is plain React, validation is a function, errors surface through
|
|
6
|
+
`color` + `message` + `messageColor`.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import { useState } from 'react';
|
|
10
|
+
import {
|
|
11
|
+
Input,
|
|
12
|
+
Select,
|
|
13
|
+
TextArea,
|
|
14
|
+
Checkbox,
|
|
15
|
+
Button,
|
|
16
|
+
Field,
|
|
17
|
+
} from '@allxsmith/bestax-bulma';
|
|
18
|
+
|
|
19
|
+
interface Values {
|
|
20
|
+
name: string;
|
|
21
|
+
email: string;
|
|
22
|
+
country: string;
|
|
23
|
+
bio: string;
|
|
24
|
+
agree: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validate(v: Values) {
|
|
28
|
+
const errors: Partial<Record<keyof Values, string>> = {};
|
|
29
|
+
if (!v.name.trim()) errors.name = 'Name is required.';
|
|
30
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v.email))
|
|
31
|
+
errors.email = 'Enter a valid email.';
|
|
32
|
+
if (!v.country) errors.country = 'Pick a country.';
|
|
33
|
+
if (!v.agree) errors.agree = 'You must accept the terms.';
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function ProfileForm() {
|
|
38
|
+
const [values, setValues] = useState<Values>({
|
|
39
|
+
name: '',
|
|
40
|
+
email: '',
|
|
41
|
+
country: '',
|
|
42
|
+
bio: '',
|
|
43
|
+
agree: false,
|
|
44
|
+
});
|
|
45
|
+
const [submitted, setSubmitted] = useState(false);
|
|
46
|
+
|
|
47
|
+
const errors = validate(values);
|
|
48
|
+
const show = (k: keyof Values) => (submitted ? errors[k] : undefined);
|
|
49
|
+
const set = (k: keyof Values, val: Values[keyof Values]) =>
|
|
50
|
+
setValues(prev => ({ ...prev, [k]: val }));
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<form
|
|
54
|
+
onSubmit={e => {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
setSubmitted(true);
|
|
57
|
+
if (Object.keys(errors).length === 0) {
|
|
58
|
+
// submit values…
|
|
59
|
+
}
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<Input
|
|
63
|
+
label="Name"
|
|
64
|
+
value={values.name}
|
|
65
|
+
onChange={e => set('name', e.target.value)}
|
|
66
|
+
color={show('name') ? 'danger' : undefined}
|
|
67
|
+
message={show('name')}
|
|
68
|
+
messageColor="danger"
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<Input
|
|
72
|
+
label="Email"
|
|
73
|
+
type="email"
|
|
74
|
+
value={values.email}
|
|
75
|
+
onChange={e => set('email', e.target.value)}
|
|
76
|
+
color={show('email') ? 'danger' : undefined}
|
|
77
|
+
message={show('email')}
|
|
78
|
+
messageColor="danger"
|
|
79
|
+
iconLeftName="envelope"
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
<Select
|
|
83
|
+
label="Country"
|
|
84
|
+
value={values.country}
|
|
85
|
+
onChange={e => set('country', e.target.value)}
|
|
86
|
+
color={show('country') ? 'danger' : undefined}
|
|
87
|
+
message={show('country')}
|
|
88
|
+
messageColor="danger"
|
|
89
|
+
>
|
|
90
|
+
<option value="">Select…</option>
|
|
91
|
+
<option value="us">United States</option>
|
|
92
|
+
<option value="ca">Canada</option>
|
|
93
|
+
</Select>
|
|
94
|
+
|
|
95
|
+
<TextArea
|
|
96
|
+
label="Bio"
|
|
97
|
+
value={values.bio}
|
|
98
|
+
onChange={e => set('bio', e.target.value)}
|
|
99
|
+
rows={3}
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
<Field>
|
|
103
|
+
<Checkbox
|
|
104
|
+
checked={values.agree}
|
|
105
|
+
onChange={e => set('agree', e.target.checked)}
|
|
106
|
+
>
|
|
107
|
+
{' '}
|
|
108
|
+
I accept the terms
|
|
109
|
+
</Checkbox>
|
|
110
|
+
{show('agree') && <p className="help is-danger">{show('agree')}</p>}
|
|
111
|
+
</Field>
|
|
112
|
+
|
|
113
|
+
<Button color="primary" type="submit" mt="4">
|
|
114
|
+
Save
|
|
115
|
+
</Button>
|
|
116
|
+
</form>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Notes:
|
|
122
|
+
|
|
123
|
+
- Validation runs every render as a pure function; `submitted` gates when errors are shown so the
|
|
124
|
+
form isn't red before the user interacts. You could gate per-field on blur instead.
|
|
125
|
+
- For the checkbox the convenience `message` prop isn't used; the help text is rendered manually
|
|
126
|
+
inside the `Field` — a good illustration of dropping to composition when needed.
|
|
127
|
+
|
|
128
|
+
## Grouped controls and addons
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
// Search box: input + button attached.
|
|
132
|
+
<Field hasAddons>
|
|
133
|
+
<Control isExpanded>
|
|
134
|
+
<InputBase placeholder="Search" />
|
|
135
|
+
</Control>
|
|
136
|
+
<Control>
|
|
137
|
+
<Button color="primary">Search</Button>
|
|
138
|
+
</Control>
|
|
139
|
+
</Field>
|
|
140
|
+
|
|
141
|
+
// Two controls on one row.
|
|
142
|
+
<Field grouped>
|
|
143
|
+
<Control>
|
|
144
|
+
<InputBase placeholder="First" />
|
|
145
|
+
</Control>
|
|
146
|
+
<Control>
|
|
147
|
+
<InputBase placeholder="Last" />
|
|
148
|
+
</Control>
|
|
149
|
+
</Field>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Horizontal field with explicit label/body
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<Field horizontal>
|
|
156
|
+
<Field.Label size="normal">Email</Field.Label>
|
|
157
|
+
<Field.Body>
|
|
158
|
+
<Control iconLeftName="envelope" hasIconsLeft>
|
|
159
|
+
<InputBase type="email" placeholder="you@example.com" />
|
|
160
|
+
</Control>
|
|
161
|
+
</Field.Body>
|
|
162
|
+
</Field>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Advanced inputs
|
|
166
|
+
|
|
167
|
+
These are controlled the same way — own the value, pass `value`/`onChange`.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// Autocomplete — onInput fires on typing, onSelect when a suggestion is chosen.
|
|
171
|
+
const [city, setCity] = useState('');
|
|
172
|
+
<Autocomplete
|
|
173
|
+
value={city}
|
|
174
|
+
onInput={setCity}
|
|
175
|
+
onSelect={item =>
|
|
176
|
+
setCity(typeof item === 'string' ? item : (item?.value ?? ''))
|
|
177
|
+
}
|
|
178
|
+
data={['Austin', 'Boston', 'Chicago']}
|
|
179
|
+
openOnFocus
|
|
180
|
+
/>;
|
|
181
|
+
|
|
182
|
+
// Slider (single and dual thumb)
|
|
183
|
+
const [volume, setVolume] = useState(50);
|
|
184
|
+
<Slider value={volume} onChange={setVolume} min={0} max={100} tooltip="auto" />;
|
|
185
|
+
|
|
186
|
+
const [range, setRange] = useState<[number, number]>([20, 80]);
|
|
187
|
+
<Slider range value={range} onChange={setRange} min={0} max={100} />;
|
|
188
|
+
|
|
189
|
+
// Numberinput
|
|
190
|
+
const [qty, setQty] = useState(1);
|
|
191
|
+
<Numberinput value={qty} onChange={setQty} min={1} max={10} step={1} />;
|
|
192
|
+
|
|
193
|
+
// Rate
|
|
194
|
+
const [stars, setStars] = useState(3);
|
|
195
|
+
<Rate value={stars} onChange={setStars} max={5} />;
|
|
196
|
+
|
|
197
|
+
// Taginput — tags can be strings or { value, label } objects; onChange yields TaginputTag[].
|
|
198
|
+
const [tags, setTags] = useState<string[]>(['react']);
|
|
199
|
+
<Taginput
|
|
200
|
+
value={tags}
|
|
201
|
+
onChange={next =>
|
|
202
|
+
setTags(next.map(t => (typeof t === 'string' ? t : t.value)))
|
|
203
|
+
}
|
|
204
|
+
data={['react', 'bulma', 'ts']}
|
|
205
|
+
/>;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Each renders inside Bulma's field/control structure already, so wrap them in a `Field` only when
|
|
209
|
+
you need a label or grouped layout. Reflect validation on them with the same `color` /
|
|
210
|
+
`message` / `messageColor` approach used for text inputs.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bestax-layout-scaffold
|
|
3
|
+
description: Scaffold a complete, responsive page layout with @allxsmith/bestax-bulma — app shells/dashboards, marketing/landing pages, centered auth/settings pages, and card-grid catalogs. Use when building a full page or overall app layout (not a single component).
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Scaffolding a page layout with @allxsmith/bestax-bulma
|
|
8
|
+
|
|
9
|
+
Turn a high-level request ("admin dashboard", "landing page", "login screen", "product catalog")
|
|
10
|
+
into a complete responsive page built from bestax-bulma layout components.
|
|
11
|
+
|
|
12
|
+
## Behavioral rule
|
|
13
|
+
|
|
14
|
+
Select an archetype from the request and build it in one shot. Do **not** ask layout questions
|
|
15
|
+
("how many columns?", "where should the nav go?", "what width?") — infer the structure from the
|
|
16
|
+
request and proceed. The archetype determines the structure; fill it with the requested content.
|
|
17
|
+
|
|
18
|
+
Ask **at most one** clarifying question, and only for a high-level fork the request genuinely does
|
|
19
|
+
not imply: whether the page is **public-facing** (marketing) or an **internal tool** (authenticated
|
|
20
|
+
app). When the request already signals this ("dashboard", "admin", "landing", "login", "pricing"),
|
|
21
|
+
skip the question and default.
|
|
22
|
+
|
|
23
|
+
## Select an archetype
|
|
24
|
+
|
|
25
|
+
| Request signals | Archetype |
|
|
26
|
+
| ---------------------------------------------------------------------- | ------------- |
|
|
27
|
+
| dashboard, admin, console, internal tool, authenticated app, "sidebar" | **App shell** |
|
|
28
|
+
| landing, marketing, homepage, product/pricing page, public site | **Landing** |
|
|
29
|
+
| login, sign up, auth, settings, checkout, a single focused form | **Centered** |
|
|
30
|
+
| catalog, gallery, products, listing, "grid of cards", search results | **Card grid** |
|
|
31
|
+
|
|
32
|
+
Default when ambiguous: internal tool → App shell; public-facing → Landing; one focused task →
|
|
33
|
+
Centered; a collection of items → Card grid. For mixed requests, pick the dominant intent (e.g.
|
|
34
|
+
"admin dashboard with a product list" → App shell whose main column holds a Card grid).
|
|
35
|
+
|
|
36
|
+
## Approach
|
|
37
|
+
|
|
38
|
+
- Compose pages from the shipped layout components — `Container`, `Section`, `Hero`, `Footer`,
|
|
39
|
+
`Level`, `Columns`/`Column`, `Navbar`, `Menu`, `Card`. There is **no `Tile` component** — build
|
|
40
|
+
grids with `Columns`/`Column`.
|
|
41
|
+
- Rely on Bulma's responsive defaults: `Columns` sit side by side on tablet and up and stack on
|
|
42
|
+
mobile. Add responsive `size*` props only to tune the breakpoints.
|
|
43
|
+
- For a `fixed="top"` `Navbar`, add the `has-navbar-fixed-top` class to `<html>` so content is not
|
|
44
|
+
hidden behind it — the library does not do this automatically.
|
|
45
|
+
|
|
46
|
+
## References
|
|
47
|
+
|
|
48
|
+
- `references/layout-components.md` — the layout component inventory: real prop names, types, and
|
|
49
|
+
accepted values, plus subcomponent nesting.
|
|
50
|
+
- `references/archetypes.md` — the four archetypes: selection criteria, JSX skeleton, and responsive
|
|
51
|
+
behavior.
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
- `examples/app-shell.tsx` — fixed `Navbar` + sidebar `Menu` + content (dashboard).
|
|
56
|
+
- `examples/landing.tsx` — `Hero` + `Section`s + `Footer`.
|
|
57
|
+
- `examples/centered.tsx` — centered single column (auth/settings).
|
|
58
|
+
- `examples/card-grid.tsx` — multiline `Columns` of `Card`s (catalog).
|
|
59
|
+
|
|
60
|
+
## Checklist
|
|
61
|
+
|
|
62
|
+
- [ ] Map the request to one archetype; do not ask layout questions.
|
|
63
|
+
- [ ] Wrap page content in `Container` (+ `Section` for vertical rhythm).
|
|
64
|
+
- [ ] Use `Columns`/`Column` for side-by-side layout; rely on the mobile stack default.
|
|
65
|
+
- [ ] For a fixed navbar, add `has-navbar-fixed-top` to `<html>`.
|
|
66
|
+
- [ ] Do not use `Tile` — it is not shipped.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// App shell with sidebar — fixed top Navbar + a narrow Menu column beside the
|
|
2
|
+
// main content. The admin/dashboard default.
|
|
3
|
+
//
|
|
4
|
+
// A fixed-top navbar needs the `has-navbar-fixed-top` class on <html> so the page
|
|
5
|
+
// is padded below it — Bulma requires this and the library does NOT add it for
|
|
6
|
+
// you. The columns sit side by side on tablet and up, and stack (menu above
|
|
7
|
+
// content) on mobile.
|
|
8
|
+
import React, { useEffect, useState } from 'react';
|
|
9
|
+
import {
|
|
10
|
+
Navbar,
|
|
11
|
+
Menu,
|
|
12
|
+
Container,
|
|
13
|
+
Columns,
|
|
14
|
+
Column,
|
|
15
|
+
Section,
|
|
16
|
+
Title,
|
|
17
|
+
Box,
|
|
18
|
+
} from '@allxsmith/bestax-bulma';
|
|
19
|
+
|
|
20
|
+
export default function AdminShell() {
|
|
21
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
document.documentElement.classList.add('has-navbar-fixed-top');
|
|
25
|
+
return () => {
|
|
26
|
+
document.documentElement.classList.remove('has-navbar-fixed-top');
|
|
27
|
+
};
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<Navbar fixed="top" color="dark">
|
|
33
|
+
<Navbar.Brand>
|
|
34
|
+
<Navbar.Item href="#">Acme Admin</Navbar.Item>
|
|
35
|
+
<Navbar.Burger
|
|
36
|
+
active={menuOpen}
|
|
37
|
+
onClick={() => setMenuOpen(open => !open)}
|
|
38
|
+
aria-label="menu"
|
|
39
|
+
aria-expanded={menuOpen}
|
|
40
|
+
/>
|
|
41
|
+
</Navbar.Brand>
|
|
42
|
+
<Navbar.Menu active={menuOpen}>
|
|
43
|
+
<Navbar.End>
|
|
44
|
+
<Navbar.Item href="#">Docs</Navbar.Item>
|
|
45
|
+
<Navbar.Item href="#">Account</Navbar.Item>
|
|
46
|
+
</Navbar.End>
|
|
47
|
+
</Navbar.Menu>
|
|
48
|
+
</Navbar>
|
|
49
|
+
|
|
50
|
+
<Container fluid>
|
|
51
|
+
<Columns>
|
|
52
|
+
<Column size={3} sizeWidescreen={2}>
|
|
53
|
+
<Menu>
|
|
54
|
+
<Menu.Label>General</Menu.Label>
|
|
55
|
+
<Menu.List>
|
|
56
|
+
<Menu.Item active href="#">
|
|
57
|
+
Dashboard
|
|
58
|
+
</Menu.Item>
|
|
59
|
+
<Menu.Item href="#">Customers</Menu.Item>
|
|
60
|
+
<Menu.Item href="#">Orders</Menu.Item>
|
|
61
|
+
</Menu.List>
|
|
62
|
+
<Menu.Label>Admin</Menu.Label>
|
|
63
|
+
<Menu.List>
|
|
64
|
+
<Menu.Item href="#">Team</Menu.Item>
|
|
65
|
+
<Menu.Item href="#">Settings</Menu.Item>
|
|
66
|
+
</Menu.List>
|
|
67
|
+
</Menu>
|
|
68
|
+
</Column>
|
|
69
|
+
|
|
70
|
+
<Column>
|
|
71
|
+
<Section>
|
|
72
|
+
<Title size="3">Dashboard</Title>
|
|
73
|
+
<Box>Main content area — drop dashboard widgets here.</Box>
|
|
74
|
+
</Section>
|
|
75
|
+
</Column>
|
|
76
|
+
</Columns>
|
|
77
|
+
</Container>
|
|
78
|
+
</>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Card grid / catalog page — a collection of similar items.
|
|
2
|
+
// `<Columns isMultiline>` wraps cards onto new rows; the responsive column sizes
|
|
3
|
+
// give 1 card per row on mobile, 2 on tablet, 3 on desktop.
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import {
|
|
6
|
+
Section,
|
|
7
|
+
Container,
|
|
8
|
+
Title,
|
|
9
|
+
Columns,
|
|
10
|
+
Column,
|
|
11
|
+
Card,
|
|
12
|
+
} from '@allxsmith/bestax-bulma';
|
|
13
|
+
|
|
14
|
+
interface Product {
|
|
15
|
+
id: number;
|
|
16
|
+
name: string;
|
|
17
|
+
price: string;
|
|
18
|
+
image: string;
|
|
19
|
+
blurb: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PRODUCTS: Product[] = [
|
|
23
|
+
{
|
|
24
|
+
id: 1,
|
|
25
|
+
name: 'Aurora Lamp',
|
|
26
|
+
price: '$48',
|
|
27
|
+
image: 'https://picsum.photos/seed/1/600/400',
|
|
28
|
+
blurb: 'Warm ambient light.',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 2,
|
|
32
|
+
name: 'Drift Chair',
|
|
33
|
+
price: '$220',
|
|
34
|
+
image: 'https://picsum.photos/seed/2/600/400',
|
|
35
|
+
blurb: 'Ergonomic and airy.',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 3,
|
|
39
|
+
name: 'Stone Mug',
|
|
40
|
+
price: '$18',
|
|
41
|
+
image: 'https://picsum.photos/seed/3/600/400',
|
|
42
|
+
blurb: 'Hand-thrown ceramic.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 4,
|
|
46
|
+
name: 'Field Notebook',
|
|
47
|
+
price: '$12',
|
|
48
|
+
image: 'https://picsum.photos/seed/4/600/400',
|
|
49
|
+
blurb: 'Pocket-sized, lined.',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 5,
|
|
53
|
+
name: 'Trail Bottle',
|
|
54
|
+
price: '$26',
|
|
55
|
+
image: 'https://picsum.photos/seed/5/600/400',
|
|
56
|
+
blurb: 'Insulated steel.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 6,
|
|
60
|
+
name: 'Linen Throw',
|
|
61
|
+
price: '$64',
|
|
62
|
+
image: 'https://picsum.photos/seed/6/600/400',
|
|
63
|
+
blurb: 'Soft, breathable.',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export default function CatalogPage() {
|
|
68
|
+
return (
|
|
69
|
+
<Section>
|
|
70
|
+
<Container>
|
|
71
|
+
<Title size="3" mb="5">
|
|
72
|
+
Catalog
|
|
73
|
+
</Title>
|
|
74
|
+
<Columns isMultiline>
|
|
75
|
+
{PRODUCTS.map(product => (
|
|
76
|
+
<Column
|
|
77
|
+
key={product.id}
|
|
78
|
+
sizeMobile="full"
|
|
79
|
+
sizeTablet="half"
|
|
80
|
+
sizeDesktop="one-third"
|
|
81
|
+
>
|
|
82
|
+
<Card
|
|
83
|
+
image={product.image}
|
|
84
|
+
imageAlt={product.name}
|
|
85
|
+
header={product.name}
|
|
86
|
+
footer={
|
|
87
|
+
<span className="card-footer-item">{product.price}</span>
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
<p>{product.blurb}</p>
|
|
91
|
+
</Card>
|
|
92
|
+
</Column>
|
|
93
|
+
))}
|
|
94
|
+
</Columns>
|
|
95
|
+
</Container>
|
|
96
|
+
</Section>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Centered single-column page — auth, settings, focused forms.
|
|
2
|
+
// A narrow column is centered with `<Columns isCentered>`; on mobile the column
|
|
3
|
+
// becomes full width automatically.
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import {
|
|
6
|
+
Section,
|
|
7
|
+
Container,
|
|
8
|
+
Columns,
|
|
9
|
+
Column,
|
|
10
|
+
Box,
|
|
11
|
+
Title,
|
|
12
|
+
SubTitle,
|
|
13
|
+
Input,
|
|
14
|
+
Button,
|
|
15
|
+
} from '@allxsmith/bestax-bulma';
|
|
16
|
+
|
|
17
|
+
export default function LoginPage() {
|
|
18
|
+
return (
|
|
19
|
+
<Section>
|
|
20
|
+
<Container>
|
|
21
|
+
<Columns isCentered>
|
|
22
|
+
<Column
|
|
23
|
+
size="half"
|
|
24
|
+
sizeTablet="two-thirds"
|
|
25
|
+
sizeDesktop="half"
|
|
26
|
+
sizeWidescreen="one-third"
|
|
27
|
+
>
|
|
28
|
+
<Box>
|
|
29
|
+
<Title size="4" textAlign="centered">
|
|
30
|
+
Sign in
|
|
31
|
+
</Title>
|
|
32
|
+
<SubTitle size="6" textAlign="centered" textColor="grey">
|
|
33
|
+
Welcome back
|
|
34
|
+
</SubTitle>
|
|
35
|
+
<Input
|
|
36
|
+
label="Email"
|
|
37
|
+
type="email"
|
|
38
|
+
iconLeftName="envelope"
|
|
39
|
+
placeholder="you@example.com"
|
|
40
|
+
/>
|
|
41
|
+
<Input
|
|
42
|
+
label="Password"
|
|
43
|
+
type="password"
|
|
44
|
+
iconLeftName="lock"
|
|
45
|
+
placeholder="••••••••"
|
|
46
|
+
/>
|
|
47
|
+
<Button color="primary" isFullWidth mt="4">
|
|
48
|
+
Sign in
|
|
49
|
+
</Button>
|
|
50
|
+
</Box>
|
|
51
|
+
</Column>
|
|
52
|
+
</Columns>
|
|
53
|
+
</Container>
|
|
54
|
+
</Section>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Marketing / landing page — Hero + stacked Sections + Footer.
|
|
2
|
+
// Each Section stacks vertically; the feature Columns collapse to one per row on
|
|
3
|
+
// mobile (Bulma columns stack below the tablet breakpoint).
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import {
|
|
6
|
+
Hero,
|
|
7
|
+
Section,
|
|
8
|
+
Container,
|
|
9
|
+
Footer,
|
|
10
|
+
Columns,
|
|
11
|
+
Column,
|
|
12
|
+
Box,
|
|
13
|
+
Title,
|
|
14
|
+
SubTitle,
|
|
15
|
+
Content,
|
|
16
|
+
Button,
|
|
17
|
+
Buttons,
|
|
18
|
+
} from '@allxsmith/bestax-bulma';
|
|
19
|
+
|
|
20
|
+
const FEATURES = [
|
|
21
|
+
{ title: 'Fast', body: 'Ship pages in minutes, not days.' },
|
|
22
|
+
{ title: 'Responsive', body: 'Looks right on every screen by default.' },
|
|
23
|
+
{ title: 'Composable', body: 'Build from small, predictable pieces.' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export default function LandingPage() {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<Hero color="primary" size="medium">
|
|
30
|
+
<Hero.Body>
|
|
31
|
+
<Container textAlign="centered">
|
|
32
|
+
<Title size="1">Ship faster with Acme</Title>
|
|
33
|
+
<SubTitle size="3">
|
|
34
|
+
The all-in-one platform for modern teams.
|
|
35
|
+
</SubTitle>
|
|
36
|
+
<Buttons isCentered mt="5">
|
|
37
|
+
<Button color="light" size="large">
|
|
38
|
+
Get started
|
|
39
|
+
</Button>
|
|
40
|
+
<Button color="primary" isInverted isOutlined size="large">
|
|
41
|
+
Live demo
|
|
42
|
+
</Button>
|
|
43
|
+
</Buttons>
|
|
44
|
+
</Container>
|
|
45
|
+
</Hero.Body>
|
|
46
|
+
</Hero>
|
|
47
|
+
|
|
48
|
+
<Section size="large">
|
|
49
|
+
<Container>
|
|
50
|
+
<Title size="3" textAlign="centered" mb="6">
|
|
51
|
+
Why Acme
|
|
52
|
+
</Title>
|
|
53
|
+
<Columns>
|
|
54
|
+
{FEATURES.map(feature => (
|
|
55
|
+
<Column key={feature.title}>
|
|
56
|
+
<Box>
|
|
57
|
+
<Title size="5">{feature.title}</Title>
|
|
58
|
+
<Content>{feature.body}</Content>
|
|
59
|
+
</Box>
|
|
60
|
+
</Column>
|
|
61
|
+
))}
|
|
62
|
+
</Columns>
|
|
63
|
+
</Container>
|
|
64
|
+
</Section>
|
|
65
|
+
|
|
66
|
+
<Footer>
|
|
67
|
+
<Container>
|
|
68
|
+
<Content textAlign="centered">
|
|
69
|
+
<p>
|
|
70
|
+
<strong>Acme</strong> — built with bestax-bulma.
|
|
71
|
+
</p>
|
|
72
|
+
</Content>
|
|
73
|
+
</Container>
|
|
74
|
+
</Footer>
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
}
|