@deepfuture/dui-components 0.0.16 → 0.0.17

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 CHANGED
@@ -146,6 +146,36 @@ DUI uses CSS custom properties for theming. Toggle dark mode by adding `class="d
146
146
  </body>
147
147
  ```
148
148
 
149
+ ## Templates
150
+
151
+ Pre-composed UI patterns built from DUI components — ready-to-use cards, feed items, and other building blocks. Templates are theme-scoped: they use the default theme's variant vocabulary and tokens, so they adapt automatically to dark mode and custom token overrides.
152
+
153
+ ```bash
154
+ npm install @deepfuture/dui-theme-default-templates
155
+ ```
156
+
157
+ ```typescript
158
+ import { DuiFeedItem } from "@deepfuture/dui-theme-default-templates/feed";
159
+
160
+ applyTheme({
161
+ theme: defaultTheme,
162
+ components: [DuiFeedItem], // dependencies (DuiBadge, etc.) auto-register
163
+ });
164
+ ```
165
+
166
+ ```html
167
+ <dui-feed-item
168
+ title="Earthquake detected"
169
+ subtitle="USGS Pacific Northwest"
170
+ timestamp="2 min ago"
171
+ category="Seismic"
172
+ severity="high"
173
+ description="Magnitude 4.2 recorded near Portland, OR."
174
+ ></dui-feed-item>
175
+ ```
176
+
177
+ Templates declare their component dependencies via `static dependencies` — `applyTheme` auto-registers them, so you don't need to import `DuiBadge` separately.
178
+
149
179
  ## Packages
150
180
 
151
181
  | Package | Purpose |
@@ -153,6 +183,7 @@ DUI uses CSS custom properties for theming. Toggle dark mode by adding `class="d
153
183
  | [`@deepfuture/dui-core`](https://www.npmjs.com/package/@deepfuture/dui-core) | `applyTheme()`, event factory, base styles |
154
184
  | [`@deepfuture/dui-components`](https://www.npmjs.com/package/@deepfuture/dui-components) | Unstyled component classes |
155
185
  | [`@deepfuture/dui-theme-default`](https://www.npmjs.com/package/@deepfuture/dui-theme-default) | Design tokens + aesthetic styles |
186
+ | [`@deepfuture/dui-theme-default-templates`](https://www.npmjs.com/package/@deepfuture/dui-theme-default-templates) | Pre-composed UI patterns for the default theme |
156
187
  | [`@deepfuture/dui-cdn`](https://www.npmjs.com/package/@deepfuture/dui-cdn) | Pre-bundled CDN build (all deps inlined) |
157
188
 
158
189
  ## Dev Tools
@@ -177,6 +208,7 @@ See **[Inspector docs](docs/inspector.md)** for the full API reference and usage
177
208
  - **[Live Docs](https://deepfuturenow.github.io/dui/)** — interactive demos for every component
178
209
  - [Architecture](docs/architecture.md) — mental model, package responsibilities, design decisions
179
210
  - [Creating Components](docs/creating-components.md) — guide for adding new components
211
+ - [Creating Templates](docs/creating-templates.md) — guide for building theme-scoped templates
180
212
  - [Theming](docs/theming.md) — theme system, design tokens, writing component styles
181
213
  - [Consuming](docs/consuming.md) — integrating DUI into an app
182
214
  - [Inspector](docs/inspector.md) — runtime inspection, mutation API, and visual editor
@@ -67,30 +67,12 @@ const styles = css `
67
67
  cursor: default;
68
68
  }
69
69
 
70
- [part="indicator"] {
71
- flex-shrink: 0;
72
- }
73
-
74
70
  [part="panel"] {
75
71
  overflow: hidden;
76
72
  contain: content;
77
73
  transition-property: height;
78
74
  }
79
75
  `;
80
- // Inline chevron-down SVG to avoid icon component dependency
81
- const chevronSvg = html `<svg
82
- part="indicator"
83
- width="16"
84
- height="16"
85
- viewBox="0 0 24 24"
86
- fill="none"
87
- stroke="currentColor"
88
- stroke-width="2"
89
- stroke-linecap="round"
90
- stroke-linejoin="round"
91
- >
92
- <path d="m6 9 6 6 6-6" />
93
- </svg>`;
94
76
  let DuiAccordionItem = (() => {
95
77
  let _classSuper = LitElement;
96
78
  let _value_decorators;
@@ -292,7 +274,6 @@ let DuiAccordionItem = (() => {
292
274
  @keydown=${this.#onKeyDown}
293
275
  >
294
276
  <slot name="trigger"></slot>
295
- ${chevronSvg}
296
277
  </button>
297
278
  </h3>
298
279
  ${shouldRender
@@ -0,0 +1,17 @@
1
+ import { LitElement, type TemplateResult } from "lit";
2
+ /**
3
+ * `<dui-card-grid>` — A responsive grid layout for cards and panels.
4
+ *
5
+ * Distributes children into equal-width columns that collapse at narrow
6
+ * container widths. Use `columns` to set the maximum column count.
7
+ *
8
+ * @slot - Grid children (cards, panels, or any block content).
9
+ * @csspart root - The grid container element.
10
+ */
11
+ export declare class DuiCardGrid extends LitElement {
12
+ static tagName: "dui-card-grid";
13
+ static styles: import("lit").CSSResult[];
14
+ /** Maximum number of columns (1–4). Responsive breakpoints reduce this automatically. */
15
+ accessor columns: string;
16
+ render(): TemplateResult;
17
+ }
@@ -0,0 +1,110 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { css, html, LitElement } from "lit";
36
+ import { property } from "lit/decorators.js";
37
+ import { base } from "@deepfuture/dui-core/base";
38
+ /** Structural styles only — layout CSS. */
39
+ const styles = css `
40
+ :host {
41
+ display: block;
42
+ min-width: 100%;
43
+ }
44
+
45
+ [part="root"] {
46
+ display: grid;
47
+ grid-template-columns: repeat(var(--_columns, 3), 1fr);
48
+ box-sizing: border-box;
49
+ }
50
+
51
+ :host([columns="1"]) { --_columns: 1; }
52
+ :host([columns="2"]) { --_columns: 2; }
53
+ :host([columns="3"]) { --_columns: 3; }
54
+ :host([columns="4"]) { --_columns: 4; }
55
+
56
+ /* Responsive collapse: narrow viewports reduce columns */
57
+ @media (max-width: 768px) {
58
+ :host([columns="3"]) { --_columns: 2; }
59
+ :host([columns="4"]) { --_columns: 2; }
60
+ }
61
+
62
+ @media (max-width: 480px) {
63
+ :host([columns="2"]),
64
+ :host([columns="3"]),
65
+ :host([columns="4"]) {
66
+ --_columns: 1;
67
+ }
68
+ }
69
+ `;
70
+ /**
71
+ * `<dui-card-grid>` — A responsive grid layout for cards and panels.
72
+ *
73
+ * Distributes children into equal-width columns that collapse at narrow
74
+ * container widths. Use `columns` to set the maximum column count.
75
+ *
76
+ * @slot - Grid children (cards, panels, or any block content).
77
+ * @csspart root - The grid container element.
78
+ */
79
+ let DuiCardGrid = (() => {
80
+ let _classSuper = LitElement;
81
+ let _columns_decorators;
82
+ let _columns_initializers = [];
83
+ let _columns_extraInitializers = [];
84
+ return class DuiCardGrid extends _classSuper {
85
+ static {
86
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
87
+ _columns_decorators = [property({ reflect: true })];
88
+ __esDecorate(this, null, _columns_decorators, { kind: "accessor", name: "columns", static: false, private: false, access: { has: obj => "columns" in obj, get: obj => obj.columns, set: (obj, value) => { obj.columns = value; } }, metadata: _metadata }, _columns_initializers, _columns_extraInitializers);
89
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
90
+ }
91
+ static tagName = "dui-card-grid";
92
+ static styles = [base, styles];
93
+ #columns_accessor_storage = __runInitializers(this, _columns_initializers, "3");
94
+ /** Maximum number of columns (1–4). Responsive breakpoints reduce this automatically. */
95
+ get columns() { return this.#columns_accessor_storage; }
96
+ set columns(value) { this.#columns_accessor_storage = value; }
97
+ render() {
98
+ return html `
99
+ <div part="root">
100
+ <slot></slot>
101
+ </div>
102
+ `;
103
+ }
104
+ constructor() {
105
+ super(...arguments);
106
+ __runInitializers(this, _columns_extraInitializers);
107
+ }
108
+ };
109
+ })();
110
+ export { DuiCardGrid };
@@ -0,0 +1,3 @@
1
+ import { DuiCardGrid } from "./card-grid.js";
2
+ export { DuiCardGrid };
3
+ export declare const cardGridFamily: (typeof DuiCardGrid)[];
@@ -0,0 +1,3 @@
1
+ import { DuiCardGrid } from "./card-grid.js";
2
+ export { DuiCardGrid };
3
+ export const cardGridFamily = [DuiCardGrid];
@@ -65,8 +65,9 @@ const styles = css `
65
65
  cursor: default;
66
66
  }
67
67
 
68
- [part="indicator"] {
69
- flex-shrink: 0;
68
+ slot[name="trigger"] {
69
+ flex: 1;
70
+ min-width: 0;
70
71
  }
71
72
 
72
73
  [part="panel"] {
@@ -75,20 +76,6 @@ const styles = css `
75
76
  transition-property: height;
76
77
  }
77
78
  `;
78
- // Inline chevron-down SVG to avoid icon component dependency
79
- const chevronSvg = html `<svg
80
- part="indicator"
81
- width="16"
82
- height="16"
83
- viewBox="0 0 24 24"
84
- fill="none"
85
- stroke="currentColor"
86
- stroke-width="2"
87
- stroke-linecap="round"
88
- stroke-linejoin="round"
89
- >
90
- <path d="m6 9 6 6 6-6" />
91
- </svg>`;
92
79
  let DuiCollapsible = (() => {
93
80
  let _classSuper = LitElement;
94
81
  let _open_decorators;
@@ -277,7 +264,6 @@ let DuiCollapsible = (() => {
277
264
  @click=${this.#onClick}
278
265
  >
279
266
  <slot name="trigger"></slot>
280
- ${chevronSvg}
281
267
  </button>
282
268
  ${shouldRender
283
269
  ? html `
@@ -42,7 +42,7 @@ const styles = css `
42
42
  display: block;
43
43
  }
44
44
 
45
- :host([data-hidden]) {
45
+ :host([data-hidden]) .Empty {
46
46
  display: none;
47
47
  }
48
48
 
@@ -45,7 +45,7 @@ const styles = css `
45
45
  display: block;
46
46
  }
47
47
 
48
- :host([data-hidden]) {
48
+ :host([data-hidden]) .Group {
49
49
  display: none;
50
50
  }
51
51
 
@@ -45,7 +45,7 @@ const styles = css `
45
45
  display: block;
46
46
  }
47
47
 
48
- :host([data-hidden]) {
48
+ :host([data-hidden]) .Item {
49
49
  display: none;
50
50
  }
51
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepfuture/dui-components",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "DUI unstyled web components — structural CSS only, themed via applyTheme()",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -174,6 +174,10 @@
174
174
  "import": "./split-button/index.js",
175
175
  "types": "./split-button/index.d.ts"
176
176
  },
177
+ "./card-grid": {
178
+ "import": "./card-grid/index.js",
179
+ "types": "./card-grid/index.d.ts"
180
+ },
177
181
  "./all": {
178
182
  "import": "./all.js",
179
183
  "types": "./all.d.ts"
@@ -186,7 +190,7 @@
186
190
  "README.md"
187
191
  ],
188
192
  "dependencies": {
189
- "@deepfuture/dui-core": "0.0.16",
193
+ "@deepfuture/dui-core": "0.0.17",
190
194
  "lit": "^3.3.2",
191
195
  "@lit/context": "^1.1.3"
192
196
  },
@@ -46,6 +46,10 @@ import { FloatingPortalController } from "@deepfuture/dui-core/floating-portal-c
46
46
  import { renderArrow, } from "@deepfuture/dui-core/floating-popup-utils";
47
47
  const hostStyles = css `
48
48
  :host {
49
+ display: contents;
50
+ }
51
+
52
+ .slot-wrapper {
49
53
  display: none;
50
54
  }
51
55
  `;
@@ -198,7 +202,7 @@ let DuiPopoverPopup = (() => {
198
202
  this.#portal.offset = this.#ctx.value?.sideOffset ?? 8;
199
203
  }
200
204
  render() {
201
- return html `<slot></slot>`;
205
+ return html `<div class="slot-wrapper"><slot></slot></div>`;
202
206
  }
203
207
  };
204
208
  })();
@@ -46,6 +46,10 @@ import { FloatingPortalController } from "@deepfuture/dui-core/floating-portal-c
46
46
  import { renderArrow, } from "@deepfuture/dui-core/floating-popup-utils";
47
47
  const hostStyles = css `
48
48
  :host {
49
+ display: contents;
50
+ }
51
+
52
+ .slot-wrapper {
49
53
  display: none;
50
54
  }
51
55
  `;
@@ -186,7 +190,7 @@ let DuiPreviewCardPopup = (() => {
186
190
  this.#portal.offset = this.#ctx.value?.sideOffset ?? 8;
187
191
  }
188
192
  render() {
189
- return html `<slot></slot>`;
193
+ return html `<div class="slot-wrapper"><slot></slot></div>`;
190
194
  }
191
195
  };
192
196
  })();
@@ -1,5 +1,5 @@
1
1
  /** Ported from original DUI: deep-future-app/app/client/components/dui/tabs */
2
- import { LitElement, nothing, type TemplateResult } from "lit";
2
+ import { LitElement, type TemplateResult } from "lit";
3
3
  import { type TabsContext } from "./tabs-context.js";
4
4
  /**
5
5
  * Content panel for a tab. Shown when the matching tab is active.
@@ -14,5 +14,5 @@ export declare class DuiTabsPanel extends LitElement {
14
14
  accessor keepMounted: boolean;
15
15
  accessor _ctx: TabsContext;
16
16
  willUpdate(): void;
17
- render(): TemplateResult | typeof nothing;
17
+ render(): TemplateResult;
18
18
  }
@@ -43,7 +43,11 @@ const styles = css `
43
43
  display: block;
44
44
  }
45
45
 
46
- :host([data-hidden]) {
46
+ .wrapper {
47
+ display: contents;
48
+ }
49
+
50
+ .wrapper[hidden] {
47
51
  display: none;
48
52
  }
49
53
 
@@ -51,10 +55,6 @@ const styles = css `
51
55
  position: relative;
52
56
  outline: 0;
53
57
  }
54
-
55
- [part="panel"][hidden] {
56
- display: none;
57
- }
58
58
  `;
59
59
  /**
60
60
  * Content panel for a tab. Shown when the matching tab is active.
@@ -107,17 +107,11 @@ let DuiTabsPanel = (() => {
107
107
  }
108
108
  render() {
109
109
  const isActive = this.#isActive;
110
- if (!isActive && !this.keepMounted) {
111
- return nothing;
112
- }
113
110
  return html `
114
- <div
115
- part="panel"
116
- role="tabpanel"
117
- ?hidden=${!isActive}
118
- tabindex="0"
119
- >
120
- <slot></slot>
111
+ <div class="wrapper" ?hidden=${!isActive}>
112
+ ${isActive || this.keepMounted
113
+ ? html `<div part="panel" role="tabpanel" tabindex="0"><slot></slot></div>`
114
+ : nothing}
121
115
  </div>
122
116
  `;
123
117
  }
@@ -46,6 +46,10 @@ import { FloatingPortalController } from "@deepfuture/dui-core/floating-portal-c
46
46
  import { renderArrow, } from "@deepfuture/dui-core/floating-popup-utils";
47
47
  const hostStyles = css `
48
48
  :host {
49
+ display: contents;
50
+ }
51
+
52
+ .slot-wrapper {
49
53
  display: none;
50
54
  }
51
55
  `;
@@ -178,7 +182,7 @@ let DuiTooltipPopup = (() => {
178
182
  this.#portal.offset = this.#ctx.value?.sideOffset ?? 6;
179
183
  }
180
184
  render() {
181
- return html `<slot></slot>`;
185
+ return html `<div class="slot-wrapper"><slot></slot></div>`;
182
186
  }
183
187
  };
184
188
  })();