@markout-lang/bootstrap-kit 0.2.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 +331 -0
- package/all.htm +44 -0
- package/package.json +33 -0
- package/parts/accordion.htm +81 -0
- package/parts/alert.htm +48 -0
- package/parts/badge.htm +33 -0
- package/parts/base.htm +71 -0
- package/parts/breadcrumb.htm +44 -0
- package/parts/button.htm +162 -0
- package/parts/card.htm +103 -0
- package/parts/carousel.htm +87 -0
- package/parts/check.htm +164 -0
- package/parts/collapse.htm +31 -0
- package/parts/dropdown.htm +86 -0
- package/parts/image.htm +46 -0
- package/parts/input.htm +178 -0
- package/parts/list-group.htm +58 -0
- package/parts/modal.htm +119 -0
- package/parts/nav.htm +89 -0
- package/parts/navbar.htm +82 -0
- package/parts/offcanvas.htm +90 -0
- package/parts/pagination.htm +73 -0
- package/parts/placeholder.htm +34 -0
- package/parts/popover.htm +43 -0
- package/parts/progress.htm +54 -0
- package/parts/scrollspy.htm +33 -0
- package/parts/select.htm +72 -0
- package/parts/spinner.htm +32 -0
- package/parts/table.htm +70 -0
- package/parts/theme.htm +70 -0
- package/parts/toast.htm +115 -0
- package/parts/tooltip.htm +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Bootstrap Kit
|
|
2
|
+
|
|
3
|
+
Bootstrap 5.3 as Markout components — one part per component, following
|
|
4
|
+
[Bootstrap's own cheatsheet](https://getbootstrap.com/docs/5.3/examples/cheatsheet/).
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install @markout-lang/bootstrap-kit # in a project
|
|
8
|
+
npm install -g @markout-lang/bootstrap-kit # for a bare docroot
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See [where kits are found](../../docs/reference/cli.md#where-kits-are-found):
|
|
12
|
+
a project's own kits are used whole, and a globally installed one is visible
|
|
13
|
+
only to a docroot that has none of its own.
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<head>
|
|
17
|
+
<:import src="/npm/@markout-lang/bootstrap-kit/all.htm" />
|
|
18
|
+
</head>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`/npm/` names the package the fragment came from, and is resolved at compile
|
|
22
|
+
time; everything the kit publishes is then addressed at `/bootstrap-kit`, the
|
|
23
|
+
logical root it declares. See [npm kits](../../docs/design/npm-kits.md) —
|
|
24
|
+
including the other case, a kit vendored into a docroot, which is imported by
|
|
25
|
+
its path instead.
|
|
26
|
+
|
|
27
|
+
`all.htm` pulls in everything. Each part imports `base.htm` itself and a file
|
|
28
|
+
is only imported once per page, so importing parts by hand never leaves
|
|
29
|
+
Bootstrap out:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<:import src="/npm/@markout-lang/bootstrap-kit/parts/button.htm" />
|
|
33
|
+
<:import src="/npm/@markout-lang/bootstrap-kit/parts/card.htm" />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Run the showcase — every component below, live — with:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
npm run dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That serves [the site](../../sites/site/), which is where the pages built on
|
|
43
|
+
this kit live: `/demos/kitchen-sink.html`, every component one after another;
|
|
44
|
+
and `/demos/orbit.html`, an operations dashboard built out of them, which is
|
|
45
|
+
what they look like wired to one page's data.
|
|
46
|
+
|
|
47
|
+
The site is a plain Express app rather than markout's `Server`, because Orbit
|
|
48
|
+
is a whole application: it has an API of its own, served from a fake
|
|
49
|
+
in-memory database (`orbit-db.ts`), and markout is the middleware that
|
|
50
|
+
renders its pages. Orbit reads that API with `std-data` from the std kit,
|
|
51
|
+
which fetches while the page renders — so the served console is complete and
|
|
52
|
+
the browser asks for nothing.
|
|
53
|
+
|
|
54
|
+
Orbit is four files, which is the shape an application takes:
|
|
55
|
+
`demos/orbit.html` for its state, layout and logic; `demos/orbit/components.htm`
|
|
56
|
+
for the tags it defines on top of the kit's; `demos/orbit/sources.htm` for
|
|
57
|
+
where its data comes from; and `server.ts` for its API. The first is imported,
|
|
58
|
+
the second included — a definition wants to arrive once per page however often
|
|
59
|
+
it is named, and an instance wants to be spliced exactly where it is written,
|
|
60
|
+
because that is where its name resolves.
|
|
61
|
+
|
|
62
|
+
`orbit/sources.htm` also shows the token pattern working for an application's
|
|
63
|
+
own fragment: its root carries `:apiBase="/api"`, which lands on whatever
|
|
64
|
+
contains the `<:include>` unless that element declares it, so a page points
|
|
65
|
+
Orbit at another host without the fragment changing. That is the same
|
|
66
|
+
mechanism as `bsCssUrl` below — not something kits get and applications
|
|
67
|
+
don't.
|
|
68
|
+
|
|
69
|
+
## Tests
|
|
70
|
+
|
|
71
|
+
`packages/cli/test/kits/bootstrap-kit.test.ts`, in two tiers:
|
|
72
|
+
|
|
73
|
+
- **compiled**, which is most of it: every part compiles on its own, the
|
|
74
|
+
showcase compiles and server-renders with nothing reported, and the id
|
|
75
|
+
wiring is checked mechanically — every `aria-controls`, `aria-labelledby`,
|
|
76
|
+
`for`, `data-bs-target` and `data-bs-parent` has to name an element that
|
|
77
|
+
exists. That last one is the kit's whole reason for existing, so it is
|
|
78
|
+
worth a test that can't be argued with.
|
|
79
|
+
- **live**, in Playwright: the value-driven components actually driven, and
|
|
80
|
+
a stubbed Bootstrap asserting the plugin calls. Skipped when no browser is
|
|
81
|
+
installed (`npx playwright install chromium`).
|
|
82
|
+
|
|
83
|
+
Nothing reaches the network: the tests set the URL tokens to local files.
|
|
84
|
+
|
|
85
|
+
## What is and isn't a component
|
|
86
|
+
|
|
87
|
+
A component earns its place by removing something a person would otherwise
|
|
88
|
+
have to keep right by hand:
|
|
89
|
+
|
|
90
|
+
- **id wiring.** A modal, an accordion, a carousel, a navbar toggler, a
|
|
91
|
+
dropdown menu and a tab panel all connect two elements by `id`. Here the id
|
|
92
|
+
comes from `$id`, which is unique per instance, so a component can appear
|
|
93
|
+
twice on a page.
|
|
94
|
+
- **accessibility.** `role`, `aria-label`, `aria-current`, `aria-expanded`,
|
|
95
|
+
`visually-hidden` text: written once, not per use.
|
|
96
|
+
- **repetition.** A nav, a breadcrumb, a table, a dropdown menu, a carousel
|
|
97
|
+
and a list group are one shape repeated over a list, so their API is that
|
|
98
|
+
list.
|
|
99
|
+
|
|
100
|
+
Typography, spacing, the grid and the colour utilities are **not** components.
|
|
101
|
+
They are already one class each, and a tag that only forwards a class is a
|
|
102
|
+
name to learn for nothing.
|
|
103
|
+
|
|
104
|
+
## Conventions
|
|
105
|
+
|
|
106
|
+
Every component follows the same rules, so knowing one is close to knowing
|
|
107
|
+
all of them.
|
|
108
|
+
|
|
109
|
+
**Parameters for the chrome, the slot for the content.** What a card *has* —
|
|
110
|
+
a title, a footer, an image — is a parameter. What it *contains* is slotted.
|
|
111
|
+
|
|
112
|
+
**`:extra` adds classes.** A `class` written at a usage site *replaces* the
|
|
113
|
+
one a definition sets, which is the language's rule and not something this
|
|
114
|
+
kit overrides. So every component takes `:extra` for the utility classes a
|
|
115
|
+
caller wants on top:
|
|
116
|
+
|
|
117
|
+
```html
|
|
118
|
+
<bs-alert :variant="warning" :extra="mb-0">Careful</bs-alert>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Comments in a tag: `//` for one line, `/* … */` for more.** Both are
|
|
122
|
+
stripped at parse time. A run of `//` lines reads as a stack of fragments;
|
|
123
|
+
one block says it once.
|
|
124
|
+
|
|
125
|
+
**Lists are arrays.** `:items`, `:options`, `:columns`, `:rows`, `:slides`.
|
|
126
|
+
The default value of each is the shape it expects — read the definition to
|
|
127
|
+
see it.
|
|
128
|
+
|
|
129
|
+
**Callbacks are values, not events.** `:select`, `:check` and `:link` are
|
|
130
|
+
functions the component calls. `:on-click` and friends stay what they are in
|
|
131
|
+
the language: DOM events.
|
|
132
|
+
|
|
133
|
+
**A plugin is built on attach and released on detach.** The five components
|
|
134
|
+
that hand their element to Bootstrap's JS — `bs-modal`, `bs-offcanvas`,
|
|
135
|
+
`bs-toast`, `bs-tooltip`, `bs-popover` — do it from `:did-attach` and undo it
|
|
136
|
+
from `:will-detach`, not from `:did-init`/`:will-dispose`. A `:for-data`
|
|
137
|
+
region takes its markup out of the page without its scope going anywhere,
|
|
138
|
+
and a plugin left holding a removed element leaves its backdrop, its popper
|
|
139
|
+
and the page's scroll lock behind.
|
|
140
|
+
|
|
141
|
+
**Values are read and written.** `bs-input`, `bs-select`, `bs-check`,
|
|
142
|
+
`bs-range`, `bs-modal`, `bs-offcanvas` and `bs-toast` keep `:value` or
|
|
143
|
+
`:open` in step with what is on screen. Name the instance and read it from
|
|
144
|
+
anywhere:
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<bs-input :aka="email" :label="Email" :type="email" />
|
|
148
|
+
<bs-button :disabled=${!email.value}>Send</bs-button>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
That holds however deeply the component sits: a `bs-toast :aka="saved"`
|
|
152
|
+
written inside a `bs-toast-container` is still named where you wrote it, so
|
|
153
|
+
`saved.open = true` reaches it from anywhere on the page.
|
|
154
|
+
|
|
155
|
+
**Optional regions are `:if`.** A region that exists only when a parameter
|
|
156
|
+
was given is written `:if=${header}`, and nothing inside it evaluates while
|
|
157
|
+
the condition is false — which is what makes `${user.name}` safe to write in
|
|
158
|
+
one. It asks the question JavaScript asks, so an unset parameter and an
|
|
159
|
+
empty string both count as absent:
|
|
160
|
+
|
|
161
|
+
```html
|
|
162
|
+
<bs-close :if=${dismissible} />
|
|
163
|
+
<span :if=${!split}>${label}</span>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`:else-if` and `:else` continue it, on the element immediately after. The
|
|
167
|
+
kit uses them where a component chooses between renderings rather than
|
|
168
|
+
merely omitting one — `bs-nav` between a tab button and a plain link:
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<button class="nav-link" :if=${toggle} ...>${item.name}</button>
|
|
172
|
+
<a class="nav-link" :else ...>${item.name}</a>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
and `bs-dropdown` between the three things an item can be:
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<hr class="dropdown-divider" :if=${item.divider}>
|
|
179
|
+
<h6 class="dropdown-header" :else-if=${item.header}>${item.header}</h6>
|
|
180
|
+
<a class="dropdown-item" :else ...>${item.name}</a>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`:for-data` is for the other case — there is something to show, and the body
|
|
184
|
+
wants it. It is `!= null` rather than truthy, so `0` and `''` stay data, and
|
|
185
|
+
it binds the item as `data`. The kit has no use for it: every optional region
|
|
186
|
+
here renders a parameter it already has a name for.
|
|
187
|
+
|
|
188
|
+
Optional parts of a component are parameters, and a named slot where markup
|
|
189
|
+
belongs — `bs-card`'s header is both: `:header` sets the text, and a
|
|
190
|
+
`:slot="header"` replaces it with markup. A `<:slot>` may sit inside a
|
|
191
|
+
`:for-data` but not inside a `:for-each`, which is what makes that possible.
|
|
192
|
+
|
|
193
|
+
## Where Bootstrap comes from
|
|
194
|
+
|
|
195
|
+
The CDN URLs and their hashes are tokens like any other, so a page points the
|
|
196
|
+
kit at its own copy without forking `base.htm`:
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<head ::bsCssUrl="/vendor/bootstrap.min.css"
|
|
200
|
+
::bsJsUrl="/vendor/bootstrap.bundle.min.js"
|
|
201
|
+
::bsCssIntegrity=${null}
|
|
202
|
+
::bsJsIntegrity=${null}>
|
|
203
|
+
<:import src="/bootstrap-kit/all.htm" />
|
|
204
|
+
</head>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| Token | Default |
|
|
208
|
+
| --- | --- |
|
|
209
|
+
| `bsCssUrl` | jsDelivr, Bootstrap 5.3.8 |
|
|
210
|
+
| `bsJsUrl` | jsDelivr, Bootstrap 5.3.8 |
|
|
211
|
+
| `bsCssIntegrity` | the matching SRI hash |
|
|
212
|
+
| `bsJsIntegrity` | the matching SRI hash |
|
|
213
|
+
|
|
214
|
+
Drop the hashes when self-hosting: `crossorigin` follows the hash, and
|
|
215
|
+
neither means anything on a same-origin file.
|
|
216
|
+
|
|
217
|
+
Four reasons this is worth having rather than a convenience:
|
|
218
|
+
|
|
219
|
+
- **A content security policy** that allows no third-party origin, which is
|
|
220
|
+
most of them once an app is behind a login.
|
|
221
|
+
- **An offline or air-gapped build**, where a CDN isn't reachable at all.
|
|
222
|
+
- **A vendored, pinned copy**, so the page doesn't depend on a third party
|
|
223
|
+
staying up or staying honest.
|
|
224
|
+
- **Your own Bootstrap build.** The tokens below only reach what Bootstrap
|
|
225
|
+
exposes as CSS variables; a design system usually compiles Bootstrap from
|
|
226
|
+
Sass with its own variables. Pointing `bsCssUrl` at that build is how
|
|
227
|
+
the kit gets out of the way of it.
|
|
228
|
+
|
|
229
|
+
## Theming
|
|
230
|
+
|
|
231
|
+
`base.htm` declares the kit's tokens and writes them into Bootstrap's own CSS
|
|
232
|
+
variables, so restyling everything is setting one value at the import site:
|
|
233
|
+
|
|
234
|
+
```html
|
|
235
|
+
<head ::bsRadius="1rem" ::bsLinkDecoration="none">
|
|
236
|
+
<:import src="/bootstrap-kit/all.htm" />
|
|
237
|
+
</head>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
| Token | Default |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `bsRadius` | `0.375rem` |
|
|
243
|
+
| `bsRadiusSm` | `0.25rem` |
|
|
244
|
+
| `bsRadiusLg` | `0.5rem` |
|
|
245
|
+
| `bsRadiusPill` | `50rem` |
|
|
246
|
+
| `bsFontSans` | Bootstrap's system stack |
|
|
247
|
+
| `bsLinkDecoration` | `underline` |
|
|
248
|
+
|
|
249
|
+
Colour modes are `theme.htm`: an inline pre-paint script so the page never
|
|
250
|
+
flashes the wrong mode, and `<bs-theme-toggle />` to switch it.
|
|
251
|
+
|
|
252
|
+
## Components
|
|
253
|
+
|
|
254
|
+
Every tag also takes `:extra`. Defaults are in the definitions, which are
|
|
255
|
+
commented.
|
|
256
|
+
|
|
257
|
+
### Content
|
|
258
|
+
|
|
259
|
+
| Tag | Parameters |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| `bs-image` | `src` `alt` `fluid` `thumbnail` `rounded` |
|
|
262
|
+
| `bs-figure` | `src` `alt` `caption` `align` |
|
|
263
|
+
| `bs-table` | `columns` `rows` `caption` `striped` `hover` `bordered` `borderless` `small` `variant` `headVariant` `align` `responsive` |
|
|
264
|
+
|
|
265
|
+
### Forms
|
|
266
|
+
|
|
267
|
+
| Tag | Parameters |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| `bs-input` | `label` `type` `name` `value` `placeholder` `help` `size` `disabled` `readonly` `required` `floating` `check` `message` |
|
|
270
|
+
| `bs-textarea` | `label` `name` `value` `placeholder` `rows` `help` `disabled` `readonly` `required` `check` `message` |
|
|
271
|
+
| `bs-select` | `label` `name` `options` `value` `placeholder` `help` `size` `multiple` `disabled` `required` `floating` |
|
|
272
|
+
| `bs-check` | `label` `type` (`checkbox`/`radio`/`switch`) `name` `value` `checked` `inline` `reverse` `disabled` `help` |
|
|
273
|
+
| `bs-check-group` | `legend` `type` `options` `value` `inline` `disabled` |
|
|
274
|
+
| `bs-range` | `label` `name` `min` `max` `step` `value` `disabled` `showValue` |
|
|
275
|
+
| `bs-input-group` | `prefix` `suffix` `size` |
|
|
276
|
+
|
|
277
|
+
### Components
|
|
278
|
+
|
|
279
|
+
| Tag | Parameters |
|
|
280
|
+
| --- | --- |
|
|
281
|
+
| `bs-accordion` | `exclusive` `flush` |
|
|
282
|
+
| `bs-accordion-item` | `title` `open` |
|
|
283
|
+
| `bs-alert` | `variant` `heading` `dismissible` |
|
|
284
|
+
| `bs-badge` | `variant` `pill` `position` |
|
|
285
|
+
| `bs-breadcrumb` | `items` `divider` |
|
|
286
|
+
| `bs-button` | `variant` `outline` `size` `active` `disabled` `type` `toggle` `target` `dismiss` |
|
|
287
|
+
| `bs-link` | `href` `variant` `outline` `size` `active` `disabled` `button` `toggle` `target` |
|
|
288
|
+
| `bs-button-group` | `label` `size` `vertical` |
|
|
289
|
+
| `bs-button-toolbar` | `label` `gap` |
|
|
290
|
+
| `bs-close` | `label` `dismiss` `disabled` |
|
|
291
|
+
| `bs-card` | `title` `subtitle` `header` `footer` `image` `imageAlt` `imageBottom` `variant` `border` `align` `bodyExtra` |
|
|
292
|
+
| `bs-card-group` | `cols` `gap` `attached` |
|
|
293
|
+
| `bs-carousel` | `slides` `controls` `indicators` `captions` `fade` `ride` `interval` `dark` |
|
|
294
|
+
| `bs-collapse` | `name` `open` `horizontal` |
|
|
295
|
+
| `bs-dropdown` | `label` `items` `variant` `outline` `size` `split` `direction` `align` `dark` |
|
|
296
|
+
| `bs-list-group` | `items` `flush` `horizontal` |
|
|
297
|
+
| `bs-modal` | `name` `title` `size` `centered` `scrollable` `fullscreen` `staticBackdrop` `open` |
|
|
298
|
+
| `bs-nav` | `items` `variant` `fill` `justified` `vertical` `align` `toggle` |
|
|
299
|
+
| `bs-tab-content`, `bs-tab-pane` | `name` `active` |
|
|
300
|
+
| `bs-navbar` | `items` `brand` `expand` `container` `sticky` `fixed` `theme` `bg`; slots: default (brand), `end` |
|
|
301
|
+
| `bs-offcanvas` | `name` `title` `placement` `backdrop` `scroll` `responsive` `open` |
|
|
302
|
+
| `bs-pagination` | `current` `pages` `size` `align` `prev` `next` `link` `select` `label` |
|
|
303
|
+
| `bs-placeholder` | `cols` `size` `variant` `animation` |
|
|
304
|
+
| `bs-popover` | `title` `content` `placement` `trigger` `html` |
|
|
305
|
+
| `bs-progress` | `value` `min` `max` `label` `variant` `striped` `animated` `height` `name` |
|
|
306
|
+
| `bs-progress-stacked` | — |
|
|
307
|
+
| `bs-scrollspy` | `target` `offset` `smooth` `height` |
|
|
308
|
+
| `bs-spinner` | `variant` `type` `small` `label` |
|
|
309
|
+
| `bs-theme-toggle` | `variant` `outline` `size` |
|
|
310
|
+
| `bs-toast` | `title` `time` `variant` `open` `autohide` `delay` |
|
|
311
|
+
| `bs-toast-container` | `placement` |
|
|
312
|
+
| `bs-tooltip` | `title` `placement` `html` `trigger` |
|
|
313
|
+
|
|
314
|
+
## Notes on the awkward corners
|
|
315
|
+
|
|
316
|
+
A few components are shaped by something in the language rather than by
|
|
317
|
+
Bootstrap, and each says so in its own file:
|
|
318
|
+
|
|
319
|
+
- **`bs-accordion-item` reads `$host`.** An item needs the id of the
|
|
320
|
+
accordion it sits in, and slotted markup resolves at the call site, so it
|
|
321
|
+
cannot reach it by name. `$host` is the instance it was slotted *into* —
|
|
322
|
+
the one place the kit needs the structural relationship rather than the
|
|
323
|
+
lexical one. No id is written by anyone.
|
|
324
|
+
- **`bs-pagination` says `:current`, not `:page`.** `page` is already the
|
|
325
|
+
name of `<html>`'s own scope, so a parameter of that name would resolve to
|
|
326
|
+
the scope rather than to the number.
|
|
327
|
+
- **`bs-tooltip` and `bs-popover` construct their plugin from a
|
|
328
|
+
`:handle-`.** They are the only two components Bootstrap doesn't start by
|
|
329
|
+
itself, and the usual answer — a page-level loop over
|
|
330
|
+
`[data-bs-toggle="tooltip"]` — misses anything added later. A handler
|
|
331
|
+
builds one per instance instead, and rebuilds it when the text changes.
|
package/all.htm
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!---
|
|
2
|
+
Everything the kit defines. Import this for a page that uses a bit of
|
|
3
|
+
everything; import individual parts for one that doesn't.
|
|
4
|
+
|
|
5
|
+
Each part imports `base.htm` itself, and a file is only imported once per
|
|
6
|
+
page, so picking parts by hand never leaves Bootstrap out.
|
|
7
|
+
-->
|
|
8
|
+
<lib>
|
|
9
|
+
<:import src="parts/base.htm" />
|
|
10
|
+
|
|
11
|
+
<!--- content -->
|
|
12
|
+
<:import src="parts/image.htm" />
|
|
13
|
+
<:import src="parts/table.htm" />
|
|
14
|
+
|
|
15
|
+
<!--- forms -->
|
|
16
|
+
<:import src="parts/check.htm" />
|
|
17
|
+
<:import src="parts/input.htm" />
|
|
18
|
+
<:import src="parts/select.htm" />
|
|
19
|
+
|
|
20
|
+
<!--- components -->
|
|
21
|
+
<:import src="parts/accordion.htm" />
|
|
22
|
+
<:import src="parts/alert.htm" />
|
|
23
|
+
<:import src="parts/badge.htm" />
|
|
24
|
+
<:import src="parts/breadcrumb.htm" />
|
|
25
|
+
<:import src="parts/button.htm" />
|
|
26
|
+
<:import src="parts/card.htm" />
|
|
27
|
+
<:import src="parts/carousel.htm" />
|
|
28
|
+
<:import src="parts/collapse.htm" />
|
|
29
|
+
<:import src="parts/dropdown.htm" />
|
|
30
|
+
<:import src="parts/list-group.htm" />
|
|
31
|
+
<:import src="parts/modal.htm" />
|
|
32
|
+
<:import src="parts/nav.htm" />
|
|
33
|
+
<:import src="parts/navbar.htm" />
|
|
34
|
+
<:import src="parts/offcanvas.htm" />
|
|
35
|
+
<:import src="parts/pagination.htm" />
|
|
36
|
+
<:import src="parts/placeholder.htm" />
|
|
37
|
+
<:import src="parts/popover.htm" />
|
|
38
|
+
<:import src="parts/progress.htm" />
|
|
39
|
+
<:import src="parts/scrollspy.htm" />
|
|
40
|
+
<:import src="parts/spinner.htm" />
|
|
41
|
+
<:import src="parts/theme.htm" />
|
|
42
|
+
<:import src="parts/toast.htm" />
|
|
43
|
+
<:import src="parts/tooltip.htm" />
|
|
44
|
+
</lib>
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@markout-lang/bootstrap-kit",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Bootstrap 5.3 as Markout components: one part per component, with the id wiring, the ARIA attributes and the repetition written once",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/fcapolini/markout.git",
|
|
8
|
+
"directory": "kits/bootstrap-kit"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/fcapolini/markout/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://markout.dev",
|
|
14
|
+
"markout": {
|
|
15
|
+
"root": "/bootstrap-kit"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"markout",
|
|
19
|
+
"markout-kit",
|
|
20
|
+
"bootstrap",
|
|
21
|
+
"components",
|
|
22
|
+
"html"
|
|
23
|
+
],
|
|
24
|
+
"author": "Fabrizio Capolini",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"all.htm",
|
|
31
|
+
"parts"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
An accordion, in two tags, because its panels hold arbitrary markup and
|
|
6
|
+
markup belongs in a slot rather than in an array.
|
|
7
|
+
|
|
8
|
+
<bs-accordion>
|
|
9
|
+
<bs-accordion-item :title="First" :open>…</bs-accordion-item>
|
|
10
|
+
<bs-accordion-item :title="Second">…</bs-accordion-item>
|
|
11
|
+
</bs-accordion>
|
|
12
|
+
|
|
13
|
+
The item needs one thing from the accordion — the id Bootstrap closes the
|
|
14
|
+
others by — and it asks for it with `$host`, which is the enclosing
|
|
15
|
+
instance rather than the scope the markup was written in. So neither tag
|
|
16
|
+
carries an id the caller had to invent, and the whole coupling is one
|
|
17
|
+
expression in the definition instead of a back-reference at every usage.
|
|
18
|
+
|
|
19
|
+
An item written outside an accordion has no `$host` and simply opens and
|
|
20
|
+
closes on its own, which is Bootstrap's "always open" behaviour reached
|
|
21
|
+
by itself rather than by remembering to leave an attribute off. Setting
|
|
22
|
+
`:exclusive=${false}` on the accordion asks for the same thing.
|
|
23
|
+
|
|
24
|
+
Every id here is derived from `$id`, so two accordions on a page cannot
|
|
25
|
+
collide and nothing has to be named.
|
|
26
|
+
-->
|
|
27
|
+
<:define tag="bs-accordion:div"
|
|
28
|
+
class="accordion ${extra ?? ''}"
|
|
29
|
+
|
|
30
|
+
// parameters
|
|
31
|
+
:extra=${null} // extra classes for this instance
|
|
32
|
+
:exclusive=${true} // one open panel at a time
|
|
33
|
+
:flush=${false}
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
private: the id the items point at. Owned here rather than
|
|
37
|
+
rebuilt there, so the two can't drift apart
|
|
38
|
+
*/
|
|
39
|
+
:_id=${`bs-accordion-${$id}`}
|
|
40
|
+
|
|
41
|
+
id=${_id}
|
|
42
|
+
:class-accordion-flush=${flush}
|
|
43
|
+
>
|
|
44
|
+
<:slot />
|
|
45
|
+
</:define>
|
|
46
|
+
|
|
47
|
+
<:define tag="bs-accordion-item:div"
|
|
48
|
+
class="accordion-item ${extra ?? ''}"
|
|
49
|
+
|
|
50
|
+
// parameters
|
|
51
|
+
:extra=${null} // extra classes for this instance
|
|
52
|
+
:title=${''}
|
|
53
|
+
:open=${false}
|
|
54
|
+
|
|
55
|
+
// private: the accordion this item was slotted into, if any
|
|
56
|
+
:_parent=${$host && $host.exclusive ? $host._id : null}
|
|
57
|
+
:_id=${`bs-accordion-item-${$id}`}
|
|
58
|
+
:_headingId=${`bs-accordion-heading-${$id}`}
|
|
59
|
+
>
|
|
60
|
+
<h2 class="accordion-header" id=${_headingId}>
|
|
61
|
+
<button class="accordion-button"
|
|
62
|
+
type="button"
|
|
63
|
+
:class-collapsed=${!open}
|
|
64
|
+
data-bs-toggle="collapse"
|
|
65
|
+
data-bs-target="#${_id}"
|
|
66
|
+
aria-expanded=${open ? 'true' : 'false'}
|
|
67
|
+
aria-controls=${_id}>${title}</button>
|
|
68
|
+
</h2>
|
|
69
|
+
|
|
70
|
+
<div class="accordion-collapse collapse"
|
|
71
|
+
id=${_id}
|
|
72
|
+
:class-show=${open}
|
|
73
|
+
aria-labelledby=${_headingId}
|
|
74
|
+
data-bs-parent=${_parent ? `#${_parent}` : null}>
|
|
75
|
+
<div class="accordion-body">
|
|
76
|
+
<:slot />
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</:define>
|
|
80
|
+
|
|
81
|
+
</lib>
|
package/parts/alert.htm
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
<:import src="button.htm" />
|
|
4
|
+
|
|
5
|
+
<!---
|
|
6
|
+
An alert, with the two parts that are easy to get wrong done for you:
|
|
7
|
+
`role="alert"` so it is announced, and the `fade show` + close button
|
|
8
|
+
combination that a dismissible one needs to animate out.
|
|
9
|
+
|
|
10
|
+
`:heading` and the dismiss button are optional REGIONS, and both are
|
|
11
|
+
`:if`: there is something to decide, not something to show. Truthiness
|
|
12
|
+
is what is wanted of a heading — `:heading=""` is a heading nobody
|
|
13
|
+
asked for, and `:for-data`'s `!= null` would have rendered the empty
|
|
14
|
+
`<h4>` around it.
|
|
15
|
+
|
|
16
|
+
The body is a `<:slot>` and the heading a parameter because that is
|
|
17
|
+
what each of them is — markup in one case, text in the other. It used
|
|
18
|
+
to be forced as well: an optional region was a `:for-each` over a
|
|
19
|
+
one-or-zero list, and a `<:slot>` cannot sit inside one of those.
|
|
20
|
+
-->
|
|
21
|
+
<:define tag="bs-alert:div"
|
|
22
|
+
role="alert"
|
|
23
|
+
|
|
24
|
+
// parameters
|
|
25
|
+
:extra=${null} // extra classes for this instance
|
|
26
|
+
:variant=${'primary'}
|
|
27
|
+
:heading=${null}
|
|
28
|
+
:dismissible=${false}
|
|
29
|
+
|
|
30
|
+
// private
|
|
31
|
+
:_class=${['alert', `alert-${variant}`, extra].filter(s => s).join(' ')}
|
|
32
|
+
|
|
33
|
+
class=${_class}
|
|
34
|
+
:class-alert-dismissible=${dismissible}
|
|
35
|
+
:class-fade=${dismissible}
|
|
36
|
+
:class-show=${dismissible}
|
|
37
|
+
>
|
|
38
|
+
<h4 class="alert-heading"
|
|
39
|
+
:if=${heading}>${heading}</h4>
|
|
40
|
+
|
|
41
|
+
<:slot />
|
|
42
|
+
|
|
43
|
+
<bs-close :dismiss="alert"
|
|
44
|
+
:label="Close alert"
|
|
45
|
+
:if=${dismissible} />
|
|
46
|
+
</:define>
|
|
47
|
+
|
|
48
|
+
</lib>
|
package/parts/badge.htm
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
`text-bg-*` rather than `bg-*`: it sets the foreground colour along with
|
|
6
|
+
the background, which is the pairing that stays readable in both colour
|
|
7
|
+
modes.
|
|
8
|
+
-->
|
|
9
|
+
<:define tag="bs-badge:span"
|
|
10
|
+
|
|
11
|
+
// parameters
|
|
12
|
+
:extra=${null} // extra classes for this instance
|
|
13
|
+
:variant=${'primary'}
|
|
14
|
+
:pill=${false}
|
|
15
|
+
:position=${null} // 'top-end' | 'bottom-end' — for a badge on a button
|
|
16
|
+
|
|
17
|
+
// private
|
|
18
|
+
:_class=${[
|
|
19
|
+
'badge',
|
|
20
|
+
`text-bg-${variant}`,
|
|
21
|
+
pill ? 'rounded-pill' : '',
|
|
22
|
+
position ? 'position-absolute translate-middle' : '',
|
|
23
|
+
position === 'top-end' ? 'top-0 start-100' : '',
|
|
24
|
+
position === 'bottom-end' ? 'top-100 start-100' : '',
|
|
25
|
+
extra,
|
|
26
|
+
].filter(s => s).join(' ')}
|
|
27
|
+
|
|
28
|
+
class=${_class}
|
|
29
|
+
>
|
|
30
|
+
<:slot />
|
|
31
|
+
</:define>
|
|
32
|
+
|
|
33
|
+
</lib>
|
package/parts/base.htm
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<!---
|
|
2
|
+
The one part every other part imports. It brings in Bootstrap itself and
|
|
3
|
+
declares the kit's design tokens.
|
|
4
|
+
|
|
5
|
+
A token is a value rather than a per-component CSS override: it is written
|
|
6
|
+
once into Bootstrap's own CSS variables below, so restyling the kit means
|
|
7
|
+
setting one value at the import site instead of patching each component:
|
|
8
|
+
|
|
9
|
+
<head ::bsRadius="1rem">
|
|
10
|
+
<:import src="/bootstrap-kit/all.htm" />
|
|
11
|
+
</head>
|
|
12
|
+
|
|
13
|
+
Where Bootstrap itself comes from is a token too. A page under a content
|
|
14
|
+
security policy that allows no third party, an offline build, or a vendored
|
|
15
|
+
copy pinned on purpose sets the two URLs and drops the hashes rather than
|
|
16
|
+
forking this file — and so does a page serving its own Sass build, which is
|
|
17
|
+
how a design system gets past what the tokens below can reach:
|
|
18
|
+
|
|
19
|
+
<head ::bsCssUrl="/vendor/bootstrap.min.css"
|
|
20
|
+
::bsJsUrl="/vendor/bootstrap.bundle.min.js"
|
|
21
|
+
::bsCssIntegrity=${null}
|
|
22
|
+
::bsJsIntegrity=${null}>
|
|
23
|
+
-->
|
|
24
|
+
<lib
|
|
25
|
+
// where Bootstrap comes from
|
|
26
|
+
::bsCssUrl=${'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css'}
|
|
27
|
+
::bsCssIntegrity=${'sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB'}
|
|
28
|
+
::bsJsUrl=${'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js'}
|
|
29
|
+
::bsJsIntegrity=${'sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI'}
|
|
30
|
+
|
|
31
|
+
// tokens
|
|
32
|
+
::bsRadius=${'0.375rem'}
|
|
33
|
+
::bsRadiusSm=${'0.25rem'}
|
|
34
|
+
::bsRadiusLg=${'0.5rem'}
|
|
35
|
+
::bsRadiusPill=${'50rem'}
|
|
36
|
+
::bsFontSans=${'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'}
|
|
37
|
+
::bsLinkDecoration=${'underline'}
|
|
38
|
+
>
|
|
39
|
+
<meta charset="utf-8">
|
|
40
|
+
<meta name="viewport"
|
|
41
|
+
content="width=device-width, initial-scale=1">
|
|
42
|
+
|
|
43
|
+
<!--- `crossorigin` follows the hash: it is what makes one checkable, and
|
|
44
|
+
means nothing on a same-origin file -->
|
|
45
|
+
<link href=${bsCssUrl}
|
|
46
|
+
rel="stylesheet"
|
|
47
|
+
integrity=${bsCssIntegrity}
|
|
48
|
+
crossorigin=${bsCssIntegrity ? 'anonymous' : null}>
|
|
49
|
+
|
|
50
|
+
<!---
|
|
51
|
+
Deliberately not `async`. The components that need Bootstrap's JS
|
|
52
|
+
(tooltips, popovers, toasts) create their plugin instance from a
|
|
53
|
+
`:handle-`, which runs the moment Markout hydrates; loading the bundle
|
|
54
|
+
synchronously here is what guarantees `window.bootstrap` already exists
|
|
55
|
+
by then. Everything driven by `data-bs-*` alone would be fine either way.
|
|
56
|
+
-->
|
|
57
|
+
<script src=${bsJsUrl}
|
|
58
|
+
integrity=${bsJsIntegrity}
|
|
59
|
+
crossorigin=${bsJsIntegrity ? 'anonymous' : null}></script>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
:root {
|
|
63
|
+
--bs-border-radius: ${bsRadius};
|
|
64
|
+
--bs-border-radius-sm: ${bsRadiusSm};
|
|
65
|
+
--bs-border-radius-lg: ${bsRadiusLg};
|
|
66
|
+
--bs-border-radius-pill: ${bsRadiusPill};
|
|
67
|
+
--bs-font-sans-serif: ${bsFontSans};
|
|
68
|
+
--bs-link-decoration: ${bsLinkDecoration};
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
71
|
+
</lib>
|