@ds-mo/ui 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/components/ds-bar-nav-action.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-nav.js.map +1 -1
- package/dist/components/ds-breadcrumb.js +1 -1
- package/dist/components/ds-button-group.js +1 -1
- package/dist/components/ds-button.js +1 -1
- package/dist/components/ds-card.js +1 -1
- package/dist/components/ds-card.js.map +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-divider.js +1 -1
- package/dist/components/ds-divider.js.map +1 -1
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-empty-state.js.map +1 -1
- package/dist/components/ds-fade.js +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-header.js +1 -1
- package/dist/components/ds-header.js.map +1 -1
- package/dist/components/ds-icon.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/{ds-label-wrap.d.ts → ds-tab-group-nav.d.ts} +4 -4
- package/dist/components/ds-tab-group-nav.js +2 -0
- package/dist/components/ds-tab-group-nav.js.map +1 -0
- package/dist/components/ds-tab-group.js +1 -1
- package/dist/components/ds-tab-group.js.map +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-table.js.map +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-toast-provider.js +1 -1
- package/dist/components/ds-toggle-button-group.js +1 -1
- package/dist/components/ds-toggle-button.js +1 -1
- package/dist/components/ds-toggle.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/p-BQq26pt9.js +2 -0
- package/dist/components/p-BQq26pt9.js.map +1 -0
- package/dist/components/{p-DouEVPmi.js → p-CE5zeV1w.js} +2 -2
- package/dist/components/{p-D3CwGoIu.js → p-COsKOwZR.js} +2 -2
- package/dist/components/p-CQQe6VNo.js +2 -0
- package/dist/components/p-CQQe6VNo.js.map +1 -0
- package/dist/components/p-Cj00JkJb.js +2 -0
- package/dist/components/p-Cj00JkJb.js.map +1 -0
- package/dist/components/p-CpbnmEu1.js +2 -0
- package/dist/components/p-CpbnmEu1.js.map +1 -0
- package/dist/components/{p-BvxKLqNx.js → p-H89PzqGU.js} +2 -2
- package/dist/components/{p-BYe8Rfdg.js → p-Vwlmhl4E.js} +2 -2
- package/dist/types/.stencil/BarNav/BarNav.d.ts +7 -2
- package/dist/types/.stencil/BarNav/bar-nav-dom-utils.d.ts +1 -2
- package/dist/types/.stencil/Divider/Divider.d.ts +14 -0
- package/dist/types/.stencil/TabGroupNav/TabGroupNav.d.ts +27 -0
- package/dist/types/.stencil/Text/Text.d.ts +2 -0
- package/dist/types/components.d.ts +125 -63
- package/package.json +37 -7
- package/src/angular/index.ts +1 -1
- package/src/angular/proxies.ts +32 -27
- package/src/react/components.ts +45 -0
- package/src/react/ds-accordion.ts +27 -0
- package/src/react/ds-badge.ts +26 -0
- package/src/react/ds-banner.ts +27 -0
- package/src/react/ds-bar-nav-action.ts +27 -0
- package/src/react/ds-bar-nav.ts +33 -0
- package/src/react/ds-breadcrumb.ts +27 -0
- package/src/react/ds-button-group.ts +27 -0
- package/src/react/ds-button.ts +35 -0
- package/src/react/ds-card.ts +26 -0
- package/src/react/ds-checkbox.ts +27 -0
- package/src/react/ds-divider.ts +26 -0
- package/src/react/ds-empty-state.ts +26 -0
- package/src/react/ds-fade.ts +26 -0
- package/src/react/ds-field.ts +26 -0
- package/src/react/ds-header.ts +26 -0
- package/src/react/ds-icon.ts +26 -0
- package/src/react/ds-input.ts +33 -0
- package/src/react/ds-loader.ts +26 -0
- package/src/react/ds-menu.ts +33 -0
- package/src/react/ds-modal.ts +27 -0
- package/src/react/ds-pagination.ts +27 -0
- package/src/react/ds-panel-nav.ts +37 -0
- package/src/react/ds-radio-group.ts +27 -0
- package/src/react/ds-scrollbar.ts +26 -0
- package/src/react/ds-select.ts +27 -0
- package/src/react/ds-sidebar.ts +33 -0
- package/src/react/ds-skeleton.ts +26 -0
- package/src/react/ds-slider.ts +27 -0
- package/src/react/ds-surface.ts +26 -0
- package/src/react/ds-tab-group-nav.ts +27 -0
- package/src/react/ds-tab-group.ts +27 -0
- package/src/react/ds-table.ts +35 -0
- package/src/react/ds-tag.ts +35 -0
- package/src/react/ds-text.ts +26 -0
- package/src/react/ds-toast-provider.ts +26 -0
- package/src/react/ds-toggle-button-group.ts +27 -0
- package/src/react/ds-toggle-button.ts +27 -0
- package/src/react/ds-toggle.ts +27 -0
- package/src/react/ds-tooltip.ts +26 -0
- package/src/wc/components/Accordion/Accordion.tsx +144 -0
- package/src/wc/components/Badge/Badge.tsx +34 -0
- package/src/wc/components/Badge/index.ts +1 -0
- package/src/wc/components/Banner/Banner.tsx +148 -0
- package/src/wc/components/BarNav/BarNav.tsx +605 -0
- package/src/wc/components/BarNav/bar-nav-dom-utils.ts +23 -0
- package/src/wc/components/BarNav/bar-nav-tabs-menu-utils.ts +59 -0
- package/src/wc/components/BarNav/bar-nav-utils.ts +76 -0
- package/src/wc/components/BarNav/index.ts +1 -0
- package/src/wc/components/BarNavAction/BarNavAction.tsx +67 -0
- package/src/wc/components/BarNavAction/index.ts +1 -0
- package/src/wc/components/Breadcrumb/Breadcrumb.tsx +61 -0
- package/src/wc/components/Button/Button.tsx +158 -0
- package/src/wc/components/Button/index.ts +1 -0
- package/src/wc/components/ButtonGroup/ButtonGroup.tsx +110 -0
- package/src/wc/components/Card/Card.tsx +26 -0
- package/src/wc/components/Checkbox/Checkbox.tsx +60 -0
- package/src/wc/components/Checkbox/index.ts +1 -0
- package/src/wc/components/Divider/Divider.tsx +107 -0
- package/src/wc/components/Divider/index.ts +2 -0
- package/src/wc/components/EmptyState/EmptyState.tsx +32 -0
- package/src/wc/components/Fade/Fade.tsx +32 -0
- package/src/wc/components/Fade/index.ts +1 -0
- package/src/wc/components/Field/Field.tsx +28 -0
- package/src/wc/components/Header/Header.tsx +33 -0
- package/src/wc/components/Icon/Icon.tsx +112 -0
- package/src/wc/components/Icon/flag-icon-catalog.ts +68 -0
- package/src/wc/components/Icon/index.ts +1 -0
- package/src/wc/components/Icon/system-icon-catalog.ts +798 -0
- package/src/wc/components/Input/Input.tsx +100 -0
- package/src/wc/components/Input/index.ts +1 -0
- package/src/wc/components/Loader/Loader.tsx +47 -0
- package/src/wc/components/Loader/index.ts +1 -0
- package/src/wc/components/Menu/Menu.tsx +340 -0
- package/src/wc/components/Modal/Modal.tsx +197 -0
- package/src/wc/components/Pagination/Pagination.tsx +97 -0
- package/src/wc/components/PanelNav/PanelNav.tsx +666 -0
- package/src/wc/components/PanelNav/index.ts +7 -0
- package/src/wc/components/PanelNav/panel-nav-utils.ts +124 -0
- package/src/wc/components/RadioGroup/RadioGroup.tsx +128 -0
- package/src/wc/components/RadioGroup/index.ts +1 -0
- package/src/wc/components/Scrollbar/Scrollbar.tsx +189 -0
- package/src/wc/components/Select/Select.tsx +195 -0
- package/src/wc/components/Sidebar/Sidebar.tsx +245 -0
- package/src/wc/components/Skeleton/Skeleton.tsx +64 -0
- package/src/wc/components/Skeleton/index.ts +1 -0
- package/src/wc/components/Slider/Slider.tsx +68 -0
- package/src/wc/components/Slider/index.ts +1 -0
- package/src/wc/components/Surface/Surface.tsx +81 -0
- package/src/wc/components/TabGroup/TabGroup.tsx +188 -0
- package/src/wc/components/TabGroup/tab-item-utils.ts +24 -0
- package/src/wc/components/TabGroupNav/TabGroupNav.tsx +188 -0
- package/src/wc/components/Table/Table.tsx +268 -0
- package/src/wc/components/Tag/Tag.tsx +135 -0
- package/src/wc/components/Tag/index.ts +1 -0
- package/src/wc/components/Text/Text.tsx +103 -0
- package/src/wc/components/Text/index.ts +1 -0
- package/src/wc/components/Toast/Toast.tsx +174 -0
- package/src/wc/components/Toast/toast-service.ts +60 -0
- package/src/wc/components/Toggle/Toggle.tsx +43 -0
- package/src/wc/components/Toggle/index.ts +1 -0
- package/src/wc/components/ToggleButton/ToggleButton.tsx +120 -0
- package/src/wc/components/ToggleButtonGroup/ToggleButtonGroup.tsx +118 -0
- package/src/wc/components/Tooltip/Tooltip.tsx +196 -0
- package/src/wc/components.d.ts +3022 -0
- package/dist/components/ds-label-wrap.js +0 -2
- package/dist/components/ds-label-wrap.js.map +0 -1
- package/dist/components/p-C4p1fG67.js +0 -2
- package/dist/components/p-C4p1fG67.js.map +0 -1
- package/dist/components/p-DV8pmt1m.js +0 -2
- package/dist/components/p-DV8pmt1m.js.map +0 -1
- package/dist/components/p-STnE_bmQ.js +0 -2
- package/dist/components/p-STnE_bmQ.js.map +0 -1
- package/dist/types/.stencil/LabelWrap/LabelWrap.d.ts +0 -15
- /package/dist/components/{p-DouEVPmi.js.map → p-CE5zeV1w.js.map} +0 -0
- /package/dist/components/{p-D3CwGoIu.js.map → p-COsKOwZR.js.map} +0 -0
- /package/dist/components/{p-BvxKLqNx.js.map → p-H89PzqGU.js.map} +0 -0
- /package/dist/components/{p-BYe8Rfdg.js.map → p-Vwlmhl4E.js.map} +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getSelectableTabs } from '../TabGroup/tab-item-utils';
|
|
2
|
+
import type { BarNavActionItem, BarNavTab } from './BarNav';
|
|
3
|
+
|
|
4
|
+
export interface BarNavUrlState {
|
|
5
|
+
/** Tab id derived from the URL, or empty when none applies. */
|
|
6
|
+
value: string;
|
|
7
|
+
/** When true, tabs should be hidden (e.g. non-tab child routes like detail pages). */
|
|
8
|
+
hideTabs: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Derive BarNav tab selection from `currentUrl` relative to `basePath`.
|
|
12
|
+
* Mirrors motive-webapp-lab `resolveBarNavState` tab-selection semantics. */
|
|
13
|
+
export function deriveBarNavValueFromUrl(
|
|
14
|
+
path: string,
|
|
15
|
+
basePath: string,
|
|
16
|
+
tabs: BarNavTab[],
|
|
17
|
+
): BarNavUrlState {
|
|
18
|
+
if (!path || !basePath) {
|
|
19
|
+
return { value: '', hideTabs: false };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (path !== basePath && !path.startsWith(`${basePath}/`)) {
|
|
23
|
+
return { value: '', hideTabs: false };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const remainder = path.slice(basePath.length);
|
|
27
|
+
const seg = remainder.startsWith('/') ? remainder.slice(1).split('/')[0] : '';
|
|
28
|
+
const selectableTabs = getSelectableTabs(tabs);
|
|
29
|
+
|
|
30
|
+
if (!seg) {
|
|
31
|
+
return { value: selectableTabs[0]?.id ?? '', hideTabs: false };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const tab = selectableTabs.find(t => t.id === seg);
|
|
35
|
+
if (tab) {
|
|
36
|
+
return { value: tab.id, hideTabs: false };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { value: '', hideTabs: true };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** True when host array/json props have data but resolved internal state is still empty. */
|
|
43
|
+
export function shouldResyncBarNavProps(
|
|
44
|
+
resolvedTabs: BarNavTab[],
|
|
45
|
+
tabs: BarNavTab[],
|
|
46
|
+
tabsJson: string,
|
|
47
|
+
resolvedActions: BarNavActionItem[],
|
|
48
|
+
actions: BarNavActionItem[],
|
|
49
|
+
actionsJson: string,
|
|
50
|
+
): boolean {
|
|
51
|
+
const tabsIncoming = tabsJson
|
|
52
|
+
? parseJsonArrayProp(tabsJson, [])
|
|
53
|
+
: (tabs ?? []);
|
|
54
|
+
const actionsIncoming = actionsJson
|
|
55
|
+
? parseJsonArrayProp(actionsJson, [])
|
|
56
|
+
: (actions ?? []);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
(resolvedTabs.length === 0 && tabsIncoming.length > 0) ||
|
|
60
|
+
(resolvedActions.length === 0 && actionsIncoming.length > 0)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Parse tabs/actions from either a JSON attribute string or a JS property array. */
|
|
65
|
+
export function parseJsonArrayProp<T>(value: string | T[] | undefined, fallback: T[]): T[] {
|
|
66
|
+
if (Array.isArray(value)) return value;
|
|
67
|
+
if (typeof value === 'string' && value !== '') {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(value);
|
|
70
|
+
return Array.isArray(parsed) ? parsed : fallback;
|
|
71
|
+
} catch {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { BarNavBackground, BarNavTab, BarNavActionItem } from './BarNav';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';
|
|
2
|
+
|
|
3
|
+
export type BarNavActionBackground = 'faint' | 'medium' | 'bold' | 'strong' | 'always-dark';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
tag: 'ds-bar-nav-action',
|
|
7
|
+
styleUrl: 'BarNavAction.css',
|
|
8
|
+
scoped: true,
|
|
9
|
+
})
|
|
10
|
+
export class BarNavAction {
|
|
11
|
+
/** Icon name passed to <ds-icon>. */
|
|
12
|
+
@Prop() icon: string = '';
|
|
13
|
+
|
|
14
|
+
/** Controlled toggle/selected state. */
|
|
15
|
+
@Prop() selected: boolean = false;
|
|
16
|
+
|
|
17
|
+
/** Show a notification dot (brand colour) at the top-right of the icon. */
|
|
18
|
+
@Prop() dot: boolean = false;
|
|
19
|
+
|
|
20
|
+
/** Disables interaction. */
|
|
21
|
+
@Prop() inactive: boolean = false;
|
|
22
|
+
|
|
23
|
+
@Prop({ attribute: 'aria-label' }) ariaLabel: string = 'action';
|
|
24
|
+
|
|
25
|
+
/** Parent surface context — adjusts hover/press colours for coloured backgrounds. */
|
|
26
|
+
@Prop() background: BarNavActionBackground | undefined;
|
|
27
|
+
|
|
28
|
+
/** Emits the new selected value (!selected) on click. */
|
|
29
|
+
@Event() dsChange!: EventEmitter<boolean>;
|
|
30
|
+
|
|
31
|
+
private handleClick = () => {
|
|
32
|
+
if (this.inactive) return;
|
|
33
|
+
this.dsChange.emit(!this.selected);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
const bg = this.background;
|
|
38
|
+
|
|
39
|
+
const cls: Record<string, boolean> = {
|
|
40
|
+
'action-btn': true,
|
|
41
|
+
selected: this.selected,
|
|
42
|
+
inactive: this.inactive,
|
|
43
|
+
onMedium: bg === 'medium',
|
|
44
|
+
onBold: bg === 'bold',
|
|
45
|
+
onStrong: bg === 'strong',
|
|
46
|
+
onAlwaysDark: bg === 'always-dark',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Host>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
class={cls}
|
|
54
|
+
disabled={this.inactive}
|
|
55
|
+
aria-pressed={this.selected}
|
|
56
|
+
aria-label={this.ariaLabel}
|
|
57
|
+
onClick={this.handleClick}
|
|
58
|
+
>
|
|
59
|
+
<span class="action-btn__icon-wrap">
|
|
60
|
+
<ds-icon name={this.icon} size="md" color="inherit" />
|
|
61
|
+
{this.dot && <span class="action-btn__dot" aria-hidden="true" />}
|
|
62
|
+
</span>
|
|
63
|
+
</button>
|
|
64
|
+
</Host>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { BarNavActionBackground } from './BarNavAction';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';
|
|
2
|
+
|
|
3
|
+
export interface BreadcrumbItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@Component({
|
|
9
|
+
tag: 'ds-breadcrumb',
|
|
10
|
+
styleUrl: 'Breadcrumb.css',
|
|
11
|
+
scoped: true,
|
|
12
|
+
})
|
|
13
|
+
export class Breadcrumb {
|
|
14
|
+
@Prop() items: BreadcrumbItem[] = [];
|
|
15
|
+
@Prop() separator: string = '/';
|
|
16
|
+
|
|
17
|
+
@Event() dsNavigate!: EventEmitter<{ item: BreadcrumbItem; index: number }>;
|
|
18
|
+
|
|
19
|
+
private handleClick(item: BreadcrumbItem, index: number, e: MouseEvent) {
|
|
20
|
+
if (!item.href) {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
}
|
|
23
|
+
this.dsNavigate.emit({ item, index });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
return (
|
|
28
|
+
<Host>
|
|
29
|
+
<nav aria-label="Breadcrumb" class="breadcrumb">
|
|
30
|
+
<ol class="list">
|
|
31
|
+
{this.items.map((item, i) => {
|
|
32
|
+
const isLast = i === this.items.length - 1;
|
|
33
|
+
return (
|
|
34
|
+
<li class="item" key={i}>
|
|
35
|
+
{isLast ? (
|
|
36
|
+
<span class="text-body-medium-emphasis current" aria-current="page">
|
|
37
|
+
{item.label}
|
|
38
|
+
</span>
|
|
39
|
+
) : (
|
|
40
|
+
<span class="item-inner">
|
|
41
|
+
<a
|
|
42
|
+
href={item.href ?? '#'}
|
|
43
|
+
class="link text-body-medium"
|
|
44
|
+
onClick={e => this.handleClick(item, i, e)}
|
|
45
|
+
>
|
|
46
|
+
{item.label}
|
|
47
|
+
</a>
|
|
48
|
+
<span class="separator text-body-small" aria-hidden="true">
|
|
49
|
+
{this.separator}
|
|
50
|
+
</span>
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
</li>
|
|
54
|
+
);
|
|
55
|
+
})}
|
|
56
|
+
</ol>
|
|
57
|
+
</nav>
|
|
58
|
+
</Host>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Component, Prop, Event, EventEmitter, Element, State, h, Host } from '@stencil/core';
|
|
2
|
+
|
|
3
|
+
export type ButtonVariant = 'primary' | 'secondary';
|
|
4
|
+
export type ButtonElevation = 'none' | 'flat' | 'elevated' | 'floating';
|
|
5
|
+
export type ButtonIntent = 'none' | 'neutral' | 'brand' | 'ai' | 'negative' | 'warning' | 'caution' | 'positive';
|
|
6
|
+
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
7
|
+
export type ButtonContrast = 'strong' | 'bold' | 'medium' | 'faint';
|
|
8
|
+
export type ButtonBackground = 'faint' | 'medium' | 'bold' | 'strong' | 'always-dark';
|
|
9
|
+
|
|
10
|
+
const ICON_SIZE: Record<ButtonSize, number> = { xs: 12, sm: 16, md: 20, lg: 24 };
|
|
11
|
+
const TEXT_CLASS: Record<ButtonSize, string> = {
|
|
12
|
+
xs: 'text-caption-emphasis',
|
|
13
|
+
sm: 'text-body-small-emphasis',
|
|
14
|
+
md: 'text-body-medium-emphasis',
|
|
15
|
+
lg: 'text-body-large-emphasis',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
@Component({
|
|
19
|
+
tag: 'ds-button',
|
|
20
|
+
styleUrl: 'Button.css',
|
|
21
|
+
scoped: true,
|
|
22
|
+
})
|
|
23
|
+
export class Button {
|
|
24
|
+
@Element() el!: HTMLElement;
|
|
25
|
+
|
|
26
|
+
@Prop() variant: ButtonVariant = 'primary';
|
|
27
|
+
@Prop() intent: ButtonIntent = 'brand';
|
|
28
|
+
@Prop() size: ButtonSize = 'md';
|
|
29
|
+
@Prop() label: string | undefined;
|
|
30
|
+
@Prop() rounded: boolean = false;
|
|
31
|
+
@Prop() fullWidth: boolean = false;
|
|
32
|
+
@Prop() width: string | undefined;
|
|
33
|
+
@Prop() contrast: ButtonContrast = 'bold';
|
|
34
|
+
@Prop() background: ButtonBackground | undefined;
|
|
35
|
+
@Prop() dropdown: boolean = false;
|
|
36
|
+
@Prop() badgeCount: number | undefined;
|
|
37
|
+
@Prop() elevation: ButtonElevation | undefined;
|
|
38
|
+
@Prop() loading: boolean = false;
|
|
39
|
+
@Prop() inactive: boolean = false;
|
|
40
|
+
@Prop() href: string | undefined;
|
|
41
|
+
@Prop() target: string | undefined;
|
|
42
|
+
@Prop() type: 'button' | 'submit' | 'reset' = 'button';
|
|
43
|
+
@Prop({ attribute: 'aria-label' }) ariaLabel: string | undefined;
|
|
44
|
+
@Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;
|
|
45
|
+
|
|
46
|
+
@State() private hasIcon: boolean = false;
|
|
47
|
+
|
|
48
|
+
@Event() dsClick!: EventEmitter<MouseEvent>;
|
|
49
|
+
@Event() dsMouseEnter!: EventEmitter<MouseEvent>;
|
|
50
|
+
@Event() dsMouseLeave!: EventEmitter<MouseEvent>;
|
|
51
|
+
|
|
52
|
+
componentWillLoad() {
|
|
53
|
+
this.hasIcon = !!this.el.querySelector('[slot="icon"]');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private handleClick = (e: MouseEvent) => {
|
|
57
|
+
if (this.inactive || this.loading) return;
|
|
58
|
+
this.dsClick.emit(e);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
private get effectiveElevation(): ButtonElevation {
|
|
62
|
+
return this.elevation ?? (this.variant === 'primary' ? 'none' : 'flat');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
render() {
|
|
66
|
+
const Tag = this.href ? 'a' : 'button';
|
|
67
|
+
const size = this.size;
|
|
68
|
+
const iconSize = ICON_SIZE[size];
|
|
69
|
+
const textClass = TEXT_CLASS[size];
|
|
70
|
+
const elev = this.effectiveElevation;
|
|
71
|
+
const hasLabel = !!this.label;
|
|
72
|
+
const showLeading = this.hasIcon || this.loading;
|
|
73
|
+
const isIconOnly = showLeading && !hasLabel;
|
|
74
|
+
const isLabelOnly = hasLabel && !showLeading;
|
|
75
|
+
const isIconAndLabel = showLeading && hasLabel;
|
|
76
|
+
const bg = this.background;
|
|
77
|
+
|
|
78
|
+
const btnCls: Record<string, boolean> = {
|
|
79
|
+
btn: true,
|
|
80
|
+
[this.variant]: true,
|
|
81
|
+
[`intent${cap(this.intent)}`]: true,
|
|
82
|
+
[`size${size.toUpperCase()}`]: size !== 'md',
|
|
83
|
+
rounded: this.rounded,
|
|
84
|
+
inactive: this.inactive || this.loading,
|
|
85
|
+
iconOnly: isIconOnly,
|
|
86
|
+
labelOnly: isLabelOnly,
|
|
87
|
+
iconAndLabel: isIconAndLabel,
|
|
88
|
+
dropdown: this.dropdown,
|
|
89
|
+
[`contrast${cap(this.contrast)}`]: this.variant === 'primary' && this.intent !== 'none' && this.contrast !== 'bold',
|
|
90
|
+
[`elevation${cap(elev)}`]: true,
|
|
91
|
+
onMedium: bg === 'medium',
|
|
92
|
+
onBold: bg === 'bold',
|
|
93
|
+
onStrong: bg === 'strong',
|
|
94
|
+
onAlwaysDark: bg === 'always-dark',
|
|
95
|
+
fullWidth: this.fullWidth,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const extraProps: Record<string, unknown> = {};
|
|
99
|
+
if (Tag === 'button') {
|
|
100
|
+
extraProps.type = this.type;
|
|
101
|
+
extraProps.disabled = this.inactive || this.loading;
|
|
102
|
+
}
|
|
103
|
+
if (this.href) extraProps.href = this.href;
|
|
104
|
+
if (this.target) extraProps.target = this.target;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Host class={{ 'btn-host': true, 'btn-host--full': this.fullWidth }}>
|
|
108
|
+
<Tag
|
|
109
|
+
class={btnCls}
|
|
110
|
+
style={this.width ? { width: this.width } : undefined}
|
|
111
|
+
aria-label={this.ariaLabel}
|
|
112
|
+
aria-labelledby={this.ariaLabelledby}
|
|
113
|
+
aria-disabled={this.inactive || this.loading || undefined}
|
|
114
|
+
aria-busy={this.loading || undefined}
|
|
115
|
+
onClick={this.handleClick}
|
|
116
|
+
onMouseEnter={(e: MouseEvent) => !this.inactive && !this.loading && this.dsMouseEnter.emit(e)}
|
|
117
|
+
onMouseLeave={(e: MouseEvent) => !this.inactive && !this.loading && this.dsMouseLeave.emit(e)}
|
|
118
|
+
{...extraProps}
|
|
119
|
+
>
|
|
120
|
+
{/* Leading icon / loader */}
|
|
121
|
+
<span class="btn__icon" style={{ fontSize: `${iconSize}px`, display: showLeading ? undefined : 'none' }}>
|
|
122
|
+
{this.loading
|
|
123
|
+
? <ds-loader size={iconSize} />
|
|
124
|
+
: <slot name="icon" />
|
|
125
|
+
}
|
|
126
|
+
</span>
|
|
127
|
+
{/* Label */}
|
|
128
|
+
{hasLabel && (
|
|
129
|
+
<span class={`btn__label ${textClass}`}>{this.label}</span>
|
|
130
|
+
)}
|
|
131
|
+
{/* Trailing chevron */}
|
|
132
|
+
{this.dropdown && (
|
|
133
|
+
<svg
|
|
134
|
+
class="btn__chevron"
|
|
135
|
+
width={iconSize}
|
|
136
|
+
height={iconSize}
|
|
137
|
+
viewBox="0 0 20 20"
|
|
138
|
+
fill="none"
|
|
139
|
+
aria-hidden="true"
|
|
140
|
+
>
|
|
141
|
+
<path d="M5 7.5L10 12.5L15 7.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
142
|
+
</svg>
|
|
143
|
+
)}
|
|
144
|
+
{/* Badge */}
|
|
145
|
+
{this.badgeCount != null && this.badgeCount > 0 && (
|
|
146
|
+
<span class="btn__badge">
|
|
147
|
+
{this.badgeCount > 9 ? '+' : this.badgeCount}
|
|
148
|
+
</span>
|
|
149
|
+
)}
|
|
150
|
+
</Tag>
|
|
151
|
+
</Host>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function cap(s: string) {
|
|
157
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
158
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { ButtonVariant, ButtonElevation, ButtonIntent, ButtonSize, ButtonContrast, ButtonBackground } from './Button';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';
|
|
2
|
+
|
|
3
|
+
export type ButtonGroupElevation = 'none' | 'flat' | 'elevated' | 'floating';
|
|
4
|
+
export type ButtonGroupSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
5
|
+
|
|
6
|
+
export interface ButtonGroupItem {
|
|
7
|
+
label?: string;
|
|
8
|
+
/** Icon name for <ds-icon>. Set via JS property. */
|
|
9
|
+
icon?: string;
|
|
10
|
+
variant?: 'primary' | 'secondary';
|
|
11
|
+
intent?: 'none' | 'neutral' | 'brand' | 'negative' | 'warning' | 'positive';
|
|
12
|
+
inactive?: boolean;
|
|
13
|
+
href?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
tag: 'ds-button-group',
|
|
18
|
+
styleUrl: 'ButtonGroup.css',
|
|
19
|
+
scoped: true,
|
|
20
|
+
})
|
|
21
|
+
export class ButtonGroup {
|
|
22
|
+
/**
|
|
23
|
+
* Array of button descriptors. Set via JS property.
|
|
24
|
+
* @example el.items = [{ label: 'Filter', icon: 'Filter' }, { label: 'Sort' }];
|
|
25
|
+
*/
|
|
26
|
+
@Prop() items: ButtonGroupItem[] = [];
|
|
27
|
+
|
|
28
|
+
/** Chrome level for the group container. */
|
|
29
|
+
@Prop() elevation: ButtonGroupElevation = 'flat';
|
|
30
|
+
|
|
31
|
+
/** Size passed to each ds-button. */
|
|
32
|
+
@Prop() size: ButtonGroupSize = 'md';
|
|
33
|
+
|
|
34
|
+
/** Pill shape for group + buttons. */
|
|
35
|
+
@Prop() rounded: boolean = false;
|
|
36
|
+
|
|
37
|
+
/** Emits the index of the clicked item. */
|
|
38
|
+
@Event() dsClick!: EventEmitter<number>;
|
|
39
|
+
|
|
40
|
+
render() {
|
|
41
|
+
const items = this.items;
|
|
42
|
+
const count = items.length;
|
|
43
|
+
const elev = this.elevation;
|
|
44
|
+
const elevKey = elev.charAt(0).toUpperCase() + elev.slice(1);
|
|
45
|
+
const isGhost = elev === 'none';
|
|
46
|
+
|
|
47
|
+
const groupCls: Record<string, boolean> = {
|
|
48
|
+
group: true,
|
|
49
|
+
[`group${elevKey}`]: true,
|
|
50
|
+
groupRounded: this.rounded,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Host>
|
|
55
|
+
<div class={groupCls} role="group">
|
|
56
|
+
{items.map((item, i) => {
|
|
57
|
+
const isFirst = i === 0;
|
|
58
|
+
const isLast = i === count - 1;
|
|
59
|
+
const isMid = !isFirst && !isLast;
|
|
60
|
+
const variant = item.variant ?? 'secondary';
|
|
61
|
+
const isIconOnly = !!item.icon && !item.label;
|
|
62
|
+
const needsRoundedCorrection = this.rounded && !isIconOnly && this.size !== 'xs';
|
|
63
|
+
const sizeKey = this.size.toUpperCase() as 'MD' | 'SM' | 'LG' | 'XS';
|
|
64
|
+
|
|
65
|
+
const itemCls: Record<string, boolean> = {
|
|
66
|
+
item: true,
|
|
67
|
+
first: isFirst,
|
|
68
|
+
last: isLast,
|
|
69
|
+
middle: isMid,
|
|
70
|
+
itemShort: isGhost && variant === 'secondary',
|
|
71
|
+
[`roundedFirst${sizeKey}`]: !!(needsRoundedCorrection && isFirst),
|
|
72
|
+
[`roundedLast${sizeKey}`]: !!(needsRoundedCorrection && isLast),
|
|
73
|
+
[`roundedMid${sizeKey}`]: !!(needsRoundedCorrection && isMid),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const dividerCls: Record<string, boolean> = {
|
|
77
|
+
divider: true,
|
|
78
|
+
dividerGhost: isGhost,
|
|
79
|
+
dividerGhostXS: isGhost && this.size === 'xs',
|
|
80
|
+
dividerGhostSM: isGhost && this.size === 'sm',
|
|
81
|
+
dividerGhostLG: isGhost && this.size === 'lg',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
i > 0 && (
|
|
86
|
+
<div class={dividerCls} aria-hidden="true" />
|
|
87
|
+
),
|
|
88
|
+
<ds-button
|
|
89
|
+
class={itemCls}
|
|
90
|
+
variant={variant}
|
|
91
|
+
intent={item.intent ?? 'none'}
|
|
92
|
+
elevation={elev}
|
|
93
|
+
size={this.size}
|
|
94
|
+
label={item.label}
|
|
95
|
+
rounded={this.rounded}
|
|
96
|
+
inactive={item.inactive}
|
|
97
|
+
href={item.href}
|
|
98
|
+
onDsClick={() => this.dsClick.emit(i)}
|
|
99
|
+
>
|
|
100
|
+
{item.icon && (
|
|
101
|
+
<ds-icon slot="icon" name={item.icon} />
|
|
102
|
+
)}
|
|
103
|
+
</ds-button>,
|
|
104
|
+
];
|
|
105
|
+
})}
|
|
106
|
+
</div>
|
|
107
|
+
</Host>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Component, Prop, h, Host } from '@stencil/core';
|
|
2
|
+
|
|
3
|
+
export type CardElevation = 'flat' | 'elevated' | 'floating';
|
|
4
|
+
export type CardRadius = 'sm' | 'md' | 'lg' | 'xl';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
tag: 'ds-card',
|
|
8
|
+
styleUrl: 'Card.css',
|
|
9
|
+
scoped: true,
|
|
10
|
+
})
|
|
11
|
+
export class Card {
|
|
12
|
+
@Prop() elevation: CardElevation = 'elevated';
|
|
13
|
+
@Prop() radius: CardRadius = 'lg';
|
|
14
|
+
|
|
15
|
+
render() {
|
|
16
|
+
return (
|
|
17
|
+
<Host class={{ card: true, [`elevation-${this.elevation}`]: true, [`radius-${this.radius}`]: true }}>
|
|
18
|
+
<slot name="header" />
|
|
19
|
+
<div class="body">
|
|
20
|
+
<slot />
|
|
21
|
+
</div>
|
|
22
|
+
<slot name="footer" />
|
|
23
|
+
</Host>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';
|
|
2
|
+
|
|
3
|
+
let idCounter = 0;
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
tag: 'ds-checkbox',
|
|
7
|
+
styleUrl: 'Checkbox.css',
|
|
8
|
+
scoped: true,
|
|
9
|
+
})
|
|
10
|
+
export class Checkbox {
|
|
11
|
+
private labelId = `ds-checkbox-label-${++idCounter}`;
|
|
12
|
+
|
|
13
|
+
@Prop() label!: string;
|
|
14
|
+
@Prop({ mutable: true }) checked: boolean = false;
|
|
15
|
+
@Prop() indeterminate: boolean = false;
|
|
16
|
+
@Prop() inactive: boolean = false;
|
|
17
|
+
|
|
18
|
+
@Event() dsChange!: EventEmitter<boolean>;
|
|
19
|
+
|
|
20
|
+
private handleActivate = () => {
|
|
21
|
+
if (this.inactive) return;
|
|
22
|
+
this.checked = !this.checked;
|
|
23
|
+
this.dsChange.emit(this.checked);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
private handleKeyDown = (e: KeyboardEvent) => {
|
|
27
|
+
if (e.key === ' ' || e.key === 'Enter') {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
this.handleActivate();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
render() {
|
|
34
|
+
const isMarked = this.checked || this.indeterminate;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Host
|
|
38
|
+
role="checkbox"
|
|
39
|
+
aria-checked={this.indeterminate ? 'mixed' : String(this.checked)}
|
|
40
|
+
aria-disabled={this.inactive || undefined}
|
|
41
|
+
aria-labelledby={this.labelId}
|
|
42
|
+
tabIndex={this.inactive ? -1 : 0}
|
|
43
|
+
class={{ checkbox: true, inactive: this.inactive }}
|
|
44
|
+
onClick={this.handleActivate}
|
|
45
|
+
onKeyDown={this.handleKeyDown}
|
|
46
|
+
>
|
|
47
|
+
<span class={{ box: true, 'box--marked': isMarked }}>
|
|
48
|
+
{isMarked && (
|
|
49
|
+
<span class="checkmark" aria-hidden="true">
|
|
50
|
+
{this.indeterminate ? '−' : '✓'}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
</span>
|
|
54
|
+
<span id={this.labelId} class="text-body-medium checkbox__label">
|
|
55
|
+
{this.label}
|
|
56
|
+
</span>
|
|
57
|
+
</Host>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { } from './Checkbox';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Component, Prop, h, Host } from '@stencil/core';
|
|
2
|
+
|
|
3
|
+
export type DividerOrientation = 'horizontal' | 'vertical';
|
|
4
|
+
export type DividerSurface =
|
|
5
|
+
| 'default'
|
|
6
|
+
| 'on-bold-background'
|
|
7
|
+
| 'on-strong-background'
|
|
8
|
+
| 'on-medium-background'
|
|
9
|
+
| 'on-translucent-background'
|
|
10
|
+
| 'navigation'
|
|
11
|
+
| 'media'
|
|
12
|
+
| 'always-dark'
|
|
13
|
+
| 'inverted';
|
|
14
|
+
export type DividerInsetToken =
|
|
15
|
+
| 'space-000'
|
|
16
|
+
| 'space-012'
|
|
17
|
+
| 'space-025'
|
|
18
|
+
| 'space-050'
|
|
19
|
+
| 'space-075'
|
|
20
|
+
| 'space-100'
|
|
21
|
+
| 'space-125'
|
|
22
|
+
| 'space-150'
|
|
23
|
+
| 'space-175'
|
|
24
|
+
| 'space-200'
|
|
25
|
+
| 'space-250'
|
|
26
|
+
| 'space-300'
|
|
27
|
+
| 'space-400'
|
|
28
|
+
| 'space-600'
|
|
29
|
+
| 'space-800';
|
|
30
|
+
export type DividerInset = 'none' | DividerInsetToken | (string & {});
|
|
31
|
+
export type DividerLength = 'auto' | 'full' | (string & {});
|
|
32
|
+
|
|
33
|
+
const INSET_VALUE: Record<'none' | DividerInsetToken, string> = {
|
|
34
|
+
none: '0',
|
|
35
|
+
'space-000': 'var(--dimension-space-000)',
|
|
36
|
+
'space-012': 'var(--dimension-space-012)',
|
|
37
|
+
'space-025': 'var(--dimension-space-025)',
|
|
38
|
+
'space-050': 'var(--dimension-space-050)',
|
|
39
|
+
'space-075': 'var(--dimension-space-075)',
|
|
40
|
+
'space-100': 'var(--dimension-space-100)',
|
|
41
|
+
'space-125': 'var(--dimension-space-125)',
|
|
42
|
+
'space-150': 'var(--dimension-space-150)',
|
|
43
|
+
'space-175': 'var(--dimension-space-175)',
|
|
44
|
+
'space-200': 'var(--dimension-space-200)',
|
|
45
|
+
'space-250': 'var(--dimension-space-250)',
|
|
46
|
+
'space-300': 'var(--dimension-space-300)',
|
|
47
|
+
'space-400': 'var(--dimension-space-400)',
|
|
48
|
+
'space-600': 'var(--dimension-space-600)',
|
|
49
|
+
'space-800': 'var(--dimension-space-800)',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
@Component({
|
|
53
|
+
tag: 'ds-divider',
|
|
54
|
+
styleUrl: 'Divider.css',
|
|
55
|
+
scoped: true,
|
|
56
|
+
})
|
|
57
|
+
export class Divider {
|
|
58
|
+
/** Direction of the divider line. Defaults to 'horizontal'. */
|
|
59
|
+
@Prop() orientation: DividerOrientation = 'horizontal';
|
|
60
|
+
|
|
61
|
+
/** Surface context so the divider uses the right TokoMo divider token. */
|
|
62
|
+
@Prop() surface: DividerSurface = 'default';
|
|
63
|
+
|
|
64
|
+
/** Insets the divider from its start/end edges. Accepts spacing token names or any CSS length. */
|
|
65
|
+
@Prop() inset: DividerInset = 'none';
|
|
66
|
+
|
|
67
|
+
/** Visual line length. Defaults to filling the available axis. Accepts any CSS length. */
|
|
68
|
+
@Prop() length: DividerLength = 'auto';
|
|
69
|
+
|
|
70
|
+
/** Expose the divider as a semantic separator. Visual-only dividers are hidden from assistive tech by default. */
|
|
71
|
+
@Prop() semantic: boolean = false;
|
|
72
|
+
|
|
73
|
+
private resolveInset(): string {
|
|
74
|
+
return Object.prototype.hasOwnProperty.call(INSET_VALUE, this.inset)
|
|
75
|
+
? INSET_VALUE[this.inset as keyof typeof INSET_VALUE]
|
|
76
|
+
: this.inset;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private resolveLength(): string {
|
|
80
|
+
return this.length === 'auto' || this.length === 'full'
|
|
81
|
+
? '100%'
|
|
82
|
+
: this.length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
render() {
|
|
86
|
+
const isVertical = this.orientation === 'vertical';
|
|
87
|
+
const hasCustomLength = this.length !== 'auto' && this.length !== 'full';
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Host
|
|
91
|
+
role={this.semantic ? 'separator' : undefined}
|
|
92
|
+
aria-orientation={this.semantic ? this.orientation : undefined}
|
|
93
|
+
aria-hidden={this.semantic ? undefined : 'true'}
|
|
94
|
+
class={{
|
|
95
|
+
divider: true,
|
|
96
|
+
'divider--vertical': isVertical,
|
|
97
|
+
'divider--custom-length': hasCustomLength,
|
|
98
|
+
[`divider--surface-${this.surface}`]: true,
|
|
99
|
+
}}
|
|
100
|
+
style={{
|
|
101
|
+
'--_divider-inset': this.resolveInset(),
|
|
102
|
+
'--_divider-length': this.resolveLength(),
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|