@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,60 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Avatar group — overlapped stack with +N overflow.
|
|
3
|
+
|
|
4
|
+
<xm-avatar-group> lays slotted <xm-avatar> children in an
|
|
5
|
+
overlapped row (consistent negative offset) and, past `max`,
|
|
6
|
+
renders a neutral "+N" overflow token in the same circular
|
|
7
|
+
avatar shape. The overflow ink is the plain on-surface neutral
|
|
8
|
+
— never coral, never a status hue (AD-11). Each avatar keeps a
|
|
9
|
+
thin ring so the stack reads as discrete chips, not a blob.
|
|
10
|
+
============================================ */
|
|
11
|
+
|
|
12
|
+
.avatar-group {
|
|
13
|
+
--avatar-group-overlap: 8px;
|
|
14
|
+
--avatar-group-size: 32px;
|
|
15
|
+
--avatar-group-font: var(--md-sys-typescale-label-large-size);
|
|
16
|
+
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* The ring + overlap on each stacked avatar. ::slotted reaches the
|
|
22
|
+
light-DOM <xm-avatar> children; the ring uses the host surface so
|
|
23
|
+
the chips separate cleanly on whatever desk tier they sit on. */
|
|
24
|
+
.avatar-group ::slotted(xm-avatar) {
|
|
25
|
+
margin-left: calc(var(--avatar-group-overlap) * -1);
|
|
26
|
+
border-radius: var(--md-sys-shape-corner-full);
|
|
27
|
+
box-shadow: 0 0 0 2px var(--md-sys-color-surface);
|
|
28
|
+
}
|
|
29
|
+
.avatar-group ::slotted(xm-avatar:first-child) {
|
|
30
|
+
margin-left: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* +N overflow token — same circle, neutral surface ink. */
|
|
34
|
+
.avatar-group__overflow {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
width: var(--avatar-group-size);
|
|
40
|
+
height: var(--avatar-group-size);
|
|
41
|
+
margin-left: calc(var(--avatar-group-overlap) * -1);
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
border-radius: var(--md-sys-shape-corner-full);
|
|
44
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
45
|
+
box-shadow: 0 0 0 2px var(--md-sys-color-surface);
|
|
46
|
+
background: var(--md-sys-color-surface-container-highest);
|
|
47
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
48
|
+
font-family: var(--md-sys-typescale-label-large-font);
|
|
49
|
+
font-size: var(--avatar-group-font);
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
line-height: 1;
|
|
52
|
+
letter-spacing: 0;
|
|
53
|
+
white-space: nowrap;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ---------- Sizes — match xm-avatar diameters + scale overlap ---------- */
|
|
57
|
+
.avatar-group--xs { --avatar-group-size: 22px; --avatar-group-font: var(--md-sys-typescale-label-small-size); --avatar-group-overlap: 6px; }
|
|
58
|
+
.avatar-group--sm { --avatar-group-size: 28px; --avatar-group-font: var(--md-sys-typescale-label-medium-size); --avatar-group-overlap: 7px; }
|
|
59
|
+
.avatar-group--md { --avatar-group-size: 32px; --avatar-group-font: var(--md-sys-typescale-label-large-size); --avatar-group-overlap: 8px; }
|
|
60
|
+
.avatar-group--lg { --avatar-group-size: 40px; --avatar-group-font: var(--md-sys-typescale-title-small-size); --avatar-group-overlap: 10px; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
type AvatarSize = "xs" | "sm" | "md" | "lg";
|
|
4
|
+
declare class XmAvatarGroup extends LitElement {
|
|
5
|
+
max: number;
|
|
6
|
+
size: AvatarSize;
|
|
7
|
+
private _overflow;
|
|
8
|
+
updated(): void;
|
|
9
|
+
private _avatars;
|
|
10
|
+
private _applyChildren;
|
|
11
|
+
private _onSlot;
|
|
12
|
+
render(): TemplateResult;
|
|
13
|
+
}
|
|
14
|
+
declare global {
|
|
15
|
+
interface HTMLElementTagNameMap {
|
|
16
|
+
"xm-avatar-group": XmAvatarGroup;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
avatar-group/index.ts — <xm-avatar-group>.
|
|
3
|
+
|
|
4
|
+
Overlaps slotted <xm-avatar> children into a stack and, when the
|
|
5
|
+
count exceeds `max`, renders a neutral "+N" overflow token in the
|
|
6
|
+
same circular shape. Reads slotted children only — never their
|
|
7
|
+
shadow roots (AD-12).
|
|
8
|
+
|
|
9
|
+
Authoring:
|
|
10
|
+
<xm-avatar-group max="3" size="md">
|
|
11
|
+
<xm-avatar name="Ada Lovelace"></xm-avatar>
|
|
12
|
+
<xm-avatar name="Grace Hopper"></xm-avatar>
|
|
13
|
+
<xm-avatar name="Alan Turing"></xm-avatar>
|
|
14
|
+
<xm-avatar name="Edsger Dijkstra"></xm-avatar>
|
|
15
|
+
<xm-avatar name="Barbara Liskov"></xm-avatar>
|
|
16
|
+
</xm-avatar-group>
|
|
17
|
+
|
|
18
|
+
Properties:
|
|
19
|
+
max number — visible avatars before collapsing to "+N" (default 4)
|
|
20
|
+
size xs|sm|md|lg — propagated to children + overflow (default md)
|
|
21
|
+
|
|
22
|
+
The "+N" overflow uses neutral surface ink, never coral / status hue
|
|
23
|
+
(AD-11). Shadow DOM; Lit is a bare `import` (peer dep).
|
|
24
|
+
*/
|
|
25
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
26
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
27
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
28
|
+
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;
|
|
29
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
30
|
+
};
|
|
31
|
+
import { LitElement, html, nothing } from "lit";
|
|
32
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
33
|
+
const GROUP_CSS = new URL("../avatar-group/index.css", import.meta.url).href;
|
|
34
|
+
let XmAvatarGroup = class XmAvatarGroup extends LitElement {
|
|
35
|
+
constructor() {
|
|
36
|
+
super(...arguments);
|
|
37
|
+
this.max = 4;
|
|
38
|
+
this.size = "md";
|
|
39
|
+
this._overflow = 0;
|
|
40
|
+
this._onSlot = () => {
|
|
41
|
+
this._applyChildren();
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
updated() {
|
|
45
|
+
this._applyChildren();
|
|
46
|
+
}
|
|
47
|
+
_avatars() {
|
|
48
|
+
return [...this.children].filter((c) => c.tagName.toLowerCase() === "xm-avatar");
|
|
49
|
+
}
|
|
50
|
+
_applyChildren() {
|
|
51
|
+
const avatars = this._avatars();
|
|
52
|
+
const overflow = Math.max(0, avatars.length - this.max);
|
|
53
|
+
avatars.forEach((el, i) => {
|
|
54
|
+
el.setAttribute("size", this.size);
|
|
55
|
+
el.hidden = i >= this.max;
|
|
56
|
+
});
|
|
57
|
+
if (this._overflow !== overflow)
|
|
58
|
+
this._overflow = overflow;
|
|
59
|
+
}
|
|
60
|
+
render() {
|
|
61
|
+
const cls = `avatar-group avatar-group--${this.size}`;
|
|
62
|
+
return html `
|
|
63
|
+
<link rel="stylesheet" href="${GROUP_CSS}" />
|
|
64
|
+
<style>
|
|
65
|
+
:host {
|
|
66
|
+
display: inline-flex;
|
|
67
|
+
}
|
|
68
|
+
:host([hidden]) {
|
|
69
|
+
display: none;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
72
|
+
<div class="${cls}">
|
|
73
|
+
<slot @slotchange=${this._onSlot}></slot>
|
|
74
|
+
${this._overflow > 0
|
|
75
|
+
? html `<span
|
|
76
|
+
class="avatar-group__overflow"
|
|
77
|
+
role="img"
|
|
78
|
+
aria-label="${this._overflow} more"
|
|
79
|
+
>+${this._overflow}</span
|
|
80
|
+
>`
|
|
81
|
+
: nothing}
|
|
82
|
+
</div>
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
__decorate([
|
|
87
|
+
property({ type: Number })
|
|
88
|
+
], XmAvatarGroup.prototype, "max", void 0);
|
|
89
|
+
__decorate([
|
|
90
|
+
property({ type: String })
|
|
91
|
+
], XmAvatarGroup.prototype, "size", void 0);
|
|
92
|
+
__decorate([
|
|
93
|
+
state()
|
|
94
|
+
], XmAvatarGroup.prototype, "_overflow", void 0);
|
|
95
|
+
XmAvatarGroup = __decorate([
|
|
96
|
+
customElement("xm-avatar-group")
|
|
97
|
+
], XmAvatarGroup);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Badge — small count / dot anchored to a child.
|
|
3
|
+
|
|
4
|
+
<xm-badge> floats a coral count or a minimal dot at the
|
|
5
|
+
top-right corner of whatever it wraps (an icon button, an
|
|
6
|
+
avatar, a label). The coral fill (--md-sys-color-primary /
|
|
7
|
+
--md-sys-color-on-primary) means "count / attention" — the
|
|
8
|
+
same functional coral as a selected pill or the active-tab
|
|
9
|
+
indicator. It is NEVER a severity palette and never references
|
|
10
|
+
--md-sys-color-error*. Severity is communicated by icon + copy
|
|
11
|
+
elsewhere, never by this badge's hue.
|
|
12
|
+
|
|
13
|
+
The badge paints its own coral surface, so it pairs primary
|
|
14
|
+
with on-primary independent of the host's surface family
|
|
15
|
+
(AD-13) — it stays legible floating over any surface.
|
|
16
|
+
============================================ */
|
|
17
|
+
|
|
18
|
+
.badge {
|
|
19
|
+
position: relative;
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* The wrapper the badged child projects into. Establishes the
|
|
24
|
+
positioning context for the absolutely-placed count/dot. */
|
|
25
|
+
.badge__anchor {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Shared corner-floated chip — both count and dot inherit this. */
|
|
30
|
+
.badge__count {
|
|
31
|
+
position: absolute;
|
|
32
|
+
top: 0;
|
|
33
|
+
right: 0;
|
|
34
|
+
transform: translate(35%, -35%);
|
|
35
|
+
z-index: 1;
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
min-width: 18px;
|
|
41
|
+
height: 18px;
|
|
42
|
+
padding: 0 var(--s-1);
|
|
43
|
+
border-radius: var(--md-sys-shape-corner-full);
|
|
44
|
+
/* Hairline halo so the chip separates from a same-hued host
|
|
45
|
+
(e.g. a coral primary button) without a hard border. */
|
|
46
|
+
border: 1px solid var(--md-sys-color-surface);
|
|
47
|
+
background: var(--xm-color-primary-fill);
|
|
48
|
+
color: var(--md-sys-color-on-primary);
|
|
49
|
+
font-family: var(--md-sys-typescale-label-small-font);
|
|
50
|
+
font-size: var(--md-sys-typescale-label-small-size);
|
|
51
|
+
font-weight: var(--md-sys-typescale-label-small-weight);
|
|
52
|
+
line-height: 1;
|
|
53
|
+
letter-spacing: var(--md-sys-typescale-label-small-tracking);
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Dot — no count, just a presence marker. Collapses to a small
|
|
58
|
+
circle in the same corner. */
|
|
59
|
+
.badge--dot .badge__count {
|
|
60
|
+
min-width: 0;
|
|
61
|
+
width: 10px;
|
|
62
|
+
height: 10px;
|
|
63
|
+
padding: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Standalone badge (no anchored child) — drops the absolute
|
|
67
|
+
positioning so the chip flows inline beside text. */
|
|
68
|
+
.badge--standalone .badge__count {
|
|
69
|
+
position: static;
|
|
70
|
+
transform: none;
|
|
71
|
+
border: none;
|
|
72
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
declare class XmBadge extends LitElement {
|
|
4
|
+
count: number | null;
|
|
5
|
+
dot: boolean;
|
|
6
|
+
max: number;
|
|
7
|
+
label: string;
|
|
8
|
+
private get _hasCount();
|
|
9
|
+
private get _display();
|
|
10
|
+
private get _ariaLabel();
|
|
11
|
+
render(): TemplateResult;
|
|
12
|
+
}
|
|
13
|
+
declare global {
|
|
14
|
+
interface HTMLElementTagNameMap {
|
|
15
|
+
"xm-badge": XmBadge;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
badge/index.ts — <xm-badge>.
|
|
3
|
+
|
|
4
|
+
A small count or dot anchored to a slotted child. The default slot
|
|
5
|
+
holds the element being badged (an icon button, an avatar, a chip);
|
|
6
|
+
the badge floats a coral count/dot at the top-right corner.
|
|
7
|
+
|
|
8
|
+
Authoring:
|
|
9
|
+
<xm-badge count="3">
|
|
10
|
+
<xm-button icon-only variant="ghost"><svg slot="icon-left">…</svg></xm-button>
|
|
11
|
+
</xm-badge>
|
|
12
|
+
|
|
13
|
+
<xm-badge dot label="Unread messages">
|
|
14
|
+
<span>Inbox</span>
|
|
15
|
+
</xm-badge>
|
|
16
|
+
|
|
17
|
+
Properties:
|
|
18
|
+
count number — numeric badge; capped at `max` then shown as "max+"
|
|
19
|
+
dot boolean — minimal presence dot (used when there's no count)
|
|
20
|
+
max number — overflow cap for the count (default 99 → "99+")
|
|
21
|
+
label string — accessible label for the badge (defaults to the
|
|
22
|
+
count, e.g. "3"); required when only a dot shows so
|
|
23
|
+
the meaning isn't carried by color alone (NFR-15)
|
|
24
|
+
|
|
25
|
+
Coral, never severity (AD-11): the count/dot uses --md-sys-color-primary;
|
|
26
|
+
it must NOT signal error severity by hue and must NOT reference
|
|
27
|
+
--md-sys-color-error*.
|
|
28
|
+
|
|
29
|
+
Shadow DOM (for <slot> projection); Lit is a bare `import` (peer dep); CSS via <link>
|
|
30
|
+
resolved relative to the built module (mirrors button/index.ts).
|
|
31
|
+
*/
|
|
32
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
33
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
34
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
35
|
+
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;
|
|
36
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
37
|
+
};
|
|
38
|
+
import { LitElement, html, nothing } from "lit";
|
|
39
|
+
import { customElement, property } from "lit/decorators.js";
|
|
40
|
+
const BADGE_CSS = new URL("../badge/index.css", import.meta.url).href;
|
|
41
|
+
let XmBadge = class XmBadge extends LitElement {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
this.count = null;
|
|
45
|
+
this.dot = false;
|
|
46
|
+
this.max = 99;
|
|
47
|
+
this.label = "";
|
|
48
|
+
}
|
|
49
|
+
get _hasCount() {
|
|
50
|
+
return typeof this.count === "number" && this.count > 0;
|
|
51
|
+
}
|
|
52
|
+
get _display() {
|
|
53
|
+
if (!this._hasCount)
|
|
54
|
+
return "";
|
|
55
|
+
const n = this.count;
|
|
56
|
+
return n > this.max ? `${this.max}+` : String(n);
|
|
57
|
+
}
|
|
58
|
+
get _ariaLabel() {
|
|
59
|
+
if (this.label)
|
|
60
|
+
return this.label;
|
|
61
|
+
if (this._hasCount)
|
|
62
|
+
return String(this.count);
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
render() {
|
|
66
|
+
const isDot = this.dot && !this._hasCount;
|
|
67
|
+
const isStandalone = this.childNodes.length === 0 ||
|
|
68
|
+
[...this.childNodes].every((n) => n.nodeType === Node.TEXT_NODE && !(n.textContent ?? "").trim());
|
|
69
|
+
const cls = [
|
|
70
|
+
"badge",
|
|
71
|
+
isDot && "badge--dot",
|
|
72
|
+
isStandalone && "badge--standalone",
|
|
73
|
+
]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join(" ");
|
|
76
|
+
const marker = isDot || this._hasCount
|
|
77
|
+
? html `<span
|
|
78
|
+
class="badge__count"
|
|
79
|
+
role="status"
|
|
80
|
+
aria-label=${this._ariaLabel || nothing}
|
|
81
|
+
>${this._display}</span
|
|
82
|
+
>`
|
|
83
|
+
: nothing;
|
|
84
|
+
return html `
|
|
85
|
+
<link rel="stylesheet" href="${BADGE_CSS}" />
|
|
86
|
+
<style>
|
|
87
|
+
:host {
|
|
88
|
+
display: inline-flex;
|
|
89
|
+
}
|
|
90
|
+
:host([hidden]) {
|
|
91
|
+
display: none;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
94
|
+
<span class="${cls}">
|
|
95
|
+
<span class="badge__anchor"><slot></slot></span>
|
|
96
|
+
${marker}
|
|
97
|
+
</span>
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
__decorate([
|
|
102
|
+
property({ type: Number })
|
|
103
|
+
], XmBadge.prototype, "count", void 0);
|
|
104
|
+
__decorate([
|
|
105
|
+
property({ type: Boolean })
|
|
106
|
+
], XmBadge.prototype, "dot", void 0);
|
|
107
|
+
__decorate([
|
|
108
|
+
property({ type: Number })
|
|
109
|
+
], XmBadge.prototype, "max", void 0);
|
|
110
|
+
__decorate([
|
|
111
|
+
property({ type: String })
|
|
112
|
+
], XmBadge.prototype, "label", void 0);
|
|
113
|
+
XmBadge = __decorate([
|
|
114
|
+
customElement("xm-badge")
|
|
115
|
+
], XmBadge);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
<BrandMark /> — xmesh logo lockup.
|
|
3
|
+
|
|
4
|
+
Owns the canonical brand glyph (three nodes routing to a single
|
|
5
|
+
hub) and the wordmark. Three sizes:
|
|
6
|
+
sm — 28px chip · sidebar header
|
|
7
|
+
md — 40px chip · generic placement
|
|
8
|
+
lg — 96px chip · brand showcase / about pages
|
|
9
|
+
|
|
10
|
+
Pairs with components/brand-mark/index.jsx. The glyph is a single SVG
|
|
11
|
+
that renders at any size — only the chip dimensions change.
|
|
12
|
+
============================================================ */
|
|
13
|
+
|
|
14
|
+
.brand-mark {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: var(--s-2-5);
|
|
18
|
+
min-width: 0;
|
|
19
|
+
color: var(--md-sys-color-on-surface);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.brand-mark__chip {
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
border-radius: 7px;
|
|
25
|
+
background: transparent;
|
|
26
|
+
color: var(--md-sys-color-on-surface);
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.brand-mark__info {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: var(--s-1);
|
|
36
|
+
min-width: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.brand-mark__wordmark {
|
|
40
|
+
font: 500 15px/1 var(--md-sys-typescale-display-large-font);
|
|
41
|
+
letter-spacing: -0.01em;
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
color: var(--md-sys-color-on-surface);
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
text-overflow: ellipsis;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.brand-mark__tagline {
|
|
50
|
+
font: 400 13px/1.5 var(--md-sys-typescale-body-large-font);
|
|
51
|
+
color: var(--xm-color-on-surface-soft);
|
|
52
|
+
max-width: 360px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ---------- Sizes ---------- */
|
|
56
|
+
.brand-mark--sm {
|
|
57
|
+
gap: var(--s-1);
|
|
58
|
+
}
|
|
59
|
+
.brand-mark--sm .brand-mark__chip {
|
|
60
|
+
width: 24px; height: 24px;
|
|
61
|
+
border-radius: 6px;
|
|
62
|
+
}
|
|
63
|
+
.brand-mark--sm .brand-mark__chip svg { width: 20px; height: 20px; }
|
|
64
|
+
|
|
65
|
+
.brand-mark--md {
|
|
66
|
+
gap: var(--s-3-5);
|
|
67
|
+
}
|
|
68
|
+
.brand-mark--md .brand-mark__chip {
|
|
69
|
+
width: 40px; height: 40px;
|
|
70
|
+
border-radius: 10px;
|
|
71
|
+
}
|
|
72
|
+
.brand-mark--md .brand-mark__chip svg { width: 24px; height: 24px; }
|
|
73
|
+
.brand-mark--md .brand-mark__wordmark { font-size: 20px; }
|
|
74
|
+
|
|
75
|
+
.brand-mark--lg {
|
|
76
|
+
gap: var(--s-8);
|
|
77
|
+
}
|
|
78
|
+
.brand-mark--lg .brand-mark__chip {
|
|
79
|
+
width: 96px; height: 96px;
|
|
80
|
+
border-radius: 22px;
|
|
81
|
+
}
|
|
82
|
+
.brand-mark--lg .brand-mark__chip svg { width: 60px; height: 60px; }
|
|
83
|
+
.brand-mark--lg .brand-mark__wordmark {
|
|
84
|
+
font: 600 34px/1 var(--md-sys-typescale-body-large-font);
|
|
85
|
+
letter-spacing: -0.02em;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ---------- Wordmark-only (no glyph) ----------
|
|
89
|
+
The serif-driven brand wordmark used in the chat sidebar header
|
|
90
|
+
and editorial brand placements. Display family + medium weight.
|
|
91
|
+
Sizes scale with the wrapper modifier (--sm | --md | --lg). */
|
|
92
|
+
.brand-mark--wordmark-only { gap: 0; }
|
|
93
|
+
.brand-mark--wordmark-only .brand-mark__wordmark {
|
|
94
|
+
font:
|
|
95
|
+
var(--xm-typescale-wordmark-weight)
|
|
96
|
+
var(--xm-typescale-wordmark-size) /
|
|
97
|
+
var(--xm-typescale-wordmark-line-height)
|
|
98
|
+
var(--xm-typescale-wordmark-font);
|
|
99
|
+
letter-spacing: var(--xm-typescale-wordmark-tracking);
|
|
100
|
+
text-transform: none;
|
|
101
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
102
|
+
}
|
|
103
|
+
.brand-mark--wordmark-only.brand-mark--sm .brand-mark__wordmark { font-size: 16px; }
|
|
104
|
+
.brand-mark--wordmark-only.brand-mark--md .brand-mark__wordmark { font-size: 18px; }
|
|
105
|
+
.brand-mark--wordmark-only.brand-mark--lg .brand-mark__wordmark {
|
|
106
|
+
font-size: 28px;
|
|
107
|
+
font-weight: 400;
|
|
108
|
+
letter-spacing: -0.02em;
|
|
109
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
type BrandSize = "sm" | "md" | "lg";
|
|
4
|
+
declare class LightElement extends LitElement {
|
|
5
|
+
createRenderRoot(): HTMLElement | DocumentFragment;
|
|
6
|
+
}
|
|
7
|
+
declare class XmBrandGlyph extends LightElement {
|
|
8
|
+
render(): TemplateResult;
|
|
9
|
+
}
|
|
10
|
+
declare class XmBrandMark extends LightElement {
|
|
11
|
+
size: BrandSize;
|
|
12
|
+
showGlyph: boolean;
|
|
13
|
+
showWordmark: boolean;
|
|
14
|
+
showTagline: boolean;
|
|
15
|
+
tagline: string;
|
|
16
|
+
render(): TemplateResult;
|
|
17
|
+
}
|
|
18
|
+
declare global {
|
|
19
|
+
interface HTMLElementTagNameMap {
|
|
20
|
+
"xm-brand-glyph": XmBrandGlyph;
|
|
21
|
+
"xm-brand-mark": XmBrandMark;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*
|
|
2
|
+
brand-mark/index.ts — Lit port of components/brand-mark/index.jsx.
|
|
3
|
+
|
|
4
|
+
<xm-brand-mark> — xmesh logo lockup.
|
|
5
|
+
<xm-brand-glyph> — just the SVG glyph (three routed paths meeting at a hub).
|
|
6
|
+
|
|
7
|
+
Properties on <xm-brand-mark>:
|
|
8
|
+
size "sm" | "md" | "lg" (default "md")
|
|
9
|
+
showGlyph boolean (default true)
|
|
10
|
+
showWordmark boolean (default true)
|
|
11
|
+
showTagline boolean (default false)
|
|
12
|
+
tagline string (default — see DEFAULT_TAGLINE)
|
|
13
|
+
|
|
14
|
+
Light DOM. Existing components/brand-mark/index.css owns all visuals.
|
|
15
|
+
The glyph SVG references --c-card-soft for the outer node fill, which
|
|
16
|
+
resolves at the host (light DOM) so theme switching works.
|
|
17
|
+
*/
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
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;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
import { LitElement, html, nothing } from "lit";
|
|
25
|
+
import { customElement, property } from "lit/decorators.js";
|
|
26
|
+
const DEFAULT_TAGLINE = "Smart-routing AI chat. The mark is an X-mesh — four nodes routing to a single hub through the letterform itself.";
|
|
27
|
+
class LightElement extends LitElement {
|
|
28
|
+
createRenderRoot() {
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let XmBrandGlyph = class XmBrandGlyph extends LightElement {
|
|
33
|
+
render() {
|
|
34
|
+
// X-mesh: two crossing strokes meet at a central hub, four outer
|
|
35
|
+
// nodes at the X endpoints. Reads as the letter X at a glance and
|
|
36
|
+
// a routed mesh on inspection — couples the brand name to the
|
|
37
|
+
// smart-routing metaphor in a single symmetric mark.
|
|
38
|
+
return html `
|
|
39
|
+
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
40
|
+
<g
|
|
41
|
+
fill="none"
|
|
42
|
+
stroke="currentColor"
|
|
43
|
+
stroke-width="1.7"
|
|
44
|
+
stroke-linecap="round"
|
|
45
|
+
stroke-linejoin="round"
|
|
46
|
+
>
|
|
47
|
+
<path d="M8 8 L16 16 L24 24" />
|
|
48
|
+
<path d="M24 8 L16 16 L8 24" />
|
|
49
|
+
<circle cx="8" cy="8" r="2.2" fill="transparent" />
|
|
50
|
+
<circle cx="24" cy="8" r="2.2" fill="transparent" />
|
|
51
|
+
<circle cx="8" cy="24" r="2.2" fill="transparent" />
|
|
52
|
+
<circle cx="24" cy="24" r="2.2" fill="transparent" />
|
|
53
|
+
<circle cx="16" cy="16" r="2.4" fill="currentColor" stroke="none" />
|
|
54
|
+
</g>
|
|
55
|
+
</svg>
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
XmBrandGlyph = __decorate([
|
|
60
|
+
customElement("xm-brand-glyph")
|
|
61
|
+
], XmBrandGlyph);
|
|
62
|
+
let XmBrandMark = class XmBrandMark extends LightElement {
|
|
63
|
+
constructor() {
|
|
64
|
+
super(...arguments);
|
|
65
|
+
this.size = "md";
|
|
66
|
+
this.showGlyph = true;
|
|
67
|
+
this.showWordmark = true;
|
|
68
|
+
this.showTagline = false;
|
|
69
|
+
this.tagline = DEFAULT_TAGLINE;
|
|
70
|
+
}
|
|
71
|
+
render() {
|
|
72
|
+
const wordmarkOnly = !this.showGlyph && this.showWordmark;
|
|
73
|
+
const cls = [
|
|
74
|
+
"brand-mark",
|
|
75
|
+
`brand-mark--${this.size}`,
|
|
76
|
+
wordmarkOnly && "brand-mark--wordmark-only",
|
|
77
|
+
].filter(Boolean).join(" ");
|
|
78
|
+
return html `
|
|
79
|
+
<span class="${cls}" title="xmesh">
|
|
80
|
+
${this.showGlyph
|
|
81
|
+
? html `<span class="brand-mark__chip"><xm-brand-glyph></xm-brand-glyph></span>`
|
|
82
|
+
: nothing}
|
|
83
|
+
${(this.showWordmark || this.showTagline)
|
|
84
|
+
? html `
|
|
85
|
+
<span class="brand-mark__info">
|
|
86
|
+
${this.showWordmark
|
|
87
|
+
? html `<span class="brand-mark__wordmark">mesh</span>`
|
|
88
|
+
: nothing}
|
|
89
|
+
${this.showTagline
|
|
90
|
+
? html `<span class="brand-mark__tagline">${this.tagline}</span>`
|
|
91
|
+
: nothing}
|
|
92
|
+
</span>
|
|
93
|
+
`
|
|
94
|
+
: nothing}
|
|
95
|
+
</span>
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
__decorate([
|
|
100
|
+
property({ type: String })
|
|
101
|
+
], XmBrandMark.prototype, "size", void 0);
|
|
102
|
+
__decorate([
|
|
103
|
+
property({ type: Boolean, attribute: "show-glyph" })
|
|
104
|
+
], XmBrandMark.prototype, "showGlyph", void 0);
|
|
105
|
+
__decorate([
|
|
106
|
+
property({ type: Boolean, attribute: "show-wordmark" })
|
|
107
|
+
], XmBrandMark.prototype, "showWordmark", void 0);
|
|
108
|
+
__decorate([
|
|
109
|
+
property({ type: Boolean, attribute: "show-tagline" })
|
|
110
|
+
], XmBrandMark.prototype, "showTagline", void 0);
|
|
111
|
+
__decorate([
|
|
112
|
+
property({ type: String })
|
|
113
|
+
], XmBrandMark.prototype, "tagline", void 0);
|
|
114
|
+
XmBrandMark = __decorate([
|
|
115
|
+
customElement("xm-brand-mark")
|
|
116
|
+
], XmBrandMark);
|