@xmesh/system-design 0.0.1
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 +472 -0
- package/assets/brand-lockup-dark.svg +9 -0
- package/assets/brand-lockup-light.svg +9 -0
- package/assets/brand-mark.svg +9 -0
- package/colors_and_type.css +11 -0
- package/dist/lit/components/alert/index.css +201 -0
- package/dist/lit/components/alert/index.d.ts +25 -0
- package/dist/lit/components/alert/index.js +191 -0
- package/dist/lit/components/app-bar/index.css +80 -0
- package/dist/lit/components/app-bar/index.d.ts +19 -0
- package/dist/lit/components/app-bar/index.js +120 -0
- package/dist/lit/components/artifact/index.css +166 -0
- package/dist/lit/components/artifact/index.d.ts +37 -0
- package/dist/lit/components/artifact/index.js +294 -0
- package/dist/lit/components/autocomplete/index.css +171 -0
- package/dist/lit/components/autocomplete/index.d.ts +47 -0
- package/dist/lit/components/autocomplete/index.js +404 -0
- package/dist/lit/components/avatar/index.css +62 -0
- package/dist/lit/components/avatar/index.d.ts +19 -0
- package/dist/lit/components/avatar/index.js +112 -0
- package/dist/lit/components/avatar-group/index.css +60 -0
- package/dist/lit/components/avatar-group/index.d.ts +19 -0
- package/dist/lit/components/avatar-group/index.js +97 -0
- package/dist/lit/components/badge/index.css +72 -0
- package/dist/lit/components/badge/index.d.ts +18 -0
- package/dist/lit/components/badge/index.js +115 -0
- package/dist/lit/components/brand-mark/index.css +109 -0
- package/dist/lit/components/brand-mark/index.d.ts +24 -0
- package/dist/lit/components/brand-mark/index.js +116 -0
- package/dist/lit/components/breadcrumbs/index.css +91 -0
- package/dist/lit/components/breadcrumbs/index.d.ts +19 -0
- package/dist/lit/components/breadcrumbs/index.js +104 -0
- package/dist/lit/components/bubble/index.css +182 -0
- package/dist/lit/components/bubble/index.d.ts +72 -0
- package/dist/lit/components/bubble/index.js +617 -0
- package/dist/lit/components/button/index.css +342 -0
- package/dist/lit/components/button/index.d.ts +32 -0
- package/dist/lit/components/button/index.js +202 -0
- package/dist/lit/components/card/index.css +99 -0
- package/dist/lit/components/card/index.d.ts +20 -0
- package/dist/lit/components/card/index.js +133 -0
- package/dist/lit/components/chat/index.css +292 -0
- package/dist/lit/components/chat/index.d.ts +74 -0
- package/dist/lit/components/chat/index.js +589 -0
- package/dist/lit/components/checkbox/index.css +126 -0
- package/dist/lit/components/checkbox/index.d.ts +21 -0
- package/dist/lit/components/checkbox/index.js +138 -0
- package/dist/lit/components/chip/index.css +145 -0
- package/dist/lit/components/chip/index.d.ts +30 -0
- package/dist/lit/components/chip/index.js +230 -0
- package/dist/lit/components/chip-group/index.css +19 -0
- package/dist/lit/components/chip-group/index.d.ts +24 -0
- package/dist/lit/components/chip-group/index.js +171 -0
- package/dist/lit/components/code/index.css +42 -0
- package/dist/lit/components/code/index.d.ts +12 -0
- package/dist/lit/components/code/index.js +68 -0
- package/dist/lit/components/composer/index.css +548 -0
- package/dist/lit/components/composer/index.d.ts +67 -0
- package/dist/lit/components/composer/index.js +713 -0
- package/dist/lit/components/data-table/index.css +166 -0
- package/dist/lit/components/data-table/index.d.ts +55 -0
- package/dist/lit/components/data-table/index.js +390 -0
- package/dist/lit/components/dialog/index.css +124 -0
- package/dist/lit/components/dialog/index.d.ts +24 -0
- package/dist/lit/components/dialog/index.js +199 -0
- package/dist/lit/components/divider/index.css +27 -0
- package/dist/lit/components/divider/index.d.ts +13 -0
- package/dist/lit/components/divider/index.js +67 -0
- package/dist/lit/components/empty-state/index.css +69 -0
- package/dist/lit/components/empty-state/index.d.ts +21 -0
- package/dist/lit/components/empty-state/index.js +123 -0
- package/dist/lit/components/expansion-panel/index.css +120 -0
- package/dist/lit/components/expansion-panel/index.d.ts +22 -0
- package/dist/lit/components/expansion-panel/index.js +174 -0
- package/dist/lit/components/field/index.css +223 -0
- package/dist/lit/components/field/index.d.ts +106 -0
- package/dist/lit/components/field/index.js +388 -0
- package/dist/lit/components/file-input/index.css +257 -0
- package/dist/lit/components/file-input/index.d.ts +30 -0
- package/dist/lit/components/file-input/index.js +298 -0
- package/dist/lit/components/form/index.css +29 -0
- package/dist/lit/components/form/index.d.ts +38 -0
- package/dist/lit/components/form/index.js +192 -0
- package/dist/lit/components/grid/index.css +53 -0
- package/dist/lit/components/grid/index.d.ts +14 -0
- package/dist/lit/components/grid/index.js +82 -0
- package/dist/lit/components/kbd/index.css +35 -0
- package/dist/lit/components/kbd/index.d.ts +11 -0
- package/dist/lit/components/kbd/index.js +43 -0
- package/dist/lit/components/list/index.css +15 -0
- package/dist/lit/components/list/index.d.ts +28 -0
- package/dist/lit/components/list/index.js +188 -0
- package/dist/lit/components/list-item/index.css +119 -0
- package/dist/lit/components/list-item/index.d.ts +20 -0
- package/dist/lit/components/list-item/index.js +127 -0
- package/dist/lit/components/menu/index.css +94 -0
- package/dist/lit/components/menu/index.d.ts +47 -0
- package/dist/lit/components/menu/index.js +386 -0
- package/dist/lit/components/navigation-drawer/index.css +114 -0
- package/dist/lit/components/navigation-drawer/index.d.ts +29 -0
- package/dist/lit/components/navigation-drawer/index.js +218 -0
- package/dist/lit/components/overlay/index.css +171 -0
- package/dist/lit/components/overlay/index.d.ts +65 -0
- package/dist/lit/components/overlay/index.js +566 -0
- package/dist/lit/components/pagination/index.css +102 -0
- package/dist/lit/components/pagination/index.d.ts +22 -0
- package/dist/lit/components/pagination/index.js +184 -0
- package/dist/lit/components/primitives/index.css +504 -0
- package/dist/lit/components/primitives/index.d.ts +25 -0
- package/dist/lit/components/primitives/index.js +283 -0
- package/dist/lit/components/progress/index.css +143 -0
- package/dist/lit/components/progress/index.d.ts +23 -0
- package/dist/lit/components/progress/index.js +180 -0
- package/dist/lit/components/radio-group/index.css +178 -0
- package/dist/lit/components/radio-group/index.d.ts +35 -0
- package/dist/lit/components/radio-group/index.js +292 -0
- package/dist/lit/components/select/index.css +151 -0
- package/dist/lit/components/select/index.d.ts +50 -0
- package/dist/lit/components/select/index.js +390 -0
- package/dist/lit/components/sidebar-item/index.css +133 -0
- package/dist/lit/components/sidebar-item/index.d.ts +20 -0
- package/dist/lit/components/sidebar-item/index.js +105 -0
- package/dist/lit/components/skeleton/index.css +81 -0
- package/dist/lit/components/skeleton/index.d.ts +19 -0
- package/dist/lit/components/skeleton/index.js +119 -0
- package/dist/lit/components/slider/index.css +171 -0
- package/dist/lit/components/slider/index.d.ts +36 -0
- package/dist/lit/components/slider/index.js +302 -0
- package/dist/lit/components/snackbar/index.css +279 -0
- package/dist/lit/components/snackbar/index.d.ts +33 -0
- package/dist/lit/components/snackbar/index.js +195 -0
- package/dist/lit/components/stack/index.css +41 -0
- package/dist/lit/components/stack/index.d.ts +20 -0
- package/dist/lit/components/stack/index.js +103 -0
- package/dist/lit/components/switch/index.css +126 -0
- package/dist/lit/components/switch/index.d.ts +17 -0
- package/dist/lit/components/switch/index.js +116 -0
- package/dist/lit/components/table/index.css +85 -0
- package/dist/lit/components/table/index.d.ts +25 -0
- package/dist/lit/components/table/index.js +139 -0
- package/dist/lit/components/tabs/index.css +116 -0
- package/dist/lit/components/tabs/index.d.ts +49 -0
- package/dist/lit/components/tabs/index.js +320 -0
- package/dist/lit/components/text-field/index.css +90 -0
- package/dist/lit/components/text-field/index.d.ts +17 -0
- package/dist/lit/components/text-field/index.js +101 -0
- package/dist/lit/components/textarea/index.css +55 -0
- package/dist/lit/components/textarea/index.d.ts +26 -0
- package/dist/lit/components/textarea/index.js +124 -0
- package/dist/lit/components/tooltip/index.css +37 -0
- package/dist/lit/components/tooltip/index.d.ts +31 -0
- package/dist/lit/components/tooltip/index.js +196 -0
- package/dist/lit/components/validation/index.css +386 -0
- package/dist/lit/components/validation/index.d.ts +45 -0
- package/dist/lit/components/validation/index.js +318 -0
- package/dist/lit/index.d.ts +50 -0
- package/dist/lit/index.js +59 -0
- package/package.json +81 -0
- package/styles/README.md +346 -0
- package/styles/_elevation.css +24 -0
- package/styles/_fonts.css +6 -0
- package/styles/_layout.css +37 -0
- package/styles/_primitives.css +154 -0
- package/styles/_scroll.css +75 -0
- package/styles/_semantic.css +146 -0
- package/styles/_space.css +61 -0
- package/styles/_type.css +139 -0
- package/styles/_xmesh-extensions.css +232 -0
- package/styles/index.css +44 -0
- package/styles/md3/_color.css +102 -0
- package/styles/md3/_elevation.css +26 -0
- package/styles/md3/_motion.css +35 -0
- package/styles/md3/_shape.css +22 -0
- package/styles/md3/_state.css +22 -0
- package/styles/md3/_type.css +111 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Dialog — a centered modal panel.
|
|
3
|
+
|
|
4
|
+
The panel chrome (inverse-surface fill, inverse-on-surface ink, hairline
|
|
5
|
+
border, corner-large radius, level5 elevation, the scrim ::backdrop, and the
|
|
6
|
+
centered modal positioning) is owned by the composed <xm-overlay> in the
|
|
7
|
+
`dialog` tier (modal mode). This file styles the panel's internal layout:
|
|
8
|
+
the header row + close control, the body, and the singular action row.
|
|
9
|
+
|
|
10
|
+
All ink is inverse-on-surface (AD-13) — the card-stack ink, matching the
|
|
11
|
+
theme-following panel surface (ADR 0006). Scrim is a flat alpha on
|
|
12
|
+
--md-sys-color-scrim (owned by the overlay); NO gradient (the snackbar scrim
|
|
13
|
+
is the only sanctioned one).
|
|
14
|
+
|
|
15
|
+
Entry/exit is opacity + a small translateY at short4 standard — no scale-in
|
|
16
|
+
reveal, no spring. The overlay owns the fade; this adds the lift.
|
|
17
|
+
|
|
18
|
+
BEM block: `dialog`. Registered in scripts/check-bem.sh STRICT_BLOCKS.
|
|
19
|
+
Elements: __head, __header, __close, __body, __actions. Modifier: --static.
|
|
20
|
+
============================================ */
|
|
21
|
+
|
|
22
|
+
.dialog {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: var(--s-4);
|
|
26
|
+
min-width: 0;
|
|
27
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
28
|
+
padding: var(--s-5);
|
|
29
|
+
animation: dialog-in var(--md-sys-motion-duration-short4)
|
|
30
|
+
var(--md-sys-motion-easing-standard) both;
|
|
31
|
+
}
|
|
32
|
+
.dialog--static {
|
|
33
|
+
animation: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@keyframes dialog-in {
|
|
37
|
+
from {
|
|
38
|
+
opacity: 0;
|
|
39
|
+
transform: translateY(8px);
|
|
40
|
+
}
|
|
41
|
+
to {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
transform: translateY(0);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dialog__head {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: flex-start;
|
|
50
|
+
justify-content: space-between;
|
|
51
|
+
gap: var(--s-3);
|
|
52
|
+
}
|
|
53
|
+
.dialog__head.is-empty {
|
|
54
|
+
/* No header text — keep the close control but drop the title row height. */
|
|
55
|
+
justify-content: flex-end;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dialog__header {
|
|
59
|
+
font-family: var(--md-sys-typescale-title-large-font);
|
|
60
|
+
font-size: var(--md-sys-typescale-title-large-size);
|
|
61
|
+
line-height: var(--md-sys-typescale-title-large-line-height);
|
|
62
|
+
font-weight: var(--md-sys-typescale-title-large-weight);
|
|
63
|
+
letter-spacing: var(--md-sys-typescale-title-large-tracking);
|
|
64
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
65
|
+
text-wrap: pretty;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.dialog__close {
|
|
69
|
+
appearance: none;
|
|
70
|
+
border: none;
|
|
71
|
+
background: transparent;
|
|
72
|
+
color: color-mix(
|
|
73
|
+
in oklch,
|
|
74
|
+
var(--md-sys-color-inverse-on-surface) 70%,
|
|
75
|
+
transparent
|
|
76
|
+
);
|
|
77
|
+
width: 32px;
|
|
78
|
+
height: 32px;
|
|
79
|
+
border-radius: var(--md-sys-shape-corner-small);
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
margin: calc(-1 * var(--s-1)) calc(-1 * var(--s-1)) 0 0;
|
|
86
|
+
transition: background var(--md-sys-motion-duration-short3)
|
|
87
|
+
var(--md-sys-motion-easing-standard);
|
|
88
|
+
}
|
|
89
|
+
.dialog__close:hover {
|
|
90
|
+
background: color-mix(
|
|
91
|
+
in oklch,
|
|
92
|
+
var(--md-sys-color-inverse-on-surface) 10%,
|
|
93
|
+
transparent
|
|
94
|
+
);
|
|
95
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
96
|
+
}
|
|
97
|
+
.dialog__close:focus-visible {
|
|
98
|
+
outline: none;
|
|
99
|
+
box-shadow: var(--xm-state-focus-ring);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.dialog__body {
|
|
103
|
+
min-width: 0;
|
|
104
|
+
font-family: var(--md-sys-typescale-body-medium-font);
|
|
105
|
+
font-size: var(--md-sys-typescale-body-medium-size);
|
|
106
|
+
line-height: var(--md-sys-typescale-body-medium-line-height);
|
|
107
|
+
color: color-mix(
|
|
108
|
+
in oklch,
|
|
109
|
+
var(--md-sys-color-inverse-on-surface) 88%,
|
|
110
|
+
transparent
|
|
111
|
+
);
|
|
112
|
+
text-wrap: pretty;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.dialog__actions {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: flex-end;
|
|
119
|
+
gap: var(--s-2);
|
|
120
|
+
flex-wrap: wrap;
|
|
121
|
+
}
|
|
122
|
+
.dialog__actions.is-empty {
|
|
123
|
+
display: none;
|
|
124
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
export declare class XmDialog extends LitElement {
|
|
4
|
+
open: boolean;
|
|
5
|
+
noScrimClose: boolean;
|
|
6
|
+
static: boolean;
|
|
7
|
+
label: string;
|
|
8
|
+
private _hasHeader;
|
|
9
|
+
private _hasAction;
|
|
10
|
+
private _overlay;
|
|
11
|
+
private readonly _headerId;
|
|
12
|
+
private _wasOpen;
|
|
13
|
+
render(): TemplateResult;
|
|
14
|
+
protected updated(): void;
|
|
15
|
+
private _onOverlayClose;
|
|
16
|
+
private _onCloseButton;
|
|
17
|
+
private _onHeaderSlot;
|
|
18
|
+
private _onActionSlot;
|
|
19
|
+
}
|
|
20
|
+
declare global {
|
|
21
|
+
interface HTMLElementTagNameMap {
|
|
22
|
+
"xm-dialog": XmDialog;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/*
|
|
2
|
+
dialog/index.ts — <xm-dialog>, a centered modal dialog.
|
|
3
|
+
|
|
4
|
+
Composes the xm-overlay foundation (Story 1.4) for its scrim, modal stacking,
|
|
5
|
+
focus-trap, scroll-lock, and focus-restore — it does NOT hand-roll any of
|
|
6
|
+
these. The modal substrate is native <dialog>.showModal() (focus-trap +
|
|
7
|
+
background `inert` for free), surfaced through xm-overlay in the `dialog` tier
|
|
8
|
+
(the topmost tier: tooltip < menu < dialog). We drive the overlay through its
|
|
9
|
+
PUBLIC API only and never reach into its shadow root (AD-12).
|
|
10
|
+
|
|
11
|
+
Behaviour (FR-142 / FR-143):
|
|
12
|
+
• `open` is the single attribute controlling visibility — set it to show a
|
|
13
|
+
centered modal with a scrim; remove it to close
|
|
14
|
+
• Esc closes only this dialog (overlay's innermost-only handler
|
|
15
|
+
stopPropagations); closing also cascades-closes descendant popovers
|
|
16
|
+
• scrim-click closes by default, suppressed by `no-scrim-close`
|
|
17
|
+
• on close, focus restores to the trigger (delegated to the overlay)
|
|
18
|
+
|
|
19
|
+
Slots: `slot="header"` (title row → aria-labelledby), default (body), and the
|
|
20
|
+
singular `slot="action"` (trailing controls — one slot, many buttons; no
|
|
21
|
+
plural `actions`). Empty header/action rows collapse (alert's is-empty
|
|
22
|
+
pattern).
|
|
23
|
+
|
|
24
|
+
Event (AD-8): `close` — bare native-style name, bubbles + composed, structured
|
|
25
|
+
detail { reason }, fired on every close path.
|
|
26
|
+
|
|
27
|
+
Authoring:
|
|
28
|
+
<xm-dialog open>
|
|
29
|
+
<span slot="header">Delete this thread?</span>
|
|
30
|
+
This permanently removes the conversation and its artifacts.
|
|
31
|
+
<xm-button slot="action" variant="ghost">Cancel</xm-button>
|
|
32
|
+
<xm-button slot="action" variant="primary">Delete</xm-button>
|
|
33
|
+
</xm-dialog>
|
|
34
|
+
|
|
35
|
+
Properties:
|
|
36
|
+
open boolean (reflect) — the single visibility control
|
|
37
|
+
no-scrim-close boolean — a scrim click is a no-op (static backdrop)
|
|
38
|
+
static boolean — preview-only, suppress entry animation
|
|
39
|
+
label accessible name fallback when there is no header
|
|
40
|
+
|
|
41
|
+
Shadow DOM. Depends on xm-overlay + primitives (close icon) + tokens (AD-12).
|
|
42
|
+
*/
|
|
43
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
44
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
45
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
46
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
47
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
48
|
+
};
|
|
49
|
+
import { LitElement, html, svg, nothing } from "lit";
|
|
50
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
51
|
+
const DIALOG_CSS = new URL("../dialog/index.css", import.meta.url).href;
|
|
52
|
+
let dialogSeq = 0;
|
|
53
|
+
const CLOSE_ICON = () => svg `
|
|
54
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
|
|
55
|
+
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
56
|
+
stroke-linejoin="round" class="ds-icon" aria-hidden="true">
|
|
57
|
+
<path d="M6 6l12 12M18 6L6 18" />
|
|
58
|
+
</svg>
|
|
59
|
+
`;
|
|
60
|
+
let XmDialog = class XmDialog extends LitElement {
|
|
61
|
+
constructor() {
|
|
62
|
+
super(...arguments);
|
|
63
|
+
this.open = false;
|
|
64
|
+
this.noScrimClose = false;
|
|
65
|
+
this.static = false;
|
|
66
|
+
this.label = "";
|
|
67
|
+
this._hasHeader = false;
|
|
68
|
+
this._hasAction = false;
|
|
69
|
+
this._headerId = `xm-dialog-${++dialogSeq}-header`;
|
|
70
|
+
this._wasOpen = false;
|
|
71
|
+
this._onOverlayClose = (e) => {
|
|
72
|
+
const detail = e.detail;
|
|
73
|
+
// Static backdrop: a scrim click must be a no-op. The overlay already
|
|
74
|
+
// closed itself, so re-open synchronously to keep the modal up.
|
|
75
|
+
if (detail?.reason === "backdrop" && this.noScrimClose) {
|
|
76
|
+
const ov = this._overlay;
|
|
77
|
+
this._wasOpen = true;
|
|
78
|
+
this.open = true;
|
|
79
|
+
ov?.show();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this._wasOpen = false;
|
|
83
|
+
if (this.open)
|
|
84
|
+
this.open = false;
|
|
85
|
+
this.dispatchEvent(new CustomEvent("close", {
|
|
86
|
+
bubbles: true,
|
|
87
|
+
composed: true,
|
|
88
|
+
detail: { reason: detail?.reason ?? "api" },
|
|
89
|
+
}));
|
|
90
|
+
};
|
|
91
|
+
this._onCloseButton = () => {
|
|
92
|
+
const ov = this._overlay;
|
|
93
|
+
if (ov?.open)
|
|
94
|
+
ov.hide("api");
|
|
95
|
+
else {
|
|
96
|
+
this.open = false;
|
|
97
|
+
this._wasOpen = false;
|
|
98
|
+
this.dispatchEvent(new CustomEvent("close", {
|
|
99
|
+
bubbles: true,
|
|
100
|
+
composed: true,
|
|
101
|
+
detail: { reason: "api" },
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
this._onHeaderSlot = (e) => {
|
|
106
|
+
const slot = e.target;
|
|
107
|
+
this._hasHeader = slot.assignedNodes({ flatten: true }).some((n) => {
|
|
108
|
+
if (n.nodeType === Node.TEXT_NODE)
|
|
109
|
+
return !!n.textContent?.trim();
|
|
110
|
+
return n.nodeType === Node.ELEMENT_NODE;
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
this._onActionSlot = (e) => {
|
|
114
|
+
const slot = e.target;
|
|
115
|
+
this._hasAction = slot.assignedElements().length > 0;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
render() {
|
|
119
|
+
const cls = `dialog${this.static ? " dialog--static" : ""}`;
|
|
120
|
+
return html `
|
|
121
|
+
<link rel="stylesheet" href="${DIALOG_CSS}" />
|
|
122
|
+
<style>
|
|
123
|
+
:host { display: contents; }
|
|
124
|
+
</style>
|
|
125
|
+
<xm-overlay
|
|
126
|
+
mode="modal"
|
|
127
|
+
tier="dialog"
|
|
128
|
+
label=${this.label || nothing}
|
|
129
|
+
@xm-overlay-close=${this._onOverlayClose}
|
|
130
|
+
>
|
|
131
|
+
<div
|
|
132
|
+
class="${cls}"
|
|
133
|
+
role="dialog"
|
|
134
|
+
aria-modal="true"
|
|
135
|
+
aria-labelledby=${this._hasHeader ? this._headerId : nothing}
|
|
136
|
+
aria-label=${!this._hasHeader && this.label ? this.label : nothing}
|
|
137
|
+
>
|
|
138
|
+
<div class="dialog__head ${this._hasHeader ? "" : "is-empty"}">
|
|
139
|
+
<span class="dialog__header" id=${this._headerId}
|
|
140
|
+
><slot name="header" @slotchange=${this._onHeaderSlot}></slot
|
|
141
|
+
></span>
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
class="dialog__close"
|
|
145
|
+
aria-label="Close"
|
|
146
|
+
@click=${this._onCloseButton}
|
|
147
|
+
>
|
|
148
|
+
${CLOSE_ICON()}
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="dialog__body"><slot></slot></div>
|
|
152
|
+
<div class="dialog__actions ${this._hasAction ? "" : "is-empty"}">
|
|
153
|
+
<slot name="action" @slotchange=${this._onActionSlot}></slot>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</xm-overlay>
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
updated() {
|
|
160
|
+
// Drive the composed overlay from our single `open` control.
|
|
161
|
+
const ov = this._overlay;
|
|
162
|
+
if (!ov)
|
|
163
|
+
return;
|
|
164
|
+
if (this.open && !this._wasOpen) {
|
|
165
|
+
this._wasOpen = true;
|
|
166
|
+
ov.show();
|
|
167
|
+
}
|
|
168
|
+
else if (!this.open && this._wasOpen) {
|
|
169
|
+
this._wasOpen = false;
|
|
170
|
+
if (ov.open)
|
|
171
|
+
ov.hide("api");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
__decorate([
|
|
176
|
+
property({ type: Boolean, reflect: true })
|
|
177
|
+
], XmDialog.prototype, "open", void 0);
|
|
178
|
+
__decorate([
|
|
179
|
+
property({ type: Boolean, attribute: "no-scrim-close" })
|
|
180
|
+
], XmDialog.prototype, "noScrimClose", void 0);
|
|
181
|
+
__decorate([
|
|
182
|
+
property({ type: Boolean })
|
|
183
|
+
], XmDialog.prototype, "static", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
property({ type: String })
|
|
186
|
+
], XmDialog.prototype, "label", void 0);
|
|
187
|
+
__decorate([
|
|
188
|
+
state()
|
|
189
|
+
], XmDialog.prototype, "_hasHeader", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
state()
|
|
192
|
+
], XmDialog.prototype, "_hasAction", void 0);
|
|
193
|
+
__decorate([
|
|
194
|
+
query("xm-overlay")
|
|
195
|
+
], XmDialog.prototype, "_overlay", void 0);
|
|
196
|
+
XmDialog = __decorate([
|
|
197
|
+
customElement("xm-dialog")
|
|
198
|
+
], XmDialog);
|
|
199
|
+
export { XmDialog };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
<xm-divider> — standalone hairline rule.
|
|
3
|
+
|
|
4
|
+
Exactly 1px using --md-sys-color-outline-variant (the default hairline
|
|
5
|
+
weight; the same rule a card's header/footer split uses). Never 2px —
|
|
6
|
+
2px is reserved for the drag-active field state only. No thickness
|
|
7
|
+
variants, no status hue: a divider is always a neutral hairline that
|
|
8
|
+
traces its host surface and reads on both surface families (AD-13).
|
|
9
|
+
|
|
10
|
+
BEM: block `divider`; modifiers `--horizontal` / `--vertical`.
|
|
11
|
+
============================================ */
|
|
12
|
+
|
|
13
|
+
.divider {
|
|
14
|
+
background: var(--md-sys-color-outline-variant);
|
|
15
|
+
border: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.divider--horizontal {
|
|
19
|
+
width: 100%;
|
|
20
|
+
height: 1px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.divider--vertical {
|
|
24
|
+
width: 1px;
|
|
25
|
+
height: 100%;
|
|
26
|
+
min-height: 1em;
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
type DividerOrientation = "horizontal" | "vertical";
|
|
4
|
+
declare class XmDivider extends LitElement {
|
|
5
|
+
orientation: DividerOrientation;
|
|
6
|
+
render(): TemplateResult;
|
|
7
|
+
}
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
"xm-divider": XmDivider;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
divider/index.ts — <xm-divider>, the standalone hairline rule.
|
|
3
|
+
|
|
4
|
+
<xm-divider> — a 1px low-saturation separator. The standalone version of
|
|
5
|
+
the hairline rule that splits a card header from its body.
|
|
6
|
+
|
|
7
|
+
Authoring:
|
|
8
|
+
<xm-divider></xm-divider> horizontal (default)
|
|
9
|
+
<xm-divider orientation="vertical"></xm-divider>
|
|
10
|
+
|
|
11
|
+
Properties:
|
|
12
|
+
orientation "horizontal" | "vertical" (default "horizontal")
|
|
13
|
+
|
|
14
|
+
Surface / ink (AD-13): a divider is transparent except for its hairline.
|
|
15
|
+
It traces its host surface and reads as a neutral hairline against BOTH
|
|
16
|
+
surface families via the single --md-sys-color-outline-variant token,
|
|
17
|
+
which resolves per theme. Exactly 1px — 2px is a policy violation.
|
|
18
|
+
|
|
19
|
+
Shadow DOM; Lit from lit; sibling CSS resolved via
|
|
20
|
+
the built-file-relative new URL(...). BEM block `divider`.
|
|
21
|
+
*/
|
|
22
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
23
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
24
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
25
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
26
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
27
|
+
};
|
|
28
|
+
import { LitElement, html } from "lit";
|
|
29
|
+
import { customElement, property } from "lit/decorators.js";
|
|
30
|
+
// Resolve CSS relative to the *built* file:
|
|
31
|
+
// lit/build/components/divider/index.js → ../divider/index.css.
|
|
32
|
+
const DIVIDER_CSS = new URL("../divider/index.css", import.meta.url).href;
|
|
33
|
+
let XmDivider = class XmDivider extends LitElement {
|
|
34
|
+
constructor() {
|
|
35
|
+
super(...arguments);
|
|
36
|
+
this.orientation = "horizontal";
|
|
37
|
+
}
|
|
38
|
+
render() {
|
|
39
|
+
const orientation = this.orientation === "vertical" ? "vertical" : "horizontal";
|
|
40
|
+
return html `
|
|
41
|
+
<link rel="stylesheet" href="${DIVIDER_CSS}" />
|
|
42
|
+
<style>
|
|
43
|
+
:host {
|
|
44
|
+
display: block;
|
|
45
|
+
}
|
|
46
|
+
:host([orientation="vertical"]) {
|
|
47
|
+
display: inline-block;
|
|
48
|
+
align-self: stretch;
|
|
49
|
+
}
|
|
50
|
+
:host([hidden]) {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
54
|
+
<div
|
|
55
|
+
class="divider divider--${orientation}"
|
|
56
|
+
role="separator"
|
|
57
|
+
aria-orientation="${orientation}"
|
|
58
|
+
></div>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
__decorate([
|
|
63
|
+
property({ type: String, reflect: true })
|
|
64
|
+
], XmDivider.prototype, "orientation", void 0);
|
|
65
|
+
XmDivider = __decorate([
|
|
66
|
+
customElement("xm-divider")
|
|
67
|
+
], XmDivider);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Empty state — the shared no-data placeholder.
|
|
3
|
+
|
|
4
|
+
A centered, stacked column: line icon · sentence-case headline ·
|
|
5
|
+
secondary copy · optional action. The component is TRANSPARENT and
|
|
6
|
+
traces its host surface (AD-13): every ink derives from the inherited
|
|
7
|
+
`color` (the host's on-* ink), never a hard-pinned surface token — so
|
|
8
|
+
it pairs correctly whether it sits on the desk (`surface*` → on-surface)
|
|
9
|
+
or on a card (`inverse-surface` → inverse-on-surface), in both themes.
|
|
10
|
+
|
|
11
|
+
No imagery, no illustration, no emoji, no gradient — the only graphic
|
|
12
|
+
is the Lucide-style line icon (currentColor, no fill).
|
|
13
|
+
|
|
14
|
+
BEM block: `empty-state`. Registered in scripts/check-bem.sh STRICT_BLOCKS.
|
|
15
|
+
============================================ */
|
|
16
|
+
|
|
17
|
+
.empty-state {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
text-align: center;
|
|
23
|
+
gap: var(--s-2);
|
|
24
|
+
padding: var(--s-8) var(--s-6);
|
|
25
|
+
/* Trace the host: inherit the on-* ink the host already set on the
|
|
26
|
+
surface this empty-state sits on. No surface fill of its own. */
|
|
27
|
+
color: inherit;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.empty-state__icon {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
margin-bottom: var(--s-1);
|
|
35
|
+
/* Icon reads as a quiet marker — softened from the host ink via the
|
|
36
|
+
inherited currentColor, not a hard-pinned token. */
|
|
37
|
+
color: color-mix(in oklab, currentColor 64%, transparent);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.empty-state__heading {
|
|
41
|
+
font-family: var(--md-sys-typescale-title-medium-font);
|
|
42
|
+
font-size: var(--md-sys-typescale-title-medium-size);
|
|
43
|
+
line-height: var(--md-sys-typescale-title-medium-line-height);
|
|
44
|
+
font-weight: var(--md-sys-typescale-title-medium-weight);
|
|
45
|
+
letter-spacing: var(--md-sys-typescale-title-medium-tracking);
|
|
46
|
+
color: currentColor;
|
|
47
|
+
text-wrap: balance;
|
|
48
|
+
}
|
|
49
|
+
.empty-state__heading.is-empty { display: none; }
|
|
50
|
+
|
|
51
|
+
.empty-state__text {
|
|
52
|
+
max-width: 44ch;
|
|
53
|
+
font-family: var(--md-sys-typescale-body-medium-font);
|
|
54
|
+
font-size: var(--md-sys-typescale-body-medium-size);
|
|
55
|
+
line-height: var(--md-sys-typescale-body-medium-line-height);
|
|
56
|
+
/* Soft tier — derived from the host ink so it works on either surface
|
|
57
|
+
family. (on-surface-variant / inverse muted both read as ~70% ink.) */
|
|
58
|
+
color: color-mix(in oklab, currentColor 70%, transparent);
|
|
59
|
+
text-wrap: pretty;
|
|
60
|
+
}
|
|
61
|
+
.empty-state__text.is-empty { display: none; }
|
|
62
|
+
|
|
63
|
+
.empty-state__action {
|
|
64
|
+
display: inline-flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
margin-top: var(--s-3);
|
|
68
|
+
}
|
|
69
|
+
.empty-state__action.is-empty { display: none; }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
export declare class XmEmptyState extends LitElement {
|
|
4
|
+
heading: string;
|
|
5
|
+
/** A primitives icon element name (e.g. "xm-search-icon"). Used only when no
|
|
6
|
+
slot="icon" content is supplied. */
|
|
7
|
+
icon: string;
|
|
8
|
+
private _hasIconSlot;
|
|
9
|
+
private _hasText;
|
|
10
|
+
private _hasAction;
|
|
11
|
+
render(): TemplateResult;
|
|
12
|
+
private _iconEl;
|
|
13
|
+
private _onIconSlot;
|
|
14
|
+
private _onDefaultSlot;
|
|
15
|
+
private _onActionSlot;
|
|
16
|
+
}
|
|
17
|
+
declare global {
|
|
18
|
+
interface HTMLElementTagNameMap {
|
|
19
|
+
"xm-empty-state": XmEmptyState;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*
|
|
2
|
+
empty-state/index.ts — <xm-empty-state>, the shared no-data placeholder.
|
|
3
|
+
|
|
4
|
+
An empty view that reads as intentionally designed, not broken: a Lucide-style
|
|
5
|
+
line icon (from `primitives`, via slot or `icon` attribute), a sentence-case
|
|
6
|
+
headline, secondary copy (default slot), and an optional singular `slot="action"`
|
|
7
|
+
CTA. There is NO imagery / illustration / emoji — the only graphic is the line
|
|
8
|
+
icon, inked via currentColor from the host surface's on-* ink (AD-13).
|
|
9
|
+
|
|
10
|
+
This is the placeholder reused by xm-autocomplete (no-match, FR-120) and
|
|
11
|
+
xm-data-table (empty rows, FR-161); its API composes into both.
|
|
12
|
+
|
|
13
|
+
Authoring:
|
|
14
|
+
<xm-empty-state heading="No results found">
|
|
15
|
+
<xm-search-icon slot="icon" size="22"></xm-search-icon>
|
|
16
|
+
Try a different search term or clear the filters.
|
|
17
|
+
<xm-button slot="action" variant="outline" size="sm">Clear filters</xm-button>
|
|
18
|
+
</xm-empty-state>
|
|
19
|
+
|
|
20
|
+
Properties:
|
|
21
|
+
heading short title string (sentence case)
|
|
22
|
+
icon optional primitives icon NAME (e.g. "xm-search-icon"); used only
|
|
23
|
+
when no slot="icon" content is provided
|
|
24
|
+
|
|
25
|
+
Slots:
|
|
26
|
+
icon a primitives line-icon (overrides the `icon` attribute)
|
|
27
|
+
default secondary copy
|
|
28
|
+
action single trailing CTA (collapses when empty, mirroring xm-alert)
|
|
29
|
+
|
|
30
|
+
Shadow DOM. Loads its sibling CSS via the built-file-relative new URL(...)
|
|
31
|
+
pattern. Depends only on `primitives` + tokens — no overlay (AD-12).
|
|
32
|
+
*/
|
|
33
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
34
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
35
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
36
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
37
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
38
|
+
};
|
|
39
|
+
import { LitElement, html, nothing } from "lit";
|
|
40
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
41
|
+
const EMPTY_STATE_CSS = new URL("../empty-state/index.css", import.meta.url).href;
|
|
42
|
+
let XmEmptyState = class XmEmptyState extends LitElement {
|
|
43
|
+
constructor() {
|
|
44
|
+
super(...arguments);
|
|
45
|
+
this.heading = "";
|
|
46
|
+
/** A primitives icon element name (e.g. "xm-search-icon"). Used only when no
|
|
47
|
+
slot="icon" content is supplied. */
|
|
48
|
+
this.icon = "";
|
|
49
|
+
this._hasIconSlot = false;
|
|
50
|
+
this._hasText = false;
|
|
51
|
+
this._hasAction = false;
|
|
52
|
+
this._onIconSlot = (e) => {
|
|
53
|
+
const slot = e.target;
|
|
54
|
+
this._hasIconSlot = slot.assignedElements().length > 0;
|
|
55
|
+
};
|
|
56
|
+
this._onDefaultSlot = (e) => {
|
|
57
|
+
const slot = e.target;
|
|
58
|
+
const nodes = slot.assignedNodes({ flatten: true });
|
|
59
|
+
this._hasText = nodes.some((n) => {
|
|
60
|
+
if (n.nodeType === Node.TEXT_NODE)
|
|
61
|
+
return !!n.textContent?.trim();
|
|
62
|
+
if (n.nodeType === Node.ELEMENT_NODE)
|
|
63
|
+
return true;
|
|
64
|
+
return false;
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
this._onActionSlot = (e) => {
|
|
68
|
+
const slot = e.target;
|
|
69
|
+
this._hasAction = slot.assignedElements().length > 0;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
render() {
|
|
73
|
+
const showFallbackIcon = !this._hasIconSlot && this.icon !== "";
|
|
74
|
+
return html `
|
|
75
|
+
<link rel="stylesheet" href="${EMPTY_STATE_CSS}" />
|
|
76
|
+
<style>
|
|
77
|
+
:host { display: block; }
|
|
78
|
+
:host([hidden]) { display: none; }
|
|
79
|
+
</style>
|
|
80
|
+
<div class="empty-state" role="status">
|
|
81
|
+
<span class="empty-state__icon" aria-hidden="true">
|
|
82
|
+
${showFallbackIcon ? this._iconEl() : nothing}
|
|
83
|
+
<slot name="icon" @slotchange=${this._onIconSlot}></slot>
|
|
84
|
+
</span>
|
|
85
|
+
<span class="empty-state__heading ${this.heading ? "" : "is-empty"}"
|
|
86
|
+
>${this.heading}</span
|
|
87
|
+
>
|
|
88
|
+
<span class="empty-state__text ${this._hasText ? "" : "is-empty"}"
|
|
89
|
+
><slot @slotchange=${this._onDefaultSlot}></slot
|
|
90
|
+
></span>
|
|
91
|
+
<span class="empty-state__action ${this._hasAction ? "" : "is-empty"}"
|
|
92
|
+
><slot name="action" @slotchange=${this._onActionSlot}></slot
|
|
93
|
+
></span>
|
|
94
|
+
</div>
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
// Render the named primitives icon by tag at 22px. The element inks via
|
|
98
|
+
// currentColor from .empty-state__icon, so no color is hard-pinned.
|
|
99
|
+
_iconEl() {
|
|
100
|
+
const el = document.createElement(this.icon);
|
|
101
|
+
el.setAttribute("size", "22");
|
|
102
|
+
return html `${el}`;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
__decorate([
|
|
106
|
+
property({ type: String })
|
|
107
|
+
], XmEmptyState.prototype, "heading", void 0);
|
|
108
|
+
__decorate([
|
|
109
|
+
property({ type: String })
|
|
110
|
+
], XmEmptyState.prototype, "icon", void 0);
|
|
111
|
+
__decorate([
|
|
112
|
+
state()
|
|
113
|
+
], XmEmptyState.prototype, "_hasIconSlot", void 0);
|
|
114
|
+
__decorate([
|
|
115
|
+
state()
|
|
116
|
+
], XmEmptyState.prototype, "_hasText", void 0);
|
|
117
|
+
__decorate([
|
|
118
|
+
state()
|
|
119
|
+
], XmEmptyState.prototype, "_hasAction", void 0);
|
|
120
|
+
XmEmptyState = __decorate([
|
|
121
|
+
customElement("xm-empty-state")
|
|
122
|
+
], XmEmptyState);
|
|
123
|
+
export { XmEmptyState };
|