@keyfunc/keybricks-ds 2.4.7 → 2.4.10
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/DEPLOY.md +9 -6
- package/DESIGN.md +4 -4
- package/INTEGRATION.md +27 -2
- package/_adherence.oxlintrc.json +4 -4
- package/assets/fonts/inter/InterVariable-Italic.woff2 +0 -0
- package/assets/fonts/inter/InterVariable.woff2 +0 -0
- package/assets/fonts/inter/LICENSE.txt +92 -0
- package/assets/fonts/inter/README.md +13 -0
- package/assets/icons/icon-index.js +1 -1
- package/assets/icons/icon-index.json +1 -1
- package/changelog.js +91 -0
- package/components/_cards/segmented-control.card.html +1 -1
- package/components/_cards/table.card.html +139 -6
- package/components/_cards/toggles.card.html +27 -27
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-display.js +1 -1
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-icons.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_shared/src-tabs.js +1 -1
- package/components/data/SmartTable.d.ts +9 -1
- package/components/data/SmartTable.jsx +24 -4
- package/components/data/SmartTable.vue +18 -5
- package/components/data/Spreadsheet.d.ts +5 -1
- package/components/data/Spreadsheet.jsx +23 -3
- package/components/data/Spreadsheet.vue +18 -5
- package/components/data/Table.d.ts +25 -4
- package/components/data/Table.jsx +74 -20
- package/components/data/Table.vue +56 -14
- package/components/display/Tag.d.ts +1 -1
- package/components/display/Tag.jsx +1 -1
- package/components/display/Tag.vue +1 -1
- package/components/forms/Switch.d.ts +2 -2
- package/components/forms/Switch.jsx +10 -8
- package/components/forms/Switch.vue +5 -4
- package/components/forms/forms.prompt.md +1 -1
- package/components/icons/Icon.jsx +2 -1
- package/components/icons/icon-paths.js +2 -1
- package/components/navigation/Breadcrumb.jsx +9 -7
- package/components/navigation/Breadcrumb.vue +16 -14
- package/components/tabs/CountBadge.vue +1 -1
- package/components/tabs/Tabs.d.ts +1 -1
- package/components/tabs/Tabs.jsx +1 -1
- package/covers.js +104 -0
- package/gallery.html +133 -0
- package/guidelines/_color-lab.js +46 -0
- package/guidelines/brand-logo.html +9 -0
- package/guidelines/colors-list.html +86 -0
- package/guidelines/colors-palette.html +85 -0
- package/guidelines/colors-schemes.html +68 -0
- package/guidelines/colors-transparency.html +59 -0
- package/guidelines/foundations-borders.html +10 -0
- package/guidelines/foundations-chrome.html +17 -0
- package/guidelines/foundations-motion.html +17 -0
- package/guidelines/foundations-radii.html +14 -0
- package/guidelines/foundations-size-tiers.html +88 -0
- package/guidelines/foundations-spacing.html +19 -0
- package/guidelines/interaction-states.html +25 -0
- package/guidelines/type-extended.html +11 -0
- package/guidelines/type-mono.html +10 -0
- package/guidelines/type-scale.html +15 -0
- package/guidelines/type-weights.html +12 -0
- package/index.html +597 -0
- package/package.json +8 -2
- package/readme.md +1 -1
- package/readme.zh-CN.md +1 -1
- package/site-nav.js +1 -0
- package/tokens/fonts.css +19 -4
- package/tokens/layout.css +5 -5
|
@@ -40,7 +40,7 @@ function Overflow({ items, onSelect }) {
|
|
|
40
40
|
</span>
|
|
41
41
|
{open ? (
|
|
42
42
|
<span style={{ position: "absolute", top: "calc(100% + 5px)", left: 0, zIndex: "var(--z-menu)", minWidth: 168, maxWidth: 260, background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-md)", boxShadow: "var(--shadow-menu)", padding: 4, display: "flex", flexDirection: "column" }}>
|
|
43
|
-
{items.map((it, i) => <OverflowItem key={
|
|
43
|
+
{items.map((it, i) => <OverflowItem key={i} label={it} depth={i} onSelect={(l) => { setOpen(false); onSelect && onSelect(l); }} />)}
|
|
44
44
|
</span>
|
|
45
45
|
) : null}
|
|
46
46
|
</span>
|
|
@@ -67,23 +67,25 @@ export function Breadcrumb({ items = [], onSelect, maxItems = 4, hoveredIndex, s
|
|
|
67
67
|
const head = collapse ? items.slice(0, 1) : items;
|
|
68
68
|
const hidden = collapse ? items.slice(1, items.length - tailCount) : [];
|
|
69
69
|
const tail = collapse ? items.slice(items.length - tailCount) : [];
|
|
70
|
-
|
|
70
|
+
/* Crumbs are identified by position, never by label: a trail may repeat a name ("Workflow › Workflow", two "2026" folders). Only the last index is the current page. */
|
|
71
|
+
const lastIndex = items.length - 1;
|
|
72
|
+
const tailStart = items.length - tailCount;
|
|
71
73
|
return (
|
|
72
74
|
<nav style={{ display: "flex", alignItems: "center", gap: 4, ...style }}>
|
|
73
75
|
{head.map((it, i) => (
|
|
74
|
-
<React.Fragment key={
|
|
76
|
+
<React.Fragment key={i}>
|
|
75
77
|
{i > 0 ? <Sep /> : null}
|
|
76
|
-
<Crumb label={it} last={
|
|
78
|
+
<Crumb label={it} last={i === lastIndex} hovered={hoveredIndex === i} onSelect={onSelect} />
|
|
77
79
|
</React.Fragment>
|
|
78
80
|
))}
|
|
79
81
|
{collapse ? (
|
|
80
82
|
<React.Fragment>
|
|
81
83
|
<Sep />
|
|
82
84
|
<Overflow items={hidden} onSelect={onSelect} />
|
|
83
|
-
{tail.map((it) => (
|
|
84
|
-
<React.Fragment key={
|
|
85
|
+
{tail.map((it, k) => (
|
|
86
|
+
<React.Fragment key={tailStart + k}>
|
|
85
87
|
<Sep />
|
|
86
|
-
<Crumb label={it} last={
|
|
88
|
+
<Crumb label={it} last={tailStart + k === lastIndex} onSelect={onSelect} />
|
|
87
89
|
</React.Fragment>
|
|
88
90
|
))}
|
|
89
91
|
</React.Fragment>
|
|
@@ -16,23 +16,25 @@ const tailCount = computed(() => props.maxItems - 2)
|
|
|
16
16
|
const head = computed(() => collapse.value ? props.items.slice(0, 1) : props.items)
|
|
17
17
|
const hidden = computed(() => collapse.value ? props.items.slice(1, props.items.length - tailCount.value) : [])
|
|
18
18
|
const tail = computed(() => collapse.value ? props.items.slice(props.items.length - tailCount.value) : [])
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
19
|
+
// Crumbs are identified by position, never by label: a trail may repeat a name ("Workflow › Workflow", two "2026" folders). Only the last index is the current page.
|
|
20
|
+
const tailStart = computed(() => props.items.length - tailCount.value)
|
|
21
|
+
const isLast = (i) => i === props.items.length - 1
|
|
22
|
+
// i = the crumb's index in items; hoveredIndex reaches the leading crumbs only (as in React)
|
|
23
|
+
const on = (i, lead) => (hover.value === i || (lead && props.hoveredIndex === i)) && !isLast(i)
|
|
24
|
+
function pick(it, i) { if (!isLast(i)) emit('select', it) }
|
|
23
25
|
function pickHidden(it) { open.value = false; emit('select', it) }
|
|
24
26
|
function onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }
|
|
25
27
|
watch(open, (v) => { if (v) document.addEventListener('mousedown', onDoc); else document.removeEventListener('mousedown', onDoc) })
|
|
26
28
|
onUnmounted(() => document.removeEventListener('mousedown', onDoc))
|
|
27
|
-
const crumbStyle = (i,
|
|
29
|
+
const crumbStyle = (i, lead) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', color: isLast(i) || on(i, lead) ? 'var(--text-heading)' : 'var(--text-secondary)', fontWeight: isLast(i) ? 'var(--weight-medium)' : 'var(--weight-regular)', cursor: isLast(i) ? 'default' : 'pointer', borderRadius: 'var(--radius-sm)', transition: 'var(--t-hover)' })
|
|
28
30
|
</script>
|
|
29
31
|
<template>
|
|
30
32
|
<nav :style="{ display: 'flex', alignItems: 'center', gap: '4px', ...style }">
|
|
31
|
-
<template v-for="(it, i) in head" :key="
|
|
33
|
+
<template v-for="(it, i) in head" :key="i">
|
|
32
34
|
<span v-if="i > 0" style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
|
|
33
|
-
<span :role="isLast(
|
|
34
|
-
@click="pick(it)" @keydown.enter.prevent="pick(it)" @keydown.space.prevent="pick(it)"
|
|
35
|
-
@mouseenter="hover = i" @mouseleave="hover = -1" :style="crumbStyle(i,
|
|
35
|
+
<span :role="isLast(i) ? undefined : 'button'" :tabindex="isLast(i) ? undefined : 0"
|
|
36
|
+
@click="pick(it, i)" @keydown.enter.prevent="pick(it, i)" @keydown.space.prevent="pick(it, i)"
|
|
37
|
+
@mouseenter="hover = i" @mouseleave="hover = -1" :style="crumbStyle(i, true)">{{ it }}</span>
|
|
36
38
|
</template>
|
|
37
39
|
<template v-if="collapse">
|
|
38
40
|
<span style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
|
|
@@ -44,7 +46,7 @@ const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(
|
|
|
44
46
|
<Icon name="ellipsis" :size="14" />
|
|
45
47
|
</span>
|
|
46
48
|
<span v-if="open" style="position:absolute;top:calc(100% + 5px);left:0;z-index:var(--z-menu);min-width:168px;max-width:260px;background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-md);box-shadow:var(--shadow-menu);padding:4px;display:flex;flex-direction:column">
|
|
47
|
-
<span v-for="(h, hi) in hidden" :key="
|
|
49
|
+
<span v-for="(h, hi) in hidden" :key="hi" role="menuitem" tabindex="0"
|
|
48
50
|
@click="pickHidden(h)" @keydown.enter.prevent="pickHidden(h)" @keydown.space.prevent="pickHidden(h)"
|
|
49
51
|
@mouseenter="menuHover = hi" @mouseleave="menuHover = -1"
|
|
50
52
|
:style="{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 9px', paddingLeft: (9 + hi * 12) + 'px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: menuHover === hi ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', color: 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', transition: 'var(--t-hover)' }">
|
|
@@ -52,11 +54,11 @@ const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(
|
|
|
52
54
|
</span>
|
|
53
55
|
</span>
|
|
54
56
|
</span>
|
|
55
|
-
<template v-for="it in tail" :key="
|
|
57
|
+
<template v-for="(it, k) in tail" :key="tailStart + k">
|
|
56
58
|
<span style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
|
|
57
|
-
<span :role="isLast(
|
|
58
|
-
@click="pick(it)" @keydown.enter.prevent="pick(it)" @keydown.space.prevent="pick(it)"
|
|
59
|
-
:style="crumbStyle(
|
|
59
|
+
<span :role="isLast(tailStart + k) ? undefined : 'button'" :tabindex="isLast(tailStart + k) ? undefined : 0"
|
|
60
|
+
@click="pick(it, tailStart + k)" @keydown.enter.prevent="pick(it, tailStart + k)" @keydown.space.prevent="pick(it, tailStart + k)"
|
|
61
|
+
@mouseenter="hover = tailStart + k" @mouseleave="hover = -1" :style="crumbStyle(tailStart + k, false)">{{ it }}</span>
|
|
60
62
|
</template>
|
|
61
63
|
</template>
|
|
62
64
|
</nav>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from 'vue'
|
|
3
|
-
// Count badge beside a segment / pill label: --count-h tall (sm 16 · md
|
|
3
|
+
// Count badge beside a segment / pill label: --count-h tall (sm 16 · md and lg 18), 6px sides, full radius, 12px tabular figures
|
|
4
4
|
// (digits are Latin, so not bumped in CJK). Fill: the alpha wash --neutral-alpha-8, so it darkens any surface by the same step (white tab,
|
|
5
5
|
// #f5f5f5 active pill, dark theme); text tells selected from unselected: --text-heading vs --text-body (a lighter grey fails 4.5:1 at 12px).
|
|
6
6
|
// Hidden for 0 or a missing count unless showZero, which draws a muted 0.
|
|
@@ -20,6 +20,6 @@ export type PillTab = TabItem;
|
|
|
20
20
|
export type PillTabsProps = TabsProps;
|
|
21
21
|
/** Text pill filter tabs at control height; active gets a tinted pill. A tab's count renders as a CountBadge 6px right of the label. */
|
|
22
22
|
export declare function PillTabs(props: PillTabsProps): JSX.Element;
|
|
23
|
-
/** Count badge beside a segment / pill label: --count-h tall (sm 16 · md
|
|
23
|
+
/** Count badge beside a segment / pill label: --count-h tall (sm 16 · md and lg 18), 6px sides, full radius, 12px tabular figures. Fill --neutral-alpha-8 on any surface; text --text-body, --text-heading when selected. Renders nothing for 0 or undefined unless showZero (muted 0). */
|
|
24
24
|
export interface CountBadgeProps { count?: number; selected?: boolean; showZero?: boolean; style?: React.CSSProperties; }
|
|
25
25
|
export declare function CountBadge(props: CountBadgeProps): JSX.Element | null;
|
package/components/tabs/Tabs.jsx
CHANGED
|
@@ -39,7 +39,7 @@ export function PanelTabs({ tabs = [], active, onChange, size, showZero, style }
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
/* Count badge beside a segment / pill label: --count-h tall (sm 16 · md
|
|
42
|
+
/* Count badge beside a segment / pill label: --count-h tall (sm 16 · md and lg 18), 6px sides, full radius, 12px tabular figures
|
|
43
43
|
(digits are Latin, so the size is not bumped in CJK). The fill is the alpha wash --neutral-alpha-8, so it darkens whatever it sits on by the
|
|
44
44
|
same step (white tab, #f5f5f5 active pill, dark theme); the text tells selected from unselected: --text-heading vs --text-body
|
|
45
45
|
(a lighter grey fails 4.5:1 on a grey fill at 12px). Hidden for 0 or a missing count unless showZero, which draws a muted 0. */
|
package/covers.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* Thumbnail covers for the documentation home page: the simplest form of each component, or one telling part of it.
|
|
2
|
+
Keyed by card file stem. Each entry is [scale, render(h, K)] — K is the design-system namespace. A cover that throws
|
|
3
|
+
falls back to the component's name (see renderTiles in index.html). Covers are inert: no pointer events, no focus. */
|
|
4
|
+
window.KB_COVERS = (function () {
|
|
5
|
+
const L = (n, a = 0) => Array.from({ length: n }, (_, i) => Math.round(50 + 30 * Math.sin(i / 1.6 + a) + i * 4));
|
|
6
|
+
const M = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
|
|
7
|
+
const col = (h, gap, kids) => h("div", { style: { display: "flex", flexDirection: "column", gap, alignItems: "flex-start" } }, ...kids);
|
|
8
|
+
const row = (h, gap, kids) => h("div", { style: { display: "flex", gap, alignItems: "center" } }, ...kids);
|
|
9
|
+
return {
|
|
10
|
+
// components
|
|
11
|
+
"accordion": [.8, (h, K) => h("div", { style: { width: 260 } }, h(K.Accordion, { title: "Connection", subtitle: "Host, port, credentials", defaultOpen: false }, "Details"), h(K.Accordion, { title: "Schedule", defaultOpen: false }, "Details"))],
|
|
12
|
+
"action-chip": [1, (h, K) => row(h, 8, [h(K.ActionChip, { tone: "info" }, "Mark as New"), h(K.ActionChip, null, "Skip")])],
|
|
13
|
+
"activity": [.85, (h, K) => h("div", { style: { width: 250 } }, h(K.ActivityItem, { icon: "upload", title: "Import finished", meta: "2 min ago" }), h(K.ActivityItem, { icon: "check", title: "Run completed", meta: "1 h ago" }))],
|
|
14
|
+
"alert": [.8, (h, K) => h("div", { style: { width: 280 } }, h(K.Alert, { tone: "info", title: "Mapping saved" }, "3 fields were updated."))],
|
|
15
|
+
"app-chrome": [.5, (h, K) => h("div", { style: { display: "flex", height: 260, width: 480, border: "1px solid var(--border-card)", borderRadius: "var(--radius-md)", overflow: "hidden" } }, h(K.Sidebar, { logo: "assets/keybricks-logo.png", active: "Home", items: [{ icon: "house", label: "Home" }, { icon: "file-text", label: "Documents", trailing: true }, { icon: "database", label: "Data Integration" }], footerItems: [{ icon: "settings", label: "Management", trailing: true }], style: { width: 180 } }), h("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", background: "var(--bg-app)" } }, h("div", { style: { height: 56, background: "var(--bg-surface)", borderBottom: "1px solid var(--border-card)", display: "flex", alignItems: "center", justifyContent: "flex-end", padding: "0 16px" } }, h(K.Button, { variant: "dark", icon: "ai" }, "AI Assistant"))))],
|
|
16
|
+
"banner": [.7, (h, K) => h("div", { style: { width: 340 } }, h(K.Banner, { tone: "warning", title: "Scheduled maintenance tonight", action: "Details" }))],
|
|
17
|
+
"breadcrumb": [1, (h, K) => h(K.Breadcrumb, { items: ["Home", "Pipelines", "Import"] })],
|
|
18
|
+
"button-1-base": [1, (h, K) => row(h, 8, [h(K.Button, { variant: "primary" }, "Save"), h(K.Button, { variant: "outline" }, "Cancel")])],
|
|
19
|
+
"button-4-group": [1, (h, K) => h(K.ButtonGroup, null, h(K.Button, { variant: "outline" }, "Day"), h(K.Button, { variant: "outline" }, "Week"), h(K.Button, { variant: "outline" }, "Month"))],
|
|
20
|
+
"button-2-states": [1, (h, K) => row(h, 8, [h(K.Button, { variant: "primary", pressed: true }, "Pressed"), h(K.Button, { variant: "primary", disabled: true }, "Disabled")])],
|
|
21
|
+
"button-3-sizes-icons": [1, (h, K) => row(h, 8, [h("div", { "data-size": "sm" }, h(K.Button, { variant: "outline", icon: "plus" }, "Add")), h(K.Button, { variant: "outline", icon: "plus" }, "Add"), h("div", { "data-size": "lg" }, h(K.Button, { variant: "outline", icon: "plus" }, "Add"))])],
|
|
22
|
+
"canvas-zoom-controls": [1, (h, K) => h(K.ZoomControls, { value: 100 })],
|
|
23
|
+
"cards-indicators": [.85, (h, K) => h("div", { style: { width: 220 } }, h(K.StatCard, { label: "Rows imported", value: "12,480" }))],
|
|
24
|
+
"checkbox": [1, (h, K) => col(h, 10, [h(K.Checkbox, { checked: true, label: "Include headers" }), h(K.Checkbox, { checked: false, label: "Skip empty rows" })])],
|
|
25
|
+
"codeblock": [.75, (h, K) => h("div", { style: { width: 300 } }, h(K.CodeBlock, { language: "SQL", code: "SELECT id, amount\nFROM ledger\nWHERE period = '2026-09'" }))],
|
|
26
|
+
"combobox": [1, (h, K) => h(K.Combobox, { options: ["Oracle", "SAP"], value: "Oracle", width: 200 })],
|
|
27
|
+
"context-menu": [.9, (h, K) => h(K.ContextMenu, { items: [{ label: "Preview", icon: "eye" }, { label: "Duplicate", icon: "copy" }, { divider: true }, { label: "Delete", icon: "trash-2", danger: true }] })],
|
|
28
|
+
"date-picker": [1, (h, K) => h(K.DatePicker, { value: "2026-10-22", width: 200 })],
|
|
29
|
+
"diffvalue": [1.1, (h, K) => h(K.DiffValue, { from: "1,200", to: "1,450" })],
|
|
30
|
+
"divider": [1, (h, K) => h("div", { style: { width: 200, display: "flex", flexDirection: "column", gap: 10, fontSize: "var(--text-base)", color: "var(--text-body)" } }, h("span", null, "Connection"), h(K.Divider), h("span", null, "Schedule"), h(K.Divider, { label: "or" }))],
|
|
31
|
+
"drawer": [.5, (h, K) => h(K.Drawer, { open: true, inline: true, title: "Run details", subtitle: "RUN-1890", width: 320, height: 220 }, h("div", { style: { padding: 16 } }, h(K.KeyValue, { label: "Status", value: "Completed" })))],
|
|
32
|
+
"dropdown-menu": [.9, (h, K) => col(h, 6, [h(K.Button, { variant: "outline", icon: "chevron-down", iconPosition: "right" }, "Sort by"), h(K.ContextMenu, { items: [{ label: "Name" }, { label: "Date modified" }, { label: "Size" }] })])],
|
|
33
|
+
"dropzone": [.6, (h, K) => h("div", { style: { width: 320 } }, h(K.Dropzone, { title: "Drop a file to import", hint: "CSV or XLSX, up to 20 MB", height: 140 }))],
|
|
34
|
+
"empty-state": [.7, (h, K) => h(K.EmptyState, { icon: "inbox", title: "No runs yet", hint: "Runs appear here once a pipeline starts." })],
|
|
35
|
+
"filter-bar": [.45, (h, K) => h("div", { style: { width: 520 } }, h(K.FilterBar, { variant: "Standard", adaptCount: 2, defaultExpanded: false }))],
|
|
36
|
+
"icon-button": [1.2, (h, K) => row(h, 4, [h(K.IconButton, { icon: "pencil-line", label: "Edit" }), h(K.IconButton, { icon: "copy", label: "Copy" }), h(K.IconButton, { icon: "ellipsis", label: "More" })])],
|
|
37
|
+
"input": [1, (h, K) => h("div", { style: { width: 220 } }, h(K.Input, { value: "FIN_CURRENCY_DEFAULT", onChange: () => {} }))],
|
|
38
|
+
"keyvalue": [.9, (h, K) => h("div", { style: { width: 240 } }, h(K.KeyValueList, { rows: [{ label: "Source", value: "Oracle EBS" }, { label: "Rows", value: "12,480" }, { label: "Status", value: "Ready" }] }))],
|
|
39
|
+
"kbd": [1.3, (h, K) => h(K.Kbd, { keys: ["⌘", "K"] })],
|
|
40
|
+
"listrow": [.8, (h, K) => h("div", { style: { width: 280 } }, h(K.ListRow, { icon: "database", title: "Oracle EBS", meta: "Last sync 2 min ago" }))],
|
|
41
|
+
"loading": [1, (h, K) => row(h, 16, [h(K.Spinner), h("div", { style: { display: "flex", flexDirection: "column", gap: 8 } }, h(K.Skeleton, { width: 140, height: 12 }), h(K.Skeleton, { width: 100, height: 12 }))])],
|
|
42
|
+
"log-viewer": [.7, (h, K) => h("div", { style: { width: 320 } }, h(K.LogViewer, { lines: ["[14:30] Task started: Import", "[14:30] Reading ledger.csv", "[14:31] 12,480 rows written"], height: 90 }))],
|
|
43
|
+
"modal": [.5, (h, K) => h("div", { style: { width: 360, borderRadius: "var(--radius-lg)", border: "1px solid var(--border-card)", background: "var(--bg-raised)", boxShadow: "var(--shadow-menu)", padding: 20, display: "flex", flexDirection: "column", gap: 16 } }, h("div", { style: { fontSize: "var(--text-md)", fontWeight: "var(--weight-semibold)", color: "var(--text-heading)" } }, "Delete pipeline?"), h("div", { style: { fontSize: "var(--text-base)", color: "var(--text-body)" } }, "This removes 4 nodes and their run history."), row(h, 8, [h("div", { style: { flex: 1 } }), h(K.Button, { variant: "outline" }, "Cancel"), h(K.Button, { variant: "danger" }, "Delete")]))],
|
|
44
|
+
"multi-select": [.9, (h, K) => h(K.MultiSelect, { options: ["HK", "SG", "CN"], value: ["HK", "SG"], width: 220 })],
|
|
45
|
+
"number-stepper": [1.1, (h, K) => h(K.NumberStepper, { value: 100 })],
|
|
46
|
+
"pagination": [.8, (h, K) => h(K.Pagination, { page: 2, pageCount: 5, total: 96 })],
|
|
47
|
+
"people": [1.2, (h, K) => h(K.AvatarGroup, { names: ["Felix Wang", "Luo Lisi", "Guo Kaige", "Ana Silva", "Kim Lee"], max: 4 })],
|
|
48
|
+
"pipeline-node": [.75, (h, K) => h(K.NodeCard, { title: "Join Ledger", subtitle: "2 inputs", icon: "merge", hue: "join", inputs: ["A", "B"] })],
|
|
49
|
+
"pipeline-palette": [.5, (h, K) => h(K.IconTile, { icon: "database", hue: "source" })],
|
|
50
|
+
"radio": [1, (h, K) => col(h, 10, [h(K.Radio, { checked: true, label: "Overwrite" }), h(K.Radio, { checked: false, label: "Append" })])],
|
|
51
|
+
"search": [1, (h, K) => h("div", { style: { width: 220 } }, h(K.SearchInput, { placeholder: "Search pipelines" }))],
|
|
52
|
+
"segmented-control": [1, (h, K) => h(K.SegmentedControl, { options: ["Grid", "List", "Board"], value: "Grid" })],
|
|
53
|
+
"relative-time-picker": [1, (h, K) => h("div", { style: { width: 220 } }, h(K.RelativeTimePicker, { value: { amount: 24, unit: "Hour" } }))],
|
|
54
|
+
"select": [1, (h, K) => h("div", { style: { width: 200 } }, h(K.Select, { options: ["Monthly", "Quarterly"], value: "Monthly" }))],
|
|
55
|
+
"slider": [1, (h, K) => h(K.Slider, { value: 60, width: 200 })],
|
|
56
|
+
"smart-table": [.5, (h, K) => h("div", { style: { width: 440 } }, h(K.SmartTable, { toolbar: false, fields: [{ key: "name", label: "Name", primary: true }, { key: "owner", label: "Owner", type: "person" }, { key: "done", label: "Done", type: "checkbox" }], rows: [{ name: "FX rates", owner: "Felix Wang", done: true }, { name: "Opening balances", owner: "Luo Lisi", done: false }] }))],
|
|
57
|
+
"spreadsheet": [.45, (h, K) => h("div", { style: { width: 460 } }, h(K.Spreadsheet, { rows: 4, toolbar: false, height: 170, data: [[{ v: "Entity" }, { v: "Q3" }, { v: "Q4" }], [{ v: "HK" }, { v: 4100 }, { v: 4600 }], [{ v: "SG" }, { v: 1610 }, { v: 1820 }]] }))],
|
|
58
|
+
"stepper": [.75, (h, K) => h(K.Stepper, { steps: [{ label: "Source" }, { label: "Map" }, { label: "Run" }], current: 1 })],
|
|
59
|
+
"table": [.55, (h, K) => h("div", { style: { width: 400 } }, h(K.Table, { bordered: true, columns: [{ key: "id", label: "Run" }, { key: "doc", label: "Document" }, { key: "st", label: "Status" }], rows: [{ id: "RUN-1890", doc: "FX Rates", st: "Completed" }, { id: "RUN-1891", doc: "Ledger", st: "Running" }] }))],
|
|
60
|
+
"tabs": [.9, (h, K) => h(K.Tabs, { tabs: [{ label: "Import", count: 3 }, "Export", "History"], active: "Import" })],
|
|
61
|
+
"tag": [1.1, (h, K) => row(h, 6, [h(K.Tag, { onRemove: () => {} }, "HK"), h(K.Tag, { tone: "info", onRemove: () => {} }, "Q3")])],
|
|
62
|
+
"textarea": [.9, (h, K) => h("div", { style: { width: 240 } }, h(K.Textarea, { value: "Monthly close notes", rows: 3, onChange: () => {} }))],
|
|
63
|
+
"timeline": [.8, (h, K) => h("div", { style: { width: 240 } }, h(K.Timeline, { items: [{ tone: "success", title: "Run completed", meta: "14:31" }, { title: "Mapping updated", meta: "13:05" }] }))],
|
|
64
|
+
"toast": [.75, (h, K) => h(K.Toast, { tone: "success", title: "Pipeline saved", meta: "Just now" })],
|
|
65
|
+
"toggles": [1.2, (h, K) => row(h, 12, [h(K.Switch, { checked: true }), h(K.Switch, { checked: false })])],
|
|
66
|
+
"tooltip": [1, (h, K) => h("div", { style: { padding: "6px 10px", borderRadius: "var(--radius-md)", background: "var(--action-dark)", color: "var(--action-dark-fg)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)" } }, "Copy to clipboard")],
|
|
67
|
+
"tree": [.8, (h, K) => h("div", { style: { width: 220 } }, h(K.Tree, { defaultExpanded: ["a"], items: [{ key: "a", title: "Assets", children: [{ key: "a1", title: "Cash" }, { key: "a2", title: "Receivables" }] }, { key: "b", title: "Liabilities", children: [{ key: "b1", title: "Payables" }] }] }))],
|
|
68
|
+
// icons — any icon card without a cover falls back to its name; new sets just need an entry here
|
|
69
|
+
"icons-0-usage": [1, (h, K) => h("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 40px)", gap: 12 } }, ...[["source", "table"], ["join", "shuffle"], ["transform", "funnel"], ["merge", "merge"], ["output", "layers"]].map(([hu, i]) => h(K.IconTile, { key: hu, icon: i, hue: hu })), h("span", { key: "s", style: { display: "flex", alignItems: "center", justifyContent: "center" } }, h(K.StatusBadge, { tone: "success", icon: "check" })))],
|
|
70
|
+
"icons-1-core": [1, (h, K) => h("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 28px)", gap: 14, color: "var(--text-body)" } }, ...["house", "file-text", "database", "journal", "reconciliation", "consolidation", "ai", "settings"].map(n => h(K.Icon, { key: n, name: n, size: 24 })))],
|
|
71
|
+
"icons-2-feather": [1, (h, K) => h("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 28px)", gap: 14, color: "var(--text-body)" } }, ...["feather", "anchor", "aperture", "compass", "cpu", "droplet", "globe", "umbrella"].map(n => h(K.Icon, { key: n, name: n, size: 24 })))],
|
|
72
|
+
"icons-2b-lucide": [1, (h, K) => h("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 28px)", gap: 14, color: "var(--text-body)" } }, ...["calendar-clock", "file-chart-pie", "list-checks", "badge-check", "folder-tree", "square-terminal", "zap", "boxes"].map(n => h(K.Lucide, { key: n, name: n, size: 24 })))],
|
|
73
|
+
"icons-3-unicons": [1, (h, K) => h("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 28px)", gap: 14, color: "var(--text-body)" } }, ...["chart", "bolt", "cloud", "envelope", "rocket", "star", "user", "wallet"].map(n => h(K.Unicon, { key: n, name: n, size: 24 })))],
|
|
74
|
+
"icons-4-datasource": [1, (h) => h("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 32px)", gap: 12 } }, ...["oracle", "sap", "sql-server", "mysql", "postgresql", "mongodb", "rest-api", "excel"].map(n => h("img", { key: n, src: "assets/logos/datasource/" + n + ".png", alt: n, width: 32, height: 32, style: { display: "block", borderRadius: "var(--radius-md)" } })))],
|
|
75
|
+
// charts
|
|
76
|
+
"charts-1-sparkline": [1, (h, K) => h(K.Sparkline, { values: L(14), width: 200, height: 56 })],
|
|
77
|
+
"charts-2-line": [.6, (h, K) => h(K.LineChart, { series: [{ name: "Actual", values: L(6) }, { name: "Plan", values: L(6, 1) }], xLabels: M, width: 340, height: 180 })],
|
|
78
|
+
"charts-3-bar": [.6, (h, K) => h(K.BarChart, { series: [{ name: "2025", values: [40, 62, 55, 80] }, { name: "2026", values: [52, 70, 66, 92] }], categories: ["Q1", "Q2", "Q3", "Q4"], width: 340, height: 180 })],
|
|
79
|
+
"charts-4-barlist": [.7, (h, K) => h(K.BarList, { rows: [{ label: "Germany", value: 5200 }, { label: "France", value: 3100 }, { label: "Spain", value: 1100 }], width: 280 })],
|
|
80
|
+
"charts-5-donut": [.7, (h, K) => h(K.DonutChart, { segments: [{ label: "EMEA", value: 42 }, { label: "APAC", value: 31 }, { label: "AMER", value: 27 }], size: 150, centerValue: "100%", legend: false })],
|
|
81
|
+
"charts-6-scatter": [.6, (h, K) => h(K.ScatterChart, { points: [[12, 340, 8], [30, 210, 5], [44, 420, 10], [58, 300, 6], [70, 480, 9], [82, 260, 4]].map(([x, y, r]) => ({ x, y, r })), width: 340, height: 180 })],
|
|
82
|
+
"charts-7-heatmap": [.6, (h, K) => h(K.HeatmapChart, { xLabels: M, yLabels: ["HK", "SG", "CN", "JP"], values: [0, 1, 2, 3].map(r => M.map((_, c) => (r * 7 + c * 5) % 17)), width: 340 })],
|
|
83
|
+
"charts-8-waterfall": [.6, (h, K) => h(K.WaterfallChart, { steps: [{ label: "Revenue", value: 6100 }, { label: "COGS", value: -2400 }, { label: "Opex", value: -1500 }, { label: "Tax", value: -500 }], width: 340, height: 180 })],
|
|
84
|
+
"charts-9-bullet": [.7, (h, K) => h(K.BulletChart, { rows: [{ label: "Revenue", value: 6100, target: 5800 }, { label: "Margin", value: 38, target: 42 }], width: 280 })],
|
|
85
|
+
"charts-10-gauge": [.75, (h, K) => h(K.GaugeChart, { value: 72, label: "Close progress" })],
|
|
86
|
+
"charts-11-funnel": [.55, (h, K) => h(K.FunnelChart, { stages: [{ label: "Imported", value: 1200 }, { label: "Mapped", value: 940 }, { label: "Validated", value: 720 }, { label: "Posted", value: 610 }], width: 280 })],
|
|
87
|
+
"charts-12-treemap": [.6, (h, K) => h(K.TreemapChart, { items: [{ label: "Payroll", value: 42 }, { label: "Rent", value: 18 }, { label: "IT", value: 14 }, { label: "Travel", value: 9 }, { label: "Other", value: 7 }], width: 340, height: 180 })],
|
|
88
|
+
"charts-14-radar": [.6, (h, K) => h(K.RadarChart, { axes: ["Speed", "Quality", "Cost", "Scope", "Risk"].map(label => ({ label, max: 100 })), series: [{ name: "Team A", values: [80, 65, 70, 90, 50] }, { name: "Team B", values: [60, 85, 55, 70, 75] }], size: 200, legend: false })],
|
|
89
|
+
"charts-15-distribution": [.6, (h, K) => h(K.Histogram, { values: Array.from({ length: 120 }, (_, i) => Math.round(50 + 20 * Math.sin(i * 1.7) + (i % 9) * 3)), bins: 12, width: 340, height: 180 })],
|
|
90
|
+
"charts-16-pareto": [.6, (h, K) => h(K.ParetoChart, { items: [{ label: "Mapping", value: 48 }, { label: "Format", value: 26 }, { label: "Dupes", value: 14 }, { label: "Other", value: 8 }], width: 340, height: 180 })],
|
|
91
|
+
"charts-17-candlestick": [.6, (h, K) => h(K.CandlestickChart, { candles: L(14).map((v, i) => { const o = v, c = v + ((i % 3) - 1) * 6; return { date: "D" + i, label: "D" + i, o, c, h: Math.max(o, c) + 5, l: Math.min(o, c) - 5, open: o, close: c, high: Math.max(o, c) + 5, low: Math.min(o, c) - 5 }; }), width: 340, height: 180 })],
|
|
92
|
+
"charts-18-sankey": [.55, (h, K) => h(K.SankeyChart, { nodes: ["Revenue", "COGS", "Opex", "Profit"].map(id => ({ id, label: id })), links: [{ source: "Revenue", target: "COGS", value: 40 }, { source: "Revenue", target: "Opex", value: 25 }, { source: "Revenue", target: "Profit", value: 35 }], width: 360, height: 200 })],
|
|
93
|
+
"charts-19-chord": [.55, (h, K) => h(K.ChordChart, { labels: ["HK", "SG", "CN", "JP"], matrix: [[0, 5, 8, 3], [4, 0, 2, 6], [7, 3, 0, 4], [2, 5, 3, 0]], size: 240 })],
|
|
94
|
+
"charts-20-sunburst": [.55, (h, K) => h(K.SunburstChart, { data: { label: "Total cost", children: [{ label: "People", children: [{ label: "Payroll", value: 40 }, { label: "Benefits", value: 12 }] }, { label: "Facilities", children: [{ label: "Rent", value: 18 }, { label: "Utilities", value: 6 }] }, { label: "IT", value: 14 }] }, size: 240 })],
|
|
95
|
+
"charts-21-worldmap": [.45, (h, K) => h(K.WorldMap, { values: { "United States": 6400, Canada: 2100, Brazil: 1400, "United Kingdom": 4200, Germany: 5200, France: 3100, China: 5800, Japan: 2600, Australia: 1200 }, width: 460, legend: false })],
|
|
96
|
+
"charts-22-gantt": [.55, (h, K) => h(K.GanttChart, { tasks: [{ label: "Extract", start: "2026-03-02", end: "2026-03-05", progress: 1, lane: 0 }, { label: "Map", start: "2026-03-04", end: "2026-03-09", progress: .6, lane: 1 }, { label: "Validate", start: "2026-03-08", end: "2026-03-12", progress: 0, lane: 2 }], width: 380 })],
|
|
97
|
+
"charts-23-bar3d": [.55, (h, K) => h(K.Bar3D, { series: [{ name: "2025", values: [40, 62, 55, 80] }, { name: "2026", values: [52, 70, 66, 92] }], categories: ["Q1", "Q2", "Q3", "Q4"], width: 360, height: 220, interactive: false })],
|
|
98
|
+
"charts-24-pie3d": [.6, (h, K) => h(K.Pie3D, { segments: [{ label: "EMEA", value: 42 }, { label: "APAC", value: 31 }, { label: "AMER", value: 27 }], legend: false, width: 300, height: 200 })],
|
|
99
|
+
"charts-25-surface3d": [.55, (h, K) => h(K.Surface3D, { width: 360, height: 220, interactive: false })],
|
|
100
|
+
"charts-26-scatter3d": [.55, (h, K) => h(K.Scatter3D, { points: Array.from({ length: 30 }, (_, i) => ({ x: (i * 37) % 100, y: (i * 53) % 100, z: (i * 71) % 100, series: ["Alpha", "Beta", "Gamma"][i % 3] })), width: 360, height: 220, legend: false, interactive: false })],
|
|
101
|
+
"charts-27-ribbon3d": [.55, (h, K) => h(K.Ribbon3D, { series: [{ name: "HK", values: L(6) }, { name: "SG", values: L(6, 1) }, { name: "CN", values: L(6, 2) }], xLabels: M, width: 360, height: 220, interactive: false })],
|
|
102
|
+
"charts-99-fullpage": [.5, (h, K) => h("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, width: 380 } }, h(K.StatCard, { label: "Revenue", value: "$6.1M" }), h(K.StatCard, { label: "Margin", value: "38%" }), h("div", { style: { gridColumn: "1 / -1" } }, h(K.Sparkline, { values: L(20), width: 380, height: 60 })))]
|
|
103
|
+
};
|
|
104
|
+
})();
|
package/gallery.html
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
3
|
+
<title>Keybricks Design System</title>
|
|
4
|
+
<script src="tokens/theme.js"></script>
|
|
5
|
+
<link rel="stylesheet" href="styles.css"><link rel="stylesheet" href="tokens/hover.css">
|
|
6
|
+
<style>
|
|
7
|
+
*{box-sizing:border-box}
|
|
8
|
+
body{margin:0;font-family:var(--font-sans);background:var(--bg-app);color:var(--text-body)}
|
|
9
|
+
header{background:var(--bg-surface);border-bottom:1px solid var(--border-card);padding:22px 32px 18px}
|
|
10
|
+
h1{margin:0;font-size:var(--text-2xl);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-2xl);color:var(--text-heading)}
|
|
11
|
+
.sub{margin:4px 0 0;font-size:var(--text-base);color:var(--text-secondary)}
|
|
12
|
+
.bar{display:flex;align-items:center;gap:10px;padding-top:16px;flex-wrap:wrap}
|
|
13
|
+
input[type=search]{height:var(--h-input);flex:1;min-width:200px;max-width:340px;border:1px solid var(--border-control);border-radius:var(--radius-md);background:var(--bg-surface);color:var(--text-primary);font-family:var(--font-sans);font-size:var(--text-xs);padding:0 10px;letter-spacing:var(--tracking-xs)}
|
|
14
|
+
input[type=search]:focus{outline:none;border-color:var(--focus-border);box-shadow:0 0 0 2px var(--focus-ring)}
|
|
15
|
+
.count{font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-muted);letter-spacing:var(--tracking-xs)}
|
|
16
|
+
a.doc{margin-left:auto;font-size:var(--text-xs);color:var(--action-primary);text-decoration:none;font-weight:var(--weight-medium);letter-spacing:var(--tracking-xs)}
|
|
17
|
+
a.doc:hover{color:var(--action-primary-hover);text-decoration:underline}
|
|
18
|
+
main{padding:24px 32px 56px}
|
|
19
|
+
h2{margin:28px 0 12px;font-size:var(--text-xs);line-height:var(--leading-xs);font-weight:var(--weight-semibold);color:var(--text-secondary);letter-spacing:var(--tracking-xs)}
|
|
20
|
+
h2:first-child{margin-top:0}
|
|
21
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px}
|
|
22
|
+
.card{background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-lg);overflow:hidden;display:flex;flex-direction:column}
|
|
23
|
+
.card figure{margin:0;height:210px;overflow:hidden;background:var(--bg-surface);border-bottom:1px solid var(--border-subtle);position:relative}
|
|
24
|
+
.card iframe{border:0;position:absolute;top:0;left:0;transform-origin:top left;background:var(--bg-surface)}
|
|
25
|
+
.meta{padding:11px 13px 13px}
|
|
26
|
+
.meta b{display:block;font-size:var(--text-base);font-weight:var(--weight-medium);color:var(--text-heading)}
|
|
27
|
+
.meta span{display:block;font-size:var(--text-xs);color:var(--text-secondary);padding-top:2px;line-height:1.45;letter-spacing:var(--tracking-xs)}
|
|
28
|
+
.meta a{display:inline-block;margin-top:8px;font-size:var(--text-xs);font-family:var(--font-sans);font-variant-numeric:tabular-nums;color:var(--action-primary);text-decoration:none;letter-spacing:var(--tracking-xs)}
|
|
29
|
+
.meta a:hover{text-decoration:underline}
|
|
30
|
+
.empty{padding:40px;text-align:center;color:var(--text-muted);font-size:var(--text-xs);letter-spacing:var(--tracking-xs)}
|
|
31
|
+
</style></head>
|
|
32
|
+
<body>
|
|
33
|
+
<header>
|
|
34
|
+
<h1>Keybricks Design System</h1>
|
|
35
|
+
<p class="sub">121 components in React and Vue 3, on one token layer. Every card below is the real component, not a screenshot.</p>
|
|
36
|
+
<div class="bar">
|
|
37
|
+
<input type="search" id="q" placeholder="Filter components…" autocomplete="off">
|
|
38
|
+
<span class="count" id="count"></span>
|
|
39
|
+
<a class="doc" href="INTEGRATION.md">Integration guide →</a>
|
|
40
|
+
</div>
|
|
41
|
+
</header>
|
|
42
|
+
<main id="out"><div class="empty">Loading…</div></main>
|
|
43
|
+
<script type="application/json" id="cards">
|
|
44
|
+
[{"path":"components/_cards/accordion.card.html","group":"Components","viewport":"700x320","name":"Accordion","subtitle":"Generic collapsible section — settings/FAQ style"},{"path":"components/_cards/action-chip.card.html","group":"Components","viewport":"700x370","name":"Action Chip","subtitle":"Tinted bulk-action chips · five tones · size tiers, Latin and CJK"},{"path":"components/_cards/activity.card.html","group":"Components","viewport":"700x300","name":"Activity","subtitle":"ActivityItem rows · DotActivity feed"},{"path":"components/_cards/alert.card.html","group":"Components","viewport":"700x350","name":"Alert","subtitle":"In-card message — 4 tones, with and without body"},{"path":"components/_cards/app-chrome.card.html","group":"Components","viewport":"700x1490","name":"App Chrome","subtitle":"Sidebar tones · SidePanel · TopBar · UserMenu · PageToolbar"},{"path":"components/_cards/banner.card.html","group":"Components","viewport":"700x300","name":"Banner","subtitle":"Page-level notification — 4 tones · action link · dismissible"},{"path":"components/_cards/breadcrumb.card.html","group":"Components","viewport":"700x468","name":"Breadcrumb","subtitle":"Path trail — hover darkens · long trails collapse to an ellipsis dropdown"},{"path":"components/_cards/button-1-base.card.html","group":"Components","viewport":"700x690","name":"Button","subtitle":"Solid · soft/outline · ghost · text-only · disabled · loading"},{"path":"components/_cards/button-4-group.card.html","group":"Components","viewport":"700x490","name":"Button Group","subtitle":"Segmented group · split button · shortcut hint · badge"},{"path":"components/_cards/button-2-states.card.html","group":"Components","viewport":"1000x810","name":"Button Interaction States","subtitle":"Rest · hover · press · focus · loading · disabled — every variant, live and forced"},{"path":"components/_cards/button-3-sizes-icons.card.html","group":"Components","viewport":"700x720","name":"Button Sizes & Icons","subtitle":"sm 28 · md 32 · lg 40, Latin and CJK · icon placement · full width"},{"path":"components/_cards/canvas-zoom-controls.card.html","group":"Components","viewport":"700x180","name":"Canvas Zoom Controls","subtitle":"Pipeline canvas − / % / + / fit-to-view"},{"path":"components/_cards/cards-indicators.card.html","group":"Components","viewport":"700x450","name":"Cards & Indicators","subtitle":"Work in progress · Card · StatCard · Badge · IconTile · charts"},{"path":"components/_cards/checkbox.card.html","group":"Components","viewport":"700x370","name":"Checkbox","subtitle":"Checked · unchecked · indeterminate · disabled · sizes"},{"path":"components/_cards/codeblock.card.html","group":"Components","viewport":"700x260","name":"Code Block","subtitle":"SQL Preview / JSON export — mono with copy button"},{"path":"components/_cards/combobox.card.html","group":"Components","viewport":"700x430","name":"Combobox","subtitle":"Typeahead single-select — type to filter · keyboard nav · no-results"},{"path":"components/_cards/context-menu.card.html","group":"Components","viewport":"700x920","name":"Context Menu","subtitle":"Icons · plain text · danger item · long list"},{"path":"components/_cards/date-picker.card.html","group":"Components","viewport":"700x520","name":"Date Picker","subtitle":"Input + month-grid popover — today outlined · selected filled"},{"path":"components/_cards/diffvalue.card.html","group":"Components","viewport":"700x220","name":"Diff Value","subtitle":"Before → after change display (config migration)"},{"path":"components/_cards/divider.card.html","group":"Components","viewport":"700x220","name":"Divider","subtitle":"Plain · labeled · vertical"},{"path":"components/_cards/drawer.card.html","group":"Components","viewport":"700x540","name":"Drawer","subtitle":"Slide-out detail panel — header · scrollable body · footer actions"},{"path":"components/_cards/dropdown-menu.card.html","group":"Components","viewport":"700x420","name":"Dropdown Menu","subtitle":"Button · icon-only · ghost text · avatar triggers — icon items & plain-text items"},{"path":"components/_cards/dropzone.card.html","group":"Components","viewport":"924x588","name":"Dropzone","subtitle":"Empty · drag-over · uploaded · error · disabled"},{"path":"components/_cards/empty-state.card.html","group":"Components","viewport":"924x580","name":"Empty State","subtitle":"First-run · no results · error — with and without action"},{"path":"components/_cards/filter-bar.card.html","group":"Components","viewport":"1000x620","name":"Filter Bar (List Report)","subtitle":"Fiori-style filter bar composed with a table — Adapt Filters · Go"},{"path":"components/_cards/icon-button.card.html","group":"Components","viewport":"700x400","name":"Icon Button","subtitle":"Square ghost icon button · sm 28 · md 32 · lg 40 · active / muted / danger / loading / disabled"},{"path":"components/_cards/input.card.html","group":"Components","viewport":"700x720","name":"Input","subtitle":"Default · filled · required · readOnly · disabled · error · hint · focus"},{"path":"components/_cards/keyvalue.card.html","group":"Components","viewport":"700x250","name":"Key/Value Rows","subtitle":"Config summary and confirmation-step rows"},{"path":"components/_cards/kbd.card.html","group":"Components","viewport":"700x180","name":"Keyboard Shortcuts","subtitle":"Kbd — keycap hints for a power-user tool"},{"path":"components/_cards/listrow.card.html","group":"Components","viewport":"700x420","name":"List Row","subtitle":"Expandable workflow rows — collapsed, expanded, with trailing count"},{"path":"components/_cards/loading.card.html","group":"Components","viewport":"700x310","name":"Loading","subtitle":"Spinner sizes · Skeleton text, blocks and table rows"},{"path":"components/_cards/log-viewer.card.html","group":"Components","viewport":"700x370","name":"Log Viewer","subtitle":"Mono run log — info · warn · error lines"},{"path":"components/_cards/modal.card.html","group":"Components","viewport":"700x520","name":"Modal","subtitle":"Default · destructive footer · with form content · no subtitle"},{"path":"components/_cards/multi-select.card.html","group":"Components","viewport":"700x400","name":"Multi Select","subtitle":"Checkbox dropdown — removable tags · +n overflow · clear all"},{"path":"components/_cards/number-stepper.card.html","group":"Components","viewport":"700x240","name":"Number Stepper","subtitle":"Default · at min · at max · disabled"},{"path":"components/_cards/pagination.card.html","group":"Components","viewport":"700x520","name":"Pagination","subtitle":"Row-range label · small set · first/last page states"},{"path":"components/_cards/people.card.html","group":"Components","viewport":"700x300","name":"People","subtitle":"Avatar · AvatarGroup — initials, sizes, real images"},{"path":"components/_cards/pipeline-node.card.html","group":"Components","viewport":"700x1620","name":"Pipeline Node Card","subtitle":"220×90 node — category hues · status badges · ports with hover, drag-to-connect and drop states"},{"path":"components/_cards/pipeline-palette.card.html","group":"Components","viewport":"700x690","name":"Pipeline Palette","subtitle":"240px component drawer — grid/list toggle · collapsible hued categories"},{"path":"components/_cards/radio.card.html","group":"Components","viewport":"700x1120","name":"Radio Button","subtitle":"Single choice from a small set — md/sm · description · disabled · row · card variant · arrow-key navigation"},{"path":"components/_cards/search.card.html","group":"Components","viewport":"700x520","name":"Search Input","subtitle":"Bordered · bare top-bar · with value/clear · disabled"},{"path":"components/_cards/segmented-control.card.html","group":"Components","viewport":"700x1260","name":"Segmented Control","subtitle":"Mutually exclusive views in one inset track — md/sm · counts · icons · icon-only · disabled option · full width · ViewToggle"},{"path":"components/_cards/select.card.html","group":"Components","viewport":"700x430","name":"Select","subtitle":"md / sm · placeholder · disabled · error"},{"path":"components/_cards/relative-time-picker.card.html","group":"Components","viewport":"700x720","name":"Relative Time Picker","subtitle":"Filter by how long ago · md / sm / lg · unlimited · disabled · localised labels","zh":"相对时间选择器","ja":"相対時間ピッカー","subtitleZh":"按\"多久以前\"筛选 · md / sm / lg · 不限时间 · 禁用 · 本地化文案","subtitleJa":"「どれくらい前」で絞り込み · md / sm / lg · 無制限 · 無効 · ローカライズ"},{"path":"components/_cards/slider.card.html","group":"Components","viewport":"700x300","name":"Slider","subtitle":"Filled track · ring knob · value bubble · min/max labels"},{"path":"components/_cards/smart-table.card.html","group":"Components","viewport":"700x1040","name":"Smart Table (Database Grid)","subtitle":"Frozen header and row numbers · default and compact density · typed fields · hue pills · aggregates · + New"},{"path":"components/_cards/spreadsheet.card.html","group":"Components","viewport":"924x560","name":"Spreadsheet","subtitle":"Formula bar · frozen headers · range selection · =SUM() · sheet tabs · status bar"},{"path":"components/_cards/stepper.card.html","group":"Components","viewport":"700x450","name":"Stepper","subtitle":"Multi-step flow header — done · current · upcoming"},{"path":"components/_cards/table.card.html","group":"Components","viewport":"700x6150","name":"Table","subtitle":"Anatomy · densities · column types · two-line name · sorting · selection · frames · column resize · states · composition"},{"path":"components/_cards/tabs.card.html","group":"Components","viewport":"700x810","name":"Tabs","subtitle":"Underline tabs · panel tabs · pill filter tabs · counts"},{"path":"components/_cards/tag.card.html","group":"Components","viewport":"700x300","name":"Tag","subtitle":"Removable filter tags — distinct from Badge/ActionChip"},{"path":"components/_cards/textarea.card.html","group":"Components","viewport":"924x520","name":"Textarea","subtitle":"Default · character count · over the limit · disabled"},{"path":"components/_cards/timeline.card.html","group":"Components","viewport":"700x350","name":"Timeline","subtitle":"Vertical activity timeline — toned icon nodes on a hairline spine"},{"path":"components/_cards/toast.card.html","group":"Components","viewport":"700x450","name":"Toast","subtitle":"Tones · with/without meta · live ToastStack demo"},{"path":"components/_cards/toggles.card.html","group":"Components","viewport":"700x1670","name":"Toggle Switch","subtitle":"Binary settings pill — sizes · semantic red→green · glyph knobs · inline text · captions · async loading"},{"path":"components/_cards/tooltip.card.html","group":"Components","viewport":"700x360","name":"Tooltip","subtitle":"Placements — top · bottom · left · right, on icon and text triggers"},{"path":"components/_cards/tree.card.html","group":"Components","viewport":"700x2820","name":"Tree","subtitle":"Cascading checkboxes · disabled nodes · lines · async load · search · drag · reorder · keyboard"},{"path":"components/_cards/icons-0-usage.card.html","group":"Icons","viewport":"700x2250","name":"Icon Usage","subtitle":"Monochrome glyphs · category tiles · document hues · status · marks · icons inside components — when an icon gets colour","zh":"图标用法","ja":"アイコンの使い方","subtitleZh":"单色图标 · 分类色块 · 文档色 · 状态 · 标记 · 组件内图标——图标什么时候用颜色","subtitleJa":"単色グリフ · カテゴリタイル · ドキュメント色 · ステータス · マーク · コンポーネント内のアイコン — いつ色を付けるか"},{"path":"components/_cards/icons-1-core.card.html","group":"Icons","viewport":"700x1900","name":"Core Set","subtitle":"The 157 stroke glyphs the product itself uses — keyword search · click to download the SVG"},{"path":"components/_cards/icons-2-feather.card.html","group":"Icons","viewport":"700x4400","name":"Feather Library","subtitle":"404 stroke icons (core + all of Feather) · keyword search · click to download the SVG"},{"path":"components/_cards/icons-2b-lucide.card.html","group":"Icons","name":"Lucide Library","viewport":"700x1370","subtitle":"Full Lucide set (1,700+ stroke glyphs, core style) via pinned CDN · keyword search · click to download the SVG","zh":"Lucide 图标库","ja":"Lucideライブラリ","subtitleZh":"完整 Lucide 图标集(1,700+ 线性图标,与核心风格一致,固定版本 CDN)· 关键词搜索 · 点击下载 SVG","subtitleJa":"Lucide 全セット(1,700+ のストロークアイコン、コアと同一スタイル、固定版 CDN)· キーワード検索 · クリックで SVG をダウンロード"},{"path":"components/_cards/icons-3-unicons.card.html","group":"Icons","viewport":"700x1370","name":"Unicons Library","subtitle":"1,206 Unicons line glyphs via CDN webfont · keyword search · click to download the SVG"},{"path":"components/_cards/icons-4-datasource.card.html","group":"Icons","viewport":"700x660","name":"Data Source Logos","subtitle":"17 data-source logo images in assets/logos/datasource/","zh":"数据源图标","ja":"データソースロゴ","subtitleZh":"17 张数据源图标图片,位于 assets/logos/datasource/","subtitleJa":"assets/logos/datasource/ の 17 枚のデータソースロゴ画像"},{"path":"guidelines/brand-logo.html","group":"Brand","viewport":"700x170","name":"Logo & Wordmark","subtitle":"Blue cube bitmap + Inter 600 wordmark, −0.609px tracking"},{"path":"guidelines/colors-list.html","group":"Colors","viewport":"700x2740","name":"Color List","subtitle":"Every color in the system, with name and hex"},{"path":"guidelines/colors-palette.html","group":"Colors","viewport":"700x880","name":"Palette","subtitle":"Grouped by role — hover a dot for its token and hex"},{"path":"guidelines/colors-schemes.html","group":"Colors","viewport":"700x800","name":"Schemes","subtitle":"Sets that work together — surface, text, border, accent"},{"path":"guidelines/colors-transparency.html","group":"Colors","viewport":"700x490","name":"Transparency","subtitle":"Alpha scale — the number is the opacity"},{"path":"guidelines/foundations-borders.html","group":"Foundations","viewport":"700x190","name":"Borders & Shadows","subtitle":"Hairlines carry the UI; shadows only on canvas nodes"},{"path":"guidelines/foundations-chrome.html","group":"Foundations","viewport":"700x180","name":"App Chrome Dimensions","subtitle":"Fixed panel widths & bar heights"},{"path":"guidelines/foundations-motion.html","group":"Foundations","viewport":"700x170","name":"Motion","subtitle":"120/160/240ms · ease-out — hover, rise, pulse, spin"},{"path":"guidelines/foundations-radii.html","group":"Foundations","viewport":"700x200","name":"Corner Radii","subtitle":"4 controls · 8 buttons · 10 cards · 14/16 tiles & nodes · full circles"},{"path":"guidelines/foundations-size-tiers.html","group":"Foundations","viewport":"700x1340","name":"Size Tiers","subtitle":"sm 28 · md 32 · lg 40 — one tier per region, never mixed in a row; CJK one step larger at sm"},{"path":"guidelines/foundations-spacing.html","group":"Foundations","viewport":"700x170","name":"Spacing","subtitle":"4px family · page 24×32 · card 21 · section gap 24 · gaps 8 inside a group, 12/16/24 between groups"},{"path":"guidelines/interaction-states.html","group":"Foundations","viewport":"700x570","name":"Interaction States","subtitle":"The five states every control carries — and where each one is defined"},{"path":"guidelines/type-extended.html","group":"Type","viewport":"700x440","name":"Display Scale","subtitle":"Display sizes 24–60 — tracking follows the same size→tracking curve"},{"path":"guidelines/type-mono.html","group":"Type","viewport":"700x170","name":"Mono / Code Only","subtitle":"Menlo for code blocks, logs, inline code and config-code chips — IDs and amounts stay in Inter with tabular figures"},{"path":"guidelines/type-scale.html","group":"Type","viewport":"700x450","name":"Type Scale","subtitle":"Inter — the 14px scale: 12 captions · 14 body and controls · 16 section titles · 20 page titles; CJK one step larger below 14"},{"path":"guidelines/type-weights.html","group":"Type","viewport":"700x280","name":"Inter Weights","subtitle":"300 numerals · 400 body · 500 UI-medium · 600 titles — 500 becomes 600 in Chinese, Japanese and Korean"},{"path":"components/_cards/charts-1-sparkline.card.html","group":"Charts","viewport":"700x830","name":"Sparkline","subtitle":"Micro trends for stat tiles, table cells, list rows — line · area · bars · curve"},{"path":"components/_cards/charts-2-line.card.html","group":"Charts","viewport":"700x1370","name":"Line & Area","subtitle":"Trends over time — straight, smooth or stepped · stacked area · hover + legend mute"},{"path":"components/_cards/charts-3-bar.card.html","group":"Charts","viewport":"700x2040","name":"Bar Chart","subtitle":"Vertical & horizontal — grouped · stacked · 100% stacked · diverging · combo line"},{"path":"components/_cards/charts-4-barlist.card.html","group":"Charts","viewport":"700x540","name":"Bar List","subtitle":"Horizontal labeled bars — top-N rankings, budget consumption"},{"path":"components/_cards/charts-5-donut.card.html","group":"Charts","viewport":"700x570","name":"Donut & Pie","subtitle":"Composition with a KPI center — hover lifts a segment"},{"path":"components/_cards/charts-6-scatter.card.html","group":"Charts","viewport":"700x480","name":"Scatter & Bubble","subtitle":"x/y correlation, bubble = third dimension — nearest-point tooltip"},{"path":"components/_cards/charts-7-heatmap.card.html","group":"Charts","viewport":"700x480","name":"Heatmap","subtitle":"Matrix density — sequential blue ramp · diverging red/green variance"},{"path":"components/_cards/charts-8-waterfall.card.html","group":"Charts","viewport":"700x470","name":"Waterfall","subtitle":"Finance bridge — signed deltas, re-baselining totals, running-sum connectors"},{"path":"components/_cards/charts-9-bullet.card.html","group":"Charts","viewport":"700x470","name":"Bullet","subtitle":"Actual vs. target rows with a target tick — misses in red"},{"path":"components/_cards/charts-10-gauge.card.html","group":"Charts","viewport":"700x310","name":"Gauge","subtitle":"Semi-circular KPI — flat arc, threshold bands, no needle"},{"path":"components/_cards/charts-11-funnel.card.html","group":"Charts","viewport":"700x620","name":"Funnel","subtitle":"Stage counts with step-to-step conversion — sub-50% steps flagged"},{"path":"components/_cards/charts-12-treemap.card.html","group":"Charts","viewport":"700x450","name":"Treemap","subtitle":"Squarified composition — readable at 15+ items, hover for share"},{"path":"components/_cards/charts-14-radar.card.html","group":"Charts","viewport":"700x490","name":"Radar","subtitle":"Spider chart — a few entities across the same 3–10 measures"},{"path":"components/_cards/charts-15-distribution.card.html","group":"Charts","viewport":"700x760","name":"Histogram & Box Plot","subtitle":"Distribution of one measure · spread and outliers per group"},{"path":"components/_cards/charts-16-pareto.card.html","group":"Charts","viewport":"700x460","name":"Pareto","subtitle":"Sorted bars + cumulative % line — the vital few vs. the trivial many"},{"path":"components/_cards/charts-17-candlestick.card.html","group":"Charts","viewport":"700x730","name":"Candlestick","subtitle":"OHLC K-line — volume band, moving average, hollow-body convention"},{"path":"components/_cards/charts-18-sankey.card.html","group":"Charts","viewport":"700x550","name":"Sankey","subtitle":"Flow diagram — columns derived from the graph, never positioned by hand"},{"path":"components/_cards/charts-19-chord.card.html","group":"Charts","viewport":"700x910","name":"Chord","subtitle":"Circular Sankey — many-to-many, cyclical flows on one ring"},{"path":"components/_cards/charts-20-sunburst.card.html","group":"Charts","viewport":"700x540","name":"Sunburst","subtitle":"Hierarchy as concentric rings — click a ring to zoom, center to go back"},{"path":"components/_cards/charts-21-worldmap.card.html","group":"Charts","viewport":"700x870","name":"World Map","subtitle":"Choropleth on real Natural Earth geometry — hover, select, diverging scale"},{"path":"components/_cards/charts-22-gantt.card.html","group":"Charts","viewport":"700x590","name":"Gantt","subtitle":"Timeline of tasks — weekend tinting, progress fill, today marker, milestones"},{"path":"components/_cards/charts-23-bar3d.card.html","group":"Charts","viewport":"700x790","name":"3D Bars","subtitle":"Cuboid bars, grouped depth rows or stacked — drag to orbit, hover to read"},{"path":"components/_cards/charts-24-pie3d.card.html","group":"Charts","viewport":"700x1270","name":"3D Pie & Donut","subtitle":"Extruded and stepped-stairs slices — drag spins in place, hover lifts a slice"},{"path":"components/_cards/charts-25-surface3d.card.html","group":"Charts","viewport":"700x480","name":"3D Surface","subtitle":"z = f(x,y) heat-shaded grid — drag to orbit, hover reads a cell"},{"path":"components/_cards/charts-26-scatter3d.card.html","group":"Charts","viewport":"700x520","name":"3D Scatter","subtitle":"Points in an orientation cube — drag to orbit, hover pins x·y·z"},{"path":"components/_cards/charts-27-ribbon3d.card.html","group":"Charts","viewport":"700x510","name":"3D Ribbon","subtitle":"Series as extruded depth bands — the classic 3D area · drag to orbit"},{"path":"components/_cards/charts-99-fullpage.card.html","group":"Charts","viewport":"1512x2210","name":"Full-Page Analytics","subtitle":"Reference layout only — every chart type composed into one screen"}]
|
|
45
|
+
</script>
|
|
46
|
+
<script>
|
|
47
|
+
// The card list is inlined above so this page works when opened straight from disk
|
|
48
|
+
// (file:// blocks fetch). When served over http it re-reads _ds_manifest.json instead,
|
|
49
|
+
// so a rebuilt design system shows up without regenerating this file.
|
|
50
|
+
function render(m) {
|
|
51
|
+
var cards = (m.cards || []).filter(function (c) { return !/\/_dir\.card\.html$/.test(c.path || ''); }); // per-directory thumbnail stubs are tooling, not site content
|
|
52
|
+
var scalers = [];
|
|
53
|
+
var groups = {};
|
|
54
|
+
cards.forEach(function (c) {
|
|
55
|
+
var g = c.group || 'Other';
|
|
56
|
+
(groups[g] = groups[g] || []).push(c);
|
|
57
|
+
});
|
|
58
|
+
var order = ['Components', 'Icons', 'Colors', 'Type', 'Spacing', 'Foundations', 'Brand'];
|
|
59
|
+
var names = Object.keys(groups).sort(function (a, b) {
|
|
60
|
+
var ia = order.indexOf(a), ib = order.indexOf(b);
|
|
61
|
+
return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib) || a.localeCompare(b);
|
|
62
|
+
});
|
|
63
|
+
var out = document.getElementById('out');
|
|
64
|
+
out.innerHTML = '';
|
|
65
|
+
names.forEach(function (g) {
|
|
66
|
+
var h = document.createElement('h2');
|
|
67
|
+
h.textContent = g + ' · ' + groups[g].length;
|
|
68
|
+
out.appendChild(h);
|
|
69
|
+
var grid = document.createElement('div');
|
|
70
|
+
grid.className = 'grid';
|
|
71
|
+
grid.dataset.group = g;
|
|
72
|
+
groups[g].sort(function (a, b) { return (a.path || '').localeCompare(b.path || ''); }).forEach(function (c) {
|
|
73
|
+
var vp = (c.viewport || '700x400').split('x');
|
|
74
|
+
var w = +vp[0] || 700, hgt = +vp[1] || 400;
|
|
75
|
+
var el = document.createElement('div');
|
|
76
|
+
el.className = 'card';
|
|
77
|
+
el.dataset.name = ((c.name || '') + ' ' + (c.subtitle || '') + ' ' + g).toLowerCase();
|
|
78
|
+
el.innerHTML = '<figure></figure><div class="meta"><b>' + (c.name || c.path) + '</b>' +
|
|
79
|
+
(c.subtitle ? '<span>' + c.subtitle + '</span>' : '') +
|
|
80
|
+
'<a href="' + c.path + '" target="_blank">open full size ↗</a></div>';
|
|
81
|
+
var fig = el.querySelector('figure');
|
|
82
|
+
var f = document.createElement('iframe');
|
|
83
|
+
f.loading = 'lazy';
|
|
84
|
+
f.src = c.path;
|
|
85
|
+
f.width = w;
|
|
86
|
+
f.height = hgt;
|
|
87
|
+
fig.appendChild(f);
|
|
88
|
+
// Thumbnails scale to fit the fixed 210px tile; "open full size" shows them at 1:1.
|
|
89
|
+
// The figure height never changes, so every meta block starts at the same y in a row.
|
|
90
|
+
scalers.push(function () {
|
|
91
|
+
var s = fig.clientWidth / w;
|
|
92
|
+
f.style.transform = 'scale(' + s + ')';
|
|
93
|
+
});
|
|
94
|
+
grid.appendChild(el);
|
|
95
|
+
});
|
|
96
|
+
out.appendChild(grid);
|
|
97
|
+
});
|
|
98
|
+
document.getElementById('count').textContent = cards.length + ' cards';
|
|
99
|
+
|
|
100
|
+
function rescale() { scalers.forEach(function (fn) { fn(); }); }
|
|
101
|
+
requestAnimationFrame(rescale);
|
|
102
|
+
if (window.ResizeObserver) new ResizeObserver(rescale).observe(out);
|
|
103
|
+
else addEventListener('resize', rescale);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var snapshot = { cards: JSON.parse(document.getElementById('cards').textContent) };
|
|
107
|
+
if (location.protocol === 'file:') {
|
|
108
|
+
render(snapshot);
|
|
109
|
+
} else {
|
|
110
|
+
fetch('_ds_manifest.json')
|
|
111
|
+
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
112
|
+
.then(render)
|
|
113
|
+
.catch(function () { render(snapshot); });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var q = document.getElementById('q');
|
|
117
|
+
q.addEventListener('input', function () {
|
|
118
|
+
var t = q.value.trim().toLowerCase();
|
|
119
|
+
var shown = 0;
|
|
120
|
+
document.querySelectorAll('.card').forEach(function (el) {
|
|
121
|
+
var hit = !t || el.dataset.name.indexOf(t) > -1;
|
|
122
|
+
el.style.display = hit ? '' : 'none';
|
|
123
|
+
if (hit) shown++;
|
|
124
|
+
});
|
|
125
|
+
document.querySelectorAll('.grid').forEach(function (grid) {
|
|
126
|
+
var any = Array.prototype.some.call(grid.children, function (c) { return c.style.display !== 'none'; });
|
|
127
|
+
grid.style.display = any ? '' : 'none';
|
|
128
|
+
if (grid.previousElementSibling) grid.previousElementSibling.style.display = any ? '' : 'none';
|
|
129
|
+
});
|
|
130
|
+
document.getElementById('count').textContent = shown + (t ? ' of ' + document.querySelectorAll('.card').length : '') + ' cards';
|
|
131
|
+
});
|
|
132
|
+
</script>
|
|
133
|
+
</body></html>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* Shared helpers for the colour specimen cards — resolves live token values from the
|
|
2
|
+
loaded stylesheet and computes WCAG contrast, so every label is measured, not typed. */
|
|
3
|
+
window.KBColor = (function () {
|
|
4
|
+
function parse(str) {
|
|
5
|
+
var m = String(str).match(/rgba?\(([^)]+)\)/);
|
|
6
|
+
if (!m) return null;
|
|
7
|
+
var p = m[1].split(/[,\s/]+/).filter(Boolean).map(Number);
|
|
8
|
+
return { r: p[0], g: p[1], b: p[2], a: p.length > 3 ? p[3] : 1 };
|
|
9
|
+
}
|
|
10
|
+
function hex(str) {
|
|
11
|
+
var c = parse(str);
|
|
12
|
+
if (!c) return String(str);
|
|
13
|
+
function h(n) { return ('0' + Math.round(n).toString(16)).slice(-2); }
|
|
14
|
+
return '#' + h(c.r) + h(c.g) + h(c.b);
|
|
15
|
+
}
|
|
16
|
+
function lum(c) {
|
|
17
|
+
var v = [c.r, c.g, c.b].map(function (x) { x /= 255; return x <= .03928 ? x / 12.92 : Math.pow((x + .055) / 1.055, 2.4); });
|
|
18
|
+
return .2126 * v[0] + .7152 * v[1] + .0722 * v[2];
|
|
19
|
+
}
|
|
20
|
+
/** Contrast ratio between two CSS colour strings (alpha is flattened onto `over`). */
|
|
21
|
+
function ratio(fg, bg, over) {
|
|
22
|
+
var a = parse(fg), b = parse(bg);
|
|
23
|
+
if (!a || !b) return 0;
|
|
24
|
+
if (b.a < 1 && over) { var o = parse(over); b = { r: b.r * b.a + o.r * (1 - b.a), g: b.g * b.a + o.g * (1 - b.a), b: b.b * b.a + o.b * (1 - b.a), a: 1 }; }
|
|
25
|
+
if (a.a < 1) { a = { r: a.r * a.a + b.r * (1 - a.a), g: a.g * a.a + b.g * (1 - a.a), b: a.b * a.a + b.b * (1 - a.a), a: 1 }; }
|
|
26
|
+
var l1 = lum(a), l2 = lum(b);
|
|
27
|
+
return (Math.max(l1, l2) + .05) / (Math.min(l1, l2) + .05);
|
|
28
|
+
}
|
|
29
|
+
/** Resolved computed value of a custom property, e.g. token('--blue-600') → "rgb(21, 93, 252)". */
|
|
30
|
+
function token(name, el) {
|
|
31
|
+
var probe = document.createElement('span');
|
|
32
|
+
probe.style.color = 'var(' + name + ')';
|
|
33
|
+
probe.style.display = 'none';
|
|
34
|
+
(el || document.body).appendChild(probe);
|
|
35
|
+
var v = getComputedStyle(probe).color;
|
|
36
|
+
probe.remove();
|
|
37
|
+
return v;
|
|
38
|
+
}
|
|
39
|
+
/** "4.7" plus a grade for normal-size text: AAA ≥7, AA ≥4.5, AA18 ≥3, else Fail. */
|
|
40
|
+
function grade(r) { return r >= 7 ? 'AAA' : r >= 4.5 ? 'AA' : r >= 3 ? 'AA 18px+' : 'Fail'; }
|
|
41
|
+
function best(bg) {
|
|
42
|
+
var w = ratio('rgb(255,255,255)', bg), b = ratio('rgb(10,10,10)', bg);
|
|
43
|
+
return w >= b ? { color: '#ffffff', ratio: w } : { color: '#0a0a0a', ratio: b };
|
|
44
|
+
}
|
|
45
|
+
return { parse: parse, hex: hex, ratio: ratio, token: token, grade: grade, best: best, fix: function (n) { return (Math.round(n * 10) / 10).toFixed(1); } };
|
|
46
|
+
})();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- @dsCard group="Brand" viewport="700x170" name="Logo & Wordmark" subtitle="Blue cube bitmap + Inter 600 wordmark, −0.609px tracking" -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html><head><meta charset="utf-8"><script src="../components/_shared/frame-theme.js"></script><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../tokens/hover.css">
|
|
4
|
+
<style>body{margin:0;padding:16px;font-family:var(--font-sans);background:var(--bg-surface);display:flex;gap:12px;box-sizing:border-box}.p{flex:1;border:1px solid var(--border-card);border-radius:10px;display:flex;align-items:center;justify-content:center;gap:10px;height:128px}.p.dark{background:var(--neutral-950)}.wm{font-weight:600;font-size:15px;letter-spacing:-.609px;color:var(--text-primary)}.dark .wm{color:#fff}.cap{position:absolute;font-size:var(--text-xs)}</style></head>
|
|
5
|
+
<body>
|
|
6
|
+
<div class="p"><img src="../assets/keybricks-logo.png" width="24" height="24" alt=""><span class="wm">Keybricks</span></div>
|
|
7
|
+
<div class="p"><img src="../assets/keybricks-logo.png" width="48" height="48" alt=""></div>
|
|
8
|
+
<div class="p dark"><img src="../assets/keybricks-logo.png" width="24" height="24" alt=""><span class="wm">Keybricks</span></div>
|
|
9
|
+
</body></html>
|