@threadlabs/looma 0.33.0 → 0.34.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 +15 -0
- package/bin/looma-docs.mjs +3 -0
- package/components/shared/overlay.js +8 -2
- package/components/shared/surface-material.js +21 -0
- package/components/ui-button/ui-button.html +63 -47
- package/components/ui-card/ui-card.html +44 -2
- package/components/ui-curve-editor/curve.js +10 -0
- package/components/ui-curve-editor/ui-curve-editor.html +181 -0
- package/components/ui-curve-editor/ui-curve-editor.js +51 -0
- package/components/ui-dialog/ui-dialog.html +62 -1
- package/components/ui-dialog/ui-dialog.js +9 -1
- package/components/ui-icon-button/ui-icon-button.html +75 -23
- package/components/ui-menu/ui-menu.html +60 -0
- package/components/ui-menu/ui-menu.js +1 -1
- package/components/ui-number-input/arithmetic.js +47 -0
- package/components/ui-number-input/ui-number-input.html +62 -0
- package/components/ui-number-input/ui-number-input.js +44 -0
- package/components/ui-popover/ui-popover.html +64 -1
- package/components/ui-popover/ui-popover.js +1 -1
- package/components/ui-slider/ui-slider.html +84 -0
- package/components/ui-slider/ui-slider.js +29 -0
- package/components/ui-tabs/ui-tabs.html +6 -1
- package/components/ui-text/ui-text.html +31 -6
- package/components/ui-tooltip/ui-tooltip.html +60 -0
- package/components/ui-tooltip/ui-tooltip.js +1 -1
- package/dist/index.js +46 -43
- package/docs/README.md +146 -0
- package/docs/card.html +77 -0
- package/docs/card.js +47 -0
- package/docs/catalog-view.js +18 -0
- package/docs/catalog-view.mjs +11 -0
- package/docs/catalog.json +23984 -0
- package/docs/catalog.mjs +131 -0
- package/docs/cli.mjs +26 -0
- package/docs/commands.mjs +94 -0
- package/docs/customizer-render.mjs +66 -0
- package/docs/customizer.js +181 -0
- package/docs/documentation.css +16 -0
- package/docs/examples/ui-action-bar/examples/01-form-footer.behavior.js +7 -0
- package/docs/examples/ui-button/examples/08-loading.behavior.js +21 -0
- package/docs/examples/ui-checkbox/examples/02-checked-indeterminate-and-disabled.behavior.js +30 -0
- package/docs/examples/ui-combobox/examples/06-selected-values.behavior.js +11 -0
- package/docs/examples/ui-search-shell/examples/02-label-dismissible-status-and-footer.behavior.js +34 -0
- package/docs/examples/ui-toast/examples/01-authored.behavior.js +15 -0
- package/docs/examples/ui-toast/examples/02-danger.behavior.js +2 -0
- package/docs/examples/ui-tree/examples/03-max-depth-2-and-hover-expand-delay-300.behavior.js +30 -0
- package/docs/examples/ui-tree/examples/06-move-without-dragging.behavior.js +2 -0
- package/docs/examples/ui-tree/examples/07-multiple-selection.behavior.js +16 -0
- package/docs/examples/ui-tree-item/examples/02-container.behavior.js +1 -0
- package/docs/index.d.ts +56 -0
- package/docs/index.mjs +4 -0
- package/docs/local-server.mjs +68 -0
- package/docs/preview.js +22 -0
- package/docs/reference.html +1 -0
- package/docs/reference.js +36 -0
- package/docs/render.mjs +93 -0
- package/docs/site.mjs +239 -0
- package/docs/store.mjs +92 -0
- package/docs/theme-session.mjs +51 -0
- package/docs/theme.mjs +219 -0
- package/docs/value-editors.mjs +33 -0
- package/docs/workspace.mjs +32 -0
- package/package.json +28 -4
- package/styles/ui-button.css +45 -45
- package/styles/ui-card.css +47 -0
- package/styles/ui-curve-editor.css +109 -0
- package/styles/ui-dialog.css +132 -0
- package/styles/ui-icon-button.css +63 -20
- package/styles/ui-menu.css +72 -0
- package/styles/ui-number-input.css +34 -0
- package/styles/ui-popover.css +74 -0
- package/styles/ui-slider.css +53 -0
- package/styles/ui-tabs.css +6 -1
- package/styles/ui-text.css +23 -1
- package/styles/ui-tooltip.css +72 -0
- package/theme-system.css +26 -10
- package/tokens.css +8 -2
- package/vanilla/UiButton.d.ts +1 -0
- package/vanilla/UiButton.js +195 -130
- package/vanilla/UiCard.d.ts +1 -1
- package/vanilla/UiCard.js +1 -1
- package/vanilla/UiCurveEditor.d.ts +28 -0
- package/vanilla/UiCurveEditor.js +134 -0
- package/vanilla/UiIconButton.d.ts +2 -1
- package/vanilla/UiIconButton.js +58 -31
- package/vanilla/UiNumberInput.d.ts +33 -0
- package/vanilla/UiNumberInput.js +79 -0
- package/vanilla/UiSlider.d.ts +33 -0
- package/vanilla/UiSlider.js +125 -0
- package/vanilla/UiText.d.ts +2 -2
- package/vanilla/UiText.js +45 -11
- package/vanilla/index.js +3 -0
- package/vue/UiButton.d.ts +6 -0
- package/vue/UiButton.vue +155 -56
- package/vue/UiCard.d.ts +2 -2
- package/vue/UiCard.vue +55 -5
- package/vue/UiCurveEditor.d.ts +53 -0
- package/vue/UiCurveEditor.js +2 -0
- package/vue/UiCurveEditor.vue +427 -0
- package/vue/UiDialog.vue +177 -1
- package/vue/UiIconButton.d.ts +8 -2
- package/vue/UiIconButton.vue +164 -34
- package/vue/UiMenu.vue +89 -0
- package/vue/UiNumberInput.d.ts +83 -0
- package/vue/UiNumberInput.js +2 -0
- package/vue/UiNumberInput.vue +370 -0
- package/vue/UiPopover.vue +92 -1
- package/vue/UiSlider.d.ts +83 -0
- package/vue/UiSlider.js +2 -0
- package/vue/UiSlider.vue +412 -0
- package/vue/UiTabs.vue +6 -1
- package/vue/UiText.d.ts +7 -5
- package/vue/UiText.vue +104 -22
- package/vue/UiTooltip.vue +89 -0
- package/vue/chunks/UiActionBar.js +1 -1
- package/vue/chunks/UiAffordanceScope.js +2 -2
- package/vue/chunks/UiAvatar.js +2 -2
- package/vue/chunks/UiAvatarGroup.js +2 -2
- package/vue/chunks/UiBadge.js +2 -2
- package/vue/chunks/UiBreadcrumbItem.js +2 -2
- package/vue/chunks/UiBreadcrumbs.js +2 -2
- package/vue/chunks/UiButton.js +94 -11
- package/vue/chunks/UiCallout.js +2 -2
- package/vue/chunks/UiCard.js +6 -5
- package/vue/chunks/UiCheckbox.js +2 -2
- package/vue/chunks/UiCluster.js +2 -2
- package/vue/chunks/UiColorPicker.js +2 -2
- package/vue/chunks/UiCombobox.js +2 -2
- package/vue/chunks/UiContainer.js +2 -2
- package/vue/chunks/UiContextMenu.js +2 -2
- package/vue/chunks/UiCover.js +2 -2
- package/vue/chunks/UiCurveEditor.js +413 -0
- package/vue/chunks/UiDescriptionItem.js +2 -2
- package/vue/chunks/UiDescriptionList.js +2 -2
- package/vue/chunks/UiDialog.js +4 -4
- package/vue/chunks/UiDisclosure.js +2 -2
- package/vue/chunks/UiEditable.js +2 -2
- package/vue/chunks/UiEditorBlockActions.js +2 -2
- package/vue/chunks/UiEditorChipEditor.js +2 -2
- package/vue/chunks/UiEditorCodeLanguage.js +2 -2
- package/vue/chunks/UiEditorFootnoteEditor.js +2 -2
- package/vue/chunks/UiEditorImageToolbar.js +3 -3
- package/vue/chunks/UiEditorInsertTableGrid.js +2 -2
- package/vue/chunks/UiEditorLinkCard.js +3 -3
- package/vue/chunks/UiEditorLinkForm.js +3 -3
- package/vue/chunks/UiEditorMentionMenu.js +2 -2
- package/vue/chunks/UiEditorMentionMenuItem.js +2 -2
- package/vue/chunks/UiEditorSlashMenu.js +2 -2
- package/vue/chunks/UiEditorSlashMenuGroup.js +2 -2
- package/vue/chunks/UiEditorSlashMenuItem.js +2 -2
- package/vue/chunks/UiEditorSurface.js +2 -2
- package/vue/chunks/UiEditorTableContextMenu.js +2 -2
- package/vue/chunks/UiEditorTableOverlay.js +2 -2
- package/vue/chunks/UiEditorTableToolbar.js +2 -2
- package/vue/chunks/UiEditorToolbar.js +2 -2
- package/vue/chunks/UiFloatingActionButton.js +2 -2
- package/vue/chunks/UiFormField.js +2 -2
- package/vue/chunks/UiGrid.js +2 -2
- package/vue/chunks/UiIcon.js +2 -2
- package/vue/chunks/UiIconButton.js +77 -11
- package/vue/chunks/UiImage.js +2 -2
- package/vue/chunks/UiInput.js +2 -2
- package/vue/chunks/UiInputGroup.js +2 -2
- package/vue/chunks/UiList.js +2 -2
- package/vue/chunks/UiListItem.js +2 -2
- package/vue/chunks/UiListbox.js +2 -2
- package/vue/chunks/UiMenu.js +3 -3
- package/vue/chunks/UiMenuGroup.js +2 -2
- package/vue/chunks/UiMenuItem.js +2 -2
- package/vue/chunks/UiMeter.js +2 -2
- package/vue/chunks/UiNavItem.js +2 -2
- package/vue/chunks/UiNumberInput.js +390 -0
- package/vue/chunks/UiPageHeader.js +2 -2
- package/vue/chunks/UiPopover.js +4 -4
- package/vue/chunks/UiRadio.js +2 -2
- package/vue/chunks/UiRadioGroup.js +2 -2
- package/vue/chunks/UiReel.js +2 -2
- package/vue/chunks/UiScrollArea.js +2 -2
- package/vue/chunks/UiSearchResultRow.js +2 -2
- package/vue/chunks/UiSearchShell.js +2 -2
- package/vue/chunks/UiSection.js +2 -2
- package/vue/chunks/UiSelect.js +2 -2
- package/vue/chunks/UiSeparator.js +2 -2
- package/vue/chunks/UiSidebar.js +2 -2
- package/vue/chunks/UiSlider.js +396 -0
- package/vue/chunks/UiSpinner.js +2 -2
- package/vue/chunks/UiStack.js +2 -2
- package/vue/chunks/UiStatusMessage.js +2 -2
- package/vue/chunks/UiSwatch.js +2 -2
- package/vue/chunks/UiSwitch.js +2 -2
- package/vue/chunks/UiSwitcher.js +2 -2
- package/vue/chunks/UiTable.js +2 -2
- package/vue/chunks/UiTabs.js +3 -3
- package/vue/chunks/UiText.js +53 -12
- package/vue/chunks/UiTextarea.js +2 -2
- package/vue/chunks/UiToast.js +2 -2
- package/vue/chunks/UiToastRegion.js +2 -2
- package/vue/chunks/UiTooltip.js +3 -3
- package/vue/chunks/UiTopBar.js +1 -1
- package/vue/chunks/UiTree.js +2 -2
- package/vue/chunks/UiTreeItem.js +2 -2
- package/vue/chunks/_plugin-vue_export-helper.js +68 -2
- package/vue/chunks/props.js +24 -1
- package/vue/components.css +2215 -1203
- package/vue/editor/index.js +623 -211
- package/vue/index.d.ts +3 -0
- package/vue/index.js +5 -2
package/docs/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Weave your design system
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Weave your design system
|
|
6
|
+
|
|
7
|
+
Looma ships the documentation generator and theme customizer used by its public site. Run it in your project to browse Looma and your own HTML Next components as one design system. The customizer uses the same authored component cards as the reference.
|
|
8
|
+
|
|
9
|
+
## Start a local design system
|
|
10
|
+
|
|
11
|
+
In a project with a `package.json`, install Looma and its documentation tools:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm add @threadlabs/looma @nextwebwg/html-next@1.0.0-alpha.69
|
|
15
|
+
pnpm add -D markupress @nextwebwg/htmlkit@1.0.0-alpha.69 vite
|
|
16
|
+
pnpm exec looma-docs init --title "The Fellowship"
|
|
17
|
+
pnpm exec looma-docs dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use the tool versions accepted by Looma's optional peer dependencies. Regular component imports do not load the documentation server. Vue and Tiptap remain optional; install them when your own authored documentation mounts the Vue editor.
|
|
21
|
+
|
|
22
|
+
Initialization creates three project-owned files:
|
|
23
|
+
|
|
24
|
+
| File | Purpose |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `looma.docs.json` | Documentation title, content, discovery roots and theme paths. |
|
|
27
|
+
| `looma.theme.json` | Editable semantic decisions. |
|
|
28
|
+
| `looma.theme.css` | Generated theme, including the shared derivation rules. |
|
|
29
|
+
|
|
30
|
+
Import `./looma.theme.css` after Looma's tokens and palette in your application's entry point. The initializer prints that import; it leaves existing application files intact. Commit the configuration, recipe and CSS. Add `.looma/` to your project's ignore file: it contains disposable generated documentation and tooling files.
|
|
31
|
+
|
|
32
|
+
Open the local address printed by `dev`, choose **Customize**, and save your theme. Saving updates those shared theme files and rebuilds the documentation. Your application consumes the same CSS import. A static or public site instead offers CSS and recipe downloads.
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pnpm exec looma-docs build
|
|
36
|
+
pnpm exec looma-docs preview
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`build` produces a static HTMLKit site in the configured output directory. `preview` serves that output without a repository writer. All commands accept `--root`; `dev` and `preview` also accept `--port`.
|
|
40
|
+
|
|
41
|
+
## Change the rule, keep the family consistent
|
|
42
|
+
|
|
43
|
+
The authoring model groups shared decisions as Colour, Type, Size & spacing, Material and Motion. The controls expose shared intent: a control size, spaciousness, corners, inner and outer depth, and response pace.
|
|
44
|
+
|
|
45
|
+
The generated values follow rules. Small and large control heights follow a ratio around the shared control size. Spacing follows an independent curve: linear at 1, increasingly curved toward 2, with stable endpoints so the largest gaps do not grow without bound. Depth generates families of shadows; it does not add motion amplitude. Fields and buttons with the same size setting use the same box height, while content can still grow when it needs more room.
|
|
46
|
+
|
|
47
|
+
Choose a coordinated palette to change related paper, ink and action seeds together without changing geometry or motion. Palette seeds remain available for precise edits; the light/dark range previews and resolved values explain readability adjustments.
|
|
48
|
+
|
|
49
|
+
Number controls accept arithmetic such as `42px / 1.2` or `150ms * 2`, calculate on entry and show the result. Unit changes preserve the quantity. Sliders include exact entry, and curve handles support dragging and arrow keys alongside numeric coordinates.
|
|
50
|
+
|
|
51
|
+
Lengths retain their CSS units, such as `rem`; times retain `ms` or `s`. The downloaded CSS includes the canonical shared recipe as well as your inputs. Import a downloaded JSON recipe to continue editing it. Keep hand-written CSS in a separate overrides file so regenerating the theme does not replace it.
|
|
52
|
+
|
|
53
|
+
Changes apply throughout the documentation and remain in this browser across navigation and reload until reset. Repository files change only when you save locally. Starting points update the shell and all selected cards. Reset returns to the repository baseline. Invalid input keeps the last valid preview and explains which decision needs attention.
|
|
54
|
+
|
|
55
|
+
Text offers body, display, heading, label, caption, eyebrow and code roles. Choose a heading's document level with `as` independently from its visual role. Button and IconButton keep appearance and tone independent, with four optional shortcuts: primary (solid accent), standard (solid neutral), quiet (ghost neutral) and destructive (solid danger). A named preset takes precedence over the separate choices; `preset="none"` preserves their existing defaults.
|
|
56
|
+
|
|
57
|
+
Glassiness and surface separation govern floating surfaces and the Card glass variant. Popup purpose and measured size refine the shared treatment. Decorative material can transmit the scene; the content plane remains opaque and readable, with opaque fallbacks for reduced transparency and unsupported backdrop filtering.
|
|
58
|
+
|
|
59
|
+
## Use light, dark, or both
|
|
60
|
+
|
|
61
|
+
Every generated theme includes a coordinated light/dark pair. The type, geometry and motion decisions stay shared. Both palettes derive readable text, outlines, focus and action states from the same identity; the generator may adjust requested color seeds to maintain contrast.
|
|
62
|
+
|
|
63
|
+
Load the generated CSS after Looma's tokens and palette. Choose how it runs:
|
|
64
|
+
|
|
65
|
+
- **System preference:** leave `data-theme` off the document root. The CSS follows the operating system automatically.
|
|
66
|
+
- **Light only:** set `<html data-theme="light">`.
|
|
67
|
+
- **Dark only:** set `<html data-theme="dark">`.
|
|
68
|
+
- **User switch:** set the root attribute to `light` or `dark`, and remove it to return to system preference. Persist that choice in your application if desired.
|
|
69
|
+
|
|
70
|
+
Fixed-mode applications still receive both palettes. A mode boundary applies to its descendants, so a dark panel can contain a light subsection without changing the page or its siblings:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<section data-theme="dark">
|
|
74
|
+
<h2>After-hours Fellowship office</h2>
|
|
75
|
+
<div data-theme="light">Sam's daylight reading area</div>
|
|
76
|
+
</section>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use the public semantic color properties inside those regions. A floating surface moved elsewhere in the DOM must retain its originating mode boundary; Looma's isolated documentation previews receive their card's mode automatically.
|
|
80
|
+
|
|
81
|
+
## Add your components
|
|
82
|
+
|
|
83
|
+
Opt into source directories in `looma.docs.json`:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"title": "The Fellowship",
|
|
88
|
+
"base": "/",
|
|
89
|
+
"outDir": ".looma/site",
|
|
90
|
+
"content": "docs",
|
|
91
|
+
"theme": { "recipe": "looma.theme.json", "css": "looma.theme.css" },
|
|
92
|
+
"components": [{ "directory": "app/components", "packageName": "fellowship-ui" }],
|
|
93
|
+
"overrides": ["styles/design-system-overrides.css"]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`content` and `overrides` are optional. Create files you configure before building. Markdown guidance supplements the generated reference; it cannot replace an API page. Relative component and asset dependencies keep their original project locations, and code examples remain literal. Local web fonts referenced by override CSS are embedded in the documentation's theme stylesheet so isolated examples render the same fonts on static hosts. Load your chosen web fonts in the consuming application; downloaded theme CSS selects their font families.
|
|
98
|
+
|
|
99
|
+
A resource can use any valid HTML Next component tag:
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<template component="office-status">
|
|
103
|
+
<defs>
|
|
104
|
+
<prop name="label" type="string" default="Ready">The team's current status.</prop>
|
|
105
|
+
</defs>
|
|
106
|
+
<p>{$label}<slot></slot></p>
|
|
107
|
+
</template>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Place a `docs.json` beside it to join a shared category:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"title": "Office status",
|
|
115
|
+
"description": "A status for the Fellowship office.",
|
|
116
|
+
"group": "display"
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Looma's category IDs are listed in its published catalog. Components without a category appear in **Project components**. A discovery root can declare additional `groups` as `{ "id": "…", "label": "…" }` entries. The top-level `categories` mapping explicitly maps category IDs when combining catalogs.
|
|
121
|
+
|
|
122
|
+
Put authored examples in an `examples/` directory beside the resource. Each `.html` example starts with a metadata comment:
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<!--
|
|
126
|
+
title: Ready for second breakfast
|
|
127
|
+
description: Sam has finished the morning checklist.
|
|
128
|
+
-->
|
|
129
|
+
<office-status label="Ready for second breakfast"></office-status>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The example appears in both the reference and the customizer's selection list. Optional `.behavior.js` or `.behavior.ts` companions export a default setup function receiving the example root; return a cleanup function when needed. Add `preview: resizable` to expose the shared card's width control. Framework-specific `.snippet.html` and `.vue` companions provide literal source views.
|
|
133
|
+
|
|
134
|
+
Discovery reads definitions and descriptions without executing their controllers. New resources under a selected root appear after the local server rebuilds. External components keep their own contracts and supported token hooks; discovery does not invent a Vue adapter or make arbitrary components respond to Looma's tokens.
|
|
135
|
+
|
|
136
|
+
## Combine installed catalogs
|
|
137
|
+
|
|
138
|
+
A library can publish the versioned catalog shape exposed by `@threadlabs/looma/docs/catalog.json`. Add package exports or relative JSON files under `catalogs` in the configuration. Component identities are package-qualified; duplicate tags, conflicting categories and missing compound parents fail with ownership information instead of silently replacing a component.
|
|
139
|
+
|
|
140
|
+
Use the `@threadlabs/looma/docs` entrypoint for programmatic discovery, catalog merging, theme validation, and build/dev/preview integration. Its TypeScript definitions describe the configuration and catalog interfaces. The browser card renderer, catalog data and compiled example companions ship in the package.
|
|
141
|
+
|
|
142
|
+
## Saving and recovery
|
|
143
|
+
|
|
144
|
+
The local editor writes only the configured recipe and CSS inside the project. It rejects symlink paths, generated or dependency directories, cross-origin save requests and stale file revisions. A competing edit leaves your unsaved preview in place; reload and reconcile it before saving again. Examples run in isolated frames and receive theme CSS, never the local editing capability.
|
|
145
|
+
|
|
146
|
+
The writer stages complete files, serializes saves with a project lock, and rolls back the recipe if replacing the CSS fails. If a process stops during a save or build, close the old editor before removing its `.looma/theme-write.lock` or `.looma/docs-build.lock`; restart and save the loaded recipe to regenerate a consistent pair. Change the server base, output directory or theme-file paths by restarting `dev`.
|
package/docs/card.html
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<link rel="component" href="@threadlabs/looma/components/ui-form-field/ui-form-field.html">
|
|
2
|
+
<link rel="component" href="@threadlabs/looma/components/ui-input/ui-input.html">
|
|
3
|
+
<link rel="component" href="@threadlabs/looma/components/ui-button/ui-button.html">
|
|
4
|
+
<template component="looma-example-card" module="./card.js">
|
|
5
|
+
<defs>
|
|
6
|
+
<prop name="identity" type="string" default="">Stable catalog example identity.</prop>
|
|
7
|
+
<prop name="title" type="string" default="Example">The documented component name.</prop>
|
|
8
|
+
<prop name="description" type="string" default="">The component's authored description.</prop>
|
|
9
|
+
<prop name="exampleTitle" type="string" default="">The selected authored example.</prop>
|
|
10
|
+
<prop name="reference" type="boolean" default="false">Use the spacious full-reference example presentation.</prop>
|
|
11
|
+
<prop name="src" type="string" default="">The compiled isolated example page.</prop>
|
|
12
|
+
<prop name="resizable" type="boolean" default="false">Expose a width control for responsive examples.</prop>
|
|
13
|
+
<prop name="category" type="string" default="forms">The shared category identity.</prop>
|
|
14
|
+
<prop name="tag" type="string" default="">The component's authored tag.</prop>
|
|
15
|
+
<prop name="packageName" type="string" default="">The package that owns this component.</prop>
|
|
16
|
+
<prop name="nativeRoot" type="string" default="">The component's semantic native root.</prop>
|
|
17
|
+
<prop name="href" type="string" default="">The component reference page.</prop>
|
|
18
|
+
<prop name="removable" type="boolean" default="false">Offer removal when this card is selected in the customizer.</prop>
|
|
19
|
+
<prop name="loading" type="keyword" values="lazy, eager" default="lazy">Load visible catalog cards lazily; selected customizer cards eagerly.</prop>
|
|
20
|
+
</defs>
|
|
21
|
+
<article from:data-example-card="$identity" from:data-component-card="$tag" from:data-category="$category">
|
|
22
|
+
<header $if="$reference" class="example-heading"><slot name="heading"><h3>{$exampleTitle}</h3><p>{$description}</p></slot></header>
|
|
23
|
+
<div class="preview"><iframe $ref="frame" from:title="$title" from:src="$src" sandbox="allow-scripts" from:loading="$loading" width="100%" height="208"></iframe></div>
|
|
24
|
+
<section $if="$reference" class="example-code"><h4>Code</h4><slot name="code"></slot></section>
|
|
25
|
+
<div $if="$resizable" class="preview-width"> <ui-form-field><label slot="label">Preview width</label><span slot="help">Explore the example at a narrower width.</span><ui-input $ref="width" type="number" min="240" max="1600" value="720" aria-label="Preview width in pixels" size="sm"></ui-input></ui-form-field></div>
|
|
26
|
+
<div $if="not $reference" class="body">
|
|
27
|
+
<div class="meta"><span>{$category}</span><code>{$nativeRoot}</code></div>
|
|
28
|
+
<h3><a from:href="$href">{$title}</a></h3>
|
|
29
|
+
<p class="description">{$description}</p>
|
|
30
|
+
<p class="owner" $if="$packageName">{$packageName}</p>
|
|
31
|
+
<p class="example-caption" $if="$exampleTitle">{$exampleTitle}</p>
|
|
32
|
+
|
|
33
|
+
<footer><ui-button $if="$href" as="a" from:href="$href" variant="link" size="sm">View component</ui-button><ui-button $if="$removable" $ref="remove" variant="ghost" size="sm">Remove</ui-button><slot name="actions"></slot></footer>
|
|
34
|
+
</div>
|
|
35
|
+
</article>
|
|
36
|
+
<style>
|
|
37
|
+
/* The existing documentation catalog card, shared by the directory, reference and customizer. */
|
|
38
|
+
:host {
|
|
39
|
+
--_card-accent: var(--ui-accent);
|
|
40
|
+
position: relative;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
background: var(--ui-surface-raised);
|
|
43
|
+
border: var(--ui-border-width) solid var(--ui-border);
|
|
44
|
+
border-radius: var(--ui-radius-lg);
|
|
45
|
+
box-shadow: var(--ui-shadow-sm);
|
|
46
|
+
transition: border-color var(--ui-motion-fast) var(--ui-motion-ease), box-shadow var(--ui-motion-fast) var(--ui-motion-ease), transform var(--ui-motion-fast) var(--ui-motion-ease);
|
|
47
|
+
}
|
|
48
|
+
:host([category="display"]) { --_card-accent: var(--ui-success); }
|
|
49
|
+
:host([category="overlay"]) { --_card-accent: var(--ui-danger); }
|
|
50
|
+
:host([category="layout"]) { --_card-accent: var(--ui-info); }
|
|
51
|
+
:host([category="editor"]) { --_card-accent: var(--ui-warning); }
|
|
52
|
+
:host:hover { border-color: color-mix(in srgb, var(--_card-accent) 45%, var(--ui-border)); box-shadow: var(--ui-shadow-md); transform: translateY(-2px); }
|
|
53
|
+
:host([reference]):hover { transform: none; border-color: var(--ui-border); box-shadow: var(--ui-shadow-sm); }
|
|
54
|
+
.example-heading { padding: var(--ui-space-5) !important; border-block-end: var(--ui-border-width) solid var(--ui-border); }
|
|
55
|
+
.example-code { padding: var(--ui-space-5) !important; border-block-start: var(--ui-border-width) solid var(--ui-border); }
|
|
56
|
+
.example-code h4 { margin: 0 0 var(--ui-space-3) !important; font-size: var(--ui-font-size-md); }
|
|
57
|
+
.example-heading p { margin: var(--ui-space-2) 0 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-sm); }
|
|
58
|
+
:host([reference]) .preview { min-block-size: 11rem; display: grid !important; align-items: center !important; border-block-end: 0; }
|
|
59
|
+
:host([reference]) iframe { min-block-size: 11rem; }
|
|
60
|
+
:host([reference][category="overlay"]) iframe { min-block-size: 26rem; }
|
|
61
|
+
:host([reference]) .preview::before { display: none; }
|
|
62
|
+
.preview { position: relative; min-block-size: 13rem; overflow: hidden; border-block-end: var(--ui-border-width) solid var(--ui-border); background: color-mix(in srgb, var(--_card-accent) 5%, var(--ui-surface)); }
|
|
63
|
+
.preview::before { position: absolute; inset-block-start: 0; inset-inline-start: 0; inline-size: 3.5rem; block-size: 3px; background: var(--_card-accent); content: ""; z-index: 1; }
|
|
64
|
+
iframe { display: block !important; max-inline-size: 100%; border: 0; }
|
|
65
|
+
footer { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: var(--ui-space-2) !important; }
|
|
66
|
+
.preview-width { padding: var(--ui-space-4) !important; }
|
|
67
|
+
.body { padding: var(--ui-space-5) !important; }
|
|
68
|
+
.meta { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: var(--ui-space-2) !important; margin-block-end: var(--ui-space-2) !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-xs); font-weight: var(--ui-font-bold); letter-spacing: .09em; text-transform: uppercase; }
|
|
69
|
+
.meta code { padding: 0 !important; background: transparent; color: inherit; text-transform: none; }
|
|
70
|
+
h3 { margin: 0 !important; padding: 0 !important; font-size: var(--ui-font-size-lg); letter-spacing: -.035em; }
|
|
71
|
+
h3 a { color: var(--ui-text); text-decoration: none; }
|
|
72
|
+
.description { margin: var(--ui-space-2) 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-sm); line-height: var(--ui-line-height); }
|
|
73
|
+
.owner { margin: var(--ui-space-2) 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-xs); overflow-wrap: anywhere; }
|
|
74
|
+
.example-caption { margin: var(--ui-space-2) 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-xs); }
|
|
75
|
+
@media (prefers-reduced-motion: reduce) { :host { transition: none; } :host:hover { transform: none; } }
|
|
76
|
+
</style>
|
|
77
|
+
</template>
|
package/docs/card.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { documentationTheme, themeEvent } from './theme-session.mjs';
|
|
2
|
+
/** Only theme data and measured height cross the opaque preview boundary. */
|
|
3
|
+
export function connect(host) {
|
|
4
|
+
const frame = host.refs.frame;
|
|
5
|
+
const width = host.refs.width;
|
|
6
|
+
const remove = host.refs.remove;
|
|
7
|
+
const removeCard = () => host.element.dispatchEvent(new CustomEvent("looma-example-remove", { bubbles: true, detail: { id: host.props.identity.value } }));
|
|
8
|
+
const view = frame.ownerDocument.defaultView;
|
|
9
|
+
let theme = documentationTheme(view)?.css ?? '';
|
|
10
|
+
const documentTheme = () => { theme = documentationTheme(view)?.css ?? ''; send(); };
|
|
11
|
+
const preference = view.matchMedia('(prefers-color-scheme: dark)');
|
|
12
|
+
const send = () => {
|
|
13
|
+
const scheme = view.getComputedStyle(host.element).colorScheme;
|
|
14
|
+
const mode = scheme === 'dark' || (scheme.includes('dark') && scheme.includes('light') && preference.matches) ? 'dark' : 'light';
|
|
15
|
+
frame.contentWindow?.postMessage({ type: 'looma-theme', css: theme, mode, reference: Boolean(host.props.reference.value) }, '*');
|
|
16
|
+
};
|
|
17
|
+
// Observe only this card's ancestors: an opposite-mode boundary belongs to its own subtree.
|
|
18
|
+
const modes = new view.MutationObserver(send);
|
|
19
|
+
for (let ancestor = host.element; ancestor; ancestor = ancestor.parentElement) modes.observe(ancestor, { attributes: true, attributeFilter: ['data-theme', 'style', 'class'] });
|
|
20
|
+
const message = event => {
|
|
21
|
+
if (event.source !== frame.contentWindow) return;
|
|
22
|
+
if (event.data?.type === 'looma-preview-ready') send();
|
|
23
|
+
if (event.data?.type === 'looma-preview-height' && Number.isFinite(event.data.height)) frame.height = String(Math.max(120, Math.min(2400, Math.ceil(event.data.height))));
|
|
24
|
+
};
|
|
25
|
+
const update = event => { theme = event.detail.css; send(); };
|
|
26
|
+
const resize = event => {
|
|
27
|
+
const value = Number(event.detail?.value ?? event.target.value);
|
|
28
|
+
if (value >= 240 && value <= 1600) frame.width = String(value);
|
|
29
|
+
};
|
|
30
|
+
host.element.addEventListener('looma-theme-preview', update);
|
|
31
|
+
width?.addEventListener('input', resize);
|
|
32
|
+
remove?.addEventListener('click', removeCard);
|
|
33
|
+
view.addEventListener('message', message);
|
|
34
|
+
view.addEventListener(themeEvent, documentTheme);
|
|
35
|
+
frame.addEventListener('load', send);
|
|
36
|
+
preference.addEventListener('change', send);
|
|
37
|
+
return () => {
|
|
38
|
+
host.element.removeEventListener('looma-theme-preview', update);
|
|
39
|
+
width?.removeEventListener('input', resize);
|
|
40
|
+
remove?.removeEventListener('click', removeCard);
|
|
41
|
+
view.removeEventListener('message', message);
|
|
42
|
+
view.removeEventListener(themeEvent, documentTheme);
|
|
43
|
+
frame.removeEventListener('load', send);
|
|
44
|
+
preference.removeEventListener('change', send);
|
|
45
|
+
modes.disconnect();
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Native catalog entries own their visibility; component previews keep their own layout. */
|
|
2
|
+
export function connect(host) {
|
|
3
|
+
const { query, category, count } = host.refs;
|
|
4
|
+
const groups = [...host.element.querySelectorAll('[data-catalog-group]')];
|
|
5
|
+
const items = [...host.element.querySelectorAll('[data-catalog-search]')];
|
|
6
|
+
const filter = () => {
|
|
7
|
+
const words = query.value.toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
8
|
+
let visible = 0;
|
|
9
|
+
for (const item of items) {
|
|
10
|
+
item.hidden = Boolean(category.value && item.dataset.category !== category.value) || !words.every(word => item.dataset.catalogSearch.toLocaleLowerCase().includes(word));
|
|
11
|
+
if (!item.hidden) visible++;
|
|
12
|
+
}
|
|
13
|
+
for (const group of groups) group.hidden = ![...group.querySelectorAll('[data-catalog-search]')].some(item => !item.hidden);
|
|
14
|
+
count.textContent = visible === 0 ? 'No components match. Try another name or category.' : `${visible} component${visible === 1 ? '' : 's'}`;
|
|
15
|
+
};
|
|
16
|
+
query.addEventListener('input', filter); category.addEventListener('change', filter); filter();
|
|
17
|
+
return () => { query.removeEventListener('input', filter); category.removeEventListener('change', filter); };
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { escapeHtml as escape } from './catalog.mjs';
|
|
2
|
+
|
|
3
|
+
/** Catalog filtering wraps the same server-rendered documentation cards. */
|
|
4
|
+
export function catalogResource(groups) {
|
|
5
|
+
return `<link rel="component" href="@threadlabs/looma/components/ui-form-field/ui-form-field.html">
|
|
6
|
+
<link rel="component" href="@threadlabs/looma/components/ui-input/ui-input.html">
|
|
7
|
+
<link rel="component" href="@threadlabs/looma/components/ui-select/ui-select.html">
|
|
8
|
+
<link rel="component" href="@threadlabs/looma/components/ui-cluster/ui-cluster.html">
|
|
9
|
+
<template component="looma-catalog" module="./catalog-view.js"><defs></defs>
|
|
10
|
+
<div><ui-cluster align="end"><ui-form-field><label slot="label" for="catalog-query">Find a component</label><ui-input id="catalog-query" $ref="query" type="search" placeholder="Name, tag or purpose"></ui-input></ui-form-field><ui-form-field><label slot="label" for="catalog-category">Category</label><ui-select id="catalog-category" $ref="category"><option value="">All categories</option>${groups.map(group => `<option value="${escape(group.id)}">${escape(group.label)}</option>`).join('')}</ui-select></ui-form-field></ui-cluster><p $ref="count" role="status" aria-live="polite"></p><slot></slot></div></template>`;
|
|
11
|
+
}
|