aeico-components 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/dist/index.cjs +4226 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +4226 -0
- package/dist/index.js.map +1 -0
- package/dist/types/aeico-component.d.ts +8 -0
- package/dist/types/aeico-field.d.ts +132 -0
- package/dist/types/alert/alert.d.ts +49 -0
- package/dist/types/alert/defines.d.ts +3 -0
- package/dist/types/alert/index.d.ts +3 -0
- package/dist/types/badge/badge.d.ts +34 -0
- package/dist/types/badge/defines.d.ts +3 -0
- package/dist/types/badge/index.d.ts +3 -0
- package/dist/types/breadcrumb/breadcrumb-item.d.ts +31 -0
- package/dist/types/breadcrumb/breadcrumb.d.ts +60 -0
- package/dist/types/breadcrumb/defines.d.ts +1 -0
- package/dist/types/breadcrumb/index.d.ts +5 -0
- package/dist/types/button/button.d.ts +60 -0
- package/dist/types/button/defines.d.ts +3 -0
- package/dist/types/button/index.d.ts +3 -0
- package/dist/types/button-group/button-group.d.ts +56 -0
- package/dist/types/button-group/index.d.ts +2 -0
- package/dist/types/card/card.d.ts +19 -0
- package/dist/types/card/defines.d.ts +2 -0
- package/dist/types/card/index.d.ts +3 -0
- package/dist/types/checkbox/checkbox.d.ts +37 -0
- package/dist/types/checkbox/defines.d.ts +1 -0
- package/dist/types/checkbox/index.d.ts +3 -0
- package/dist/types/detail/defines.d.ts +2 -0
- package/dist/types/detail/detail.d.ts +40 -0
- package/dist/types/detail/index.d.ts +3 -0
- package/dist/types/dialog/dialog.d.ts +29 -0
- package/dist/types/dialog/index.d.ts +2 -0
- package/dist/types/divider/divider.d.ts +34 -0
- package/dist/types/divider/index.d.ts +2 -0
- package/dist/types/dropdown/defines.d.ts +1 -0
- package/dist/types/dropdown/dropdown-button.d.ts +60 -0
- package/dist/types/dropdown/dropdown-item.d.ts +56 -0
- package/dist/types/dropdown/dropdown.d.ts +84 -0
- package/dist/types/dropdown/index.d.ts +7 -0
- package/dist/types/icon/defines.d.ts +10 -0
- package/dist/types/icon/icon.d.ts +21 -0
- package/dist/types/icon/index.d.ts +4 -0
- package/dist/types/icon/registry.d.ts +8 -0
- package/dist/types/icon-button/icon-button.d.ts +32 -0
- package/dist/types/icon-button/index.d.ts +2 -0
- package/dist/types/index.d.ts +74 -0
- package/dist/types/navbar/defines.d.ts +2 -0
- package/dist/types/navbar/index.d.ts +3 -0
- package/dist/types/navbar/navbar.d.ts +73 -0
- package/dist/types/radio-group/defines.d.ts +6 -0
- package/dist/types/radio-group/index.d.ts +5 -0
- package/dist/types/radio-group/radio-group.d.ts +41 -0
- package/dist/types/radio-group/radio.d.ts +47 -0
- package/dist/types/select/defines.d.ts +8 -0
- package/dist/types/select/index.d.ts +5 -0
- package/dist/types/select/select-option.d.ts +20 -0
- package/dist/types/select/select.d.ts +60 -0
- package/dist/types/slider/defines.d.ts +31 -0
- package/dist/types/slider/index.d.ts +3 -0
- package/dist/types/slider/slider.d.ts +45 -0
- package/dist/types/switch/index.d.ts +2 -0
- package/dist/types/switch/switch.d.ts +35 -0
- package/dist/types/tabs/defines.d.ts +1 -0
- package/dist/types/tabs/index.d.ts +3 -0
- package/dist/types/tabs/tab-panel.d.ts +11 -0
- package/dist/types/tabs/tab.d.ts +18 -0
- package/dist/types/tabs/tabs.d.ts +24 -0
- package/dist/types/tag/defines.d.ts +3 -0
- package/dist/types/tag/index.d.ts +3 -0
- package/dist/types/tag/tag.d.ts +36 -0
- package/dist/types/text-input/index.d.ts +2 -0
- package/dist/types/text-input/text-input.d.ts +26 -0
- package/dist/types/utils.d.ts +2 -0
- package/package.json +63 -0
- package/src/aeico-component.ts +17 -0
- package/src/aeico-field.ts +228 -0
- package/src/alert/alert.ts +107 -0
- package/src/alert/defines.ts +11 -0
- package/src/alert/index.ts +3 -0
- package/src/badge/badge.ts +62 -0
- package/src/badge/defines.ts +12 -0
- package/src/badge/index.ts +3 -0
- package/src/breadcrumb/breadcrumb-item.ts +61 -0
- package/src/breadcrumb/breadcrumb.ts +138 -0
- package/src/breadcrumb/defines.ts +10 -0
- package/src/breadcrumb/index.ts +5 -0
- package/src/button/button.ts +147 -0
- package/src/button/defines.ts +12 -0
- package/src/button/index.ts +3 -0
- package/src/button-group/button-group.ts +140 -0
- package/src/button-group/index.ts +2 -0
- package/src/card/card.ts +57 -0
- package/src/card/defines.ts +11 -0
- package/src/card/index.ts +3 -0
- package/src/checkbox/checkbox.ts +90 -0
- package/src/checkbox/defines.ts +1 -0
- package/src/checkbox/index.ts +3 -0
- package/src/detail/defines.ts +11 -0
- package/src/detail/detail.ts +122 -0
- package/src/detail/index.ts +3 -0
- package/src/dialog/dialog.ts +149 -0
- package/src/dialog/index.ts +2 -0
- package/src/divider/divider.ts +56 -0
- package/src/divider/index.ts +2 -0
- package/src/dropdown/defines.ts +13 -0
- package/src/dropdown/dropdown-button.ts +130 -0
- package/src/dropdown/dropdown-item.ts +136 -0
- package/src/dropdown/dropdown.ts +211 -0
- package/src/dropdown/index.ts +7 -0
- package/src/icon/defines.ts +21 -0
- package/src/icon/icon.ts +84 -0
- package/src/icon/index.ts +4 -0
- package/src/icon/registry.ts +25 -0
- package/src/icon-button/icon-button.ts +64 -0
- package/src/icon-button/index.ts +2 -0
- package/src/index.ts +85 -0
- package/src/navbar/defines.ts +11 -0
- package/src/navbar/index.ts +3 -0
- package/src/navbar/navbar.ts +162 -0
- package/src/radio-group/defines.ts +5 -0
- package/src/radio-group/index.ts +5 -0
- package/src/radio-group/radio-group.ts +227 -0
- package/src/radio-group/radio.ts +58 -0
- package/src/select/defines.ts +12 -0
- package/src/select/index.ts +5 -0
- package/src/select/select-option.ts +59 -0
- package/src/select/select.ts +387 -0
- package/src/slider/defines.ts +33 -0
- package/src/slider/index.ts +3 -0
- package/src/slider/slider.ts +364 -0
- package/src/styles/color.css +117 -0
- package/src/styles/components/alert.css +104 -0
- package/src/styles/components/badge.css +67 -0
- package/src/styles/components/breadcrumb-item.css +59 -0
- package/src/styles/components/breadcrumb.css +19 -0
- package/src/styles/components/button-group.css +25 -0
- package/src/styles/components/button.css +213 -0
- package/src/styles/components/card.css +64 -0
- package/src/styles/components/checkbox.css +78 -0
- package/src/styles/components/detail.css +127 -0
- package/src/styles/components/dialog.css +103 -0
- package/src/styles/components/divider.css +18 -0
- package/src/styles/components/dropdown-item.css +91 -0
- package/src/styles/components/dropdown.css +179 -0
- package/src/styles/components/icon-button.css +116 -0
- package/src/styles/components/icon.css +29 -0
- package/src/styles/components/navbar.css +250 -0
- package/src/styles/components/radio-group.css +360 -0
- package/src/styles/components/select-option.css +43 -0
- package/src/styles/components/select.css +222 -0
- package/src/styles/components/slider.css +326 -0
- package/src/styles/components/switch.css +117 -0
- package/src/styles/components/tab-panel.css +8 -0
- package/src/styles/components/tab.css +44 -0
- package/src/styles/components/tabs.css +16 -0
- package/src/styles/components/tag.css +107 -0
- package/src/styles/components/text-input.css +110 -0
- package/src/styles/layout.css +43 -0
- package/src/styles/size.css +7 -0
- package/src/styles/variables.css +368 -0
- package/src/switch/index.ts +2 -0
- package/src/switch/switch.ts +88 -0
- package/src/tabs/defines.ts +1 -0
- package/src/tabs/index.ts +3 -0
- package/src/tabs/tab-panel.ts +23 -0
- package/src/tabs/tab.ts +62 -0
- package/src/tabs/tabs.ts +134 -0
- package/src/tag/defines.ts +12 -0
- package/src/tag/index.ts +3 -0
- package/src/tag/tag.ts +85 -0
- package/src/text-input/index.ts +2 -0
- package/src/text-input/text-input.ts +75 -0
- package/src/utils.ts +6 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import AeicoComponent from '../aeico-component';
|
|
2
|
+
import type { InferProps } from 'aeico';
|
|
3
|
+
import { html } from 'aeico';
|
|
4
|
+
import { prop } from 'aeico';
|
|
5
|
+
import style from '../styles/components/dropdown.css?inline';
|
|
6
|
+
import variables from '../styles/variables.css?inline';
|
|
7
|
+
import type { DropdownPlacement } from './defines';
|
|
8
|
+
// Ensure ae-dropdown-item is registered when this module is used
|
|
9
|
+
import './dropdown-item';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Dropdown component — renders a floating menu panel anchored to a trigger slot.
|
|
13
|
+
*
|
|
14
|
+
* The trigger is provided via `slot="trigger"` (typically an `<ae-button>`).
|
|
15
|
+
* Menu items are provided as `<ae-dropdown-item>` default-slot children.
|
|
16
|
+
*
|
|
17
|
+
* Emits:
|
|
18
|
+
* - `open` — when the panel opens
|
|
19
|
+
* - `close` — when the panel closes
|
|
20
|
+
* - `select` — `{ detail: { value, label } }` when a menu item is clicked
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```html
|
|
24
|
+
* <ae-dropdown>
|
|
25
|
+
* <ae-button slot="trigger">Actions</ae-button>
|
|
26
|
+
* <ae-dropdown-item value="edit" icon="edit">Edit</ae-dropdown-item>
|
|
27
|
+
* <ae-dropdown-item value="delete" danger icon="trash">Delete</ae-dropdown-item>
|
|
28
|
+
* </ae-dropdown>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```html
|
|
33
|
+
* <!-- Inside ae-navbar -->
|
|
34
|
+
* <ae-navbar>
|
|
35
|
+
* <a slot="brand" href="/">MyApp</a>
|
|
36
|
+
* <ae-dropdown slot="end">
|
|
37
|
+
* <ae-button slot="trigger" variant="outlined" size="sm">User</ae-button>
|
|
38
|
+
* <ae-dropdown-item href="/profile" icon="user">Profile</ae-dropdown-item>
|
|
39
|
+
* <ae-dropdown-item value="logout" danger>Sign out</ae-dropdown-item>
|
|
40
|
+
* </ae-dropdown>
|
|
41
|
+
* </ae-navbar>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
class Dropdown extends AeicoComponent {
|
|
45
|
+
static tagName = 'dropdown';
|
|
46
|
+
|
|
47
|
+
protected static styles = [variables, style];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Position of the panel relative to the trigger.
|
|
51
|
+
* Defaults to `'bottom-start'` (left-aligned, below trigger).
|
|
52
|
+
*/
|
|
53
|
+
@prop({ type: String })
|
|
54
|
+
accessor placement: DropdownPlacement = 'bottom-start';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the dropdown panel is visible. Reflects as the `open` attribute.
|
|
58
|
+
* Can be used for controlled open/close state.
|
|
59
|
+
*/
|
|
60
|
+
@prop({ type: Boolean })
|
|
61
|
+
accessor open: boolean = false;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* When `true` (default), clicking a menu item automatically closes the panel.
|
|
65
|
+
*/
|
|
66
|
+
@prop({ type: Boolean })
|
|
67
|
+
accessor closeOnSelect: boolean = true;
|
|
68
|
+
|
|
69
|
+
/** Disables the trigger and prevents opening. */
|
|
70
|
+
@prop({ type: Boolean })
|
|
71
|
+
accessor disabled: boolean = false;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Optional label text. When set, `ae-dropdown` renders its own trigger button
|
|
75
|
+
* in the shadow DOM (no `slot="trigger"` needed). Inherits `--ae-navbar-link-*`
|
|
76
|
+
* CSS variables so it automatically matches navbar link styles.
|
|
77
|
+
*/
|
|
78
|
+
@prop({ type: String })
|
|
79
|
+
accessor label: string = '';
|
|
80
|
+
|
|
81
|
+
private _outsideClickHandler: ((e: MouseEvent) => void) | null = null;
|
|
82
|
+
|
|
83
|
+
connectedCallback() {
|
|
84
|
+
super.connectedCallback();
|
|
85
|
+
|
|
86
|
+
this.listen('_item-select', this._handleItemSelect as EventListener);
|
|
87
|
+
this.listen('keydown', this._handleKeydown as EventListener);
|
|
88
|
+
|
|
89
|
+
this._outsideClickHandler = (e: MouseEvent) => {
|
|
90
|
+
if (!this.open) return;
|
|
91
|
+
const path = e.composedPath();
|
|
92
|
+
if (!path.includes(this)) {
|
|
93
|
+
this._closePanel();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
document.addEventListener('click', this._outsideClickHandler);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
disconnectedCallback() {
|
|
100
|
+
super.disconnectedCallback();
|
|
101
|
+
if (this._outsideClickHandler) {
|
|
102
|
+
document.removeEventListener('click', this._outsideClickHandler);
|
|
103
|
+
this._outsideClickHandler = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Opens the dropdown panel. */
|
|
108
|
+
show(): void {
|
|
109
|
+
if (this.disabled || this.open) return;
|
|
110
|
+
this.open = true;
|
|
111
|
+
this.emit('open');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Closes the dropdown panel. */
|
|
115
|
+
hide(): void {
|
|
116
|
+
if (!this.open) return;
|
|
117
|
+
this.open = false;
|
|
118
|
+
this.emit('close');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Toggles the dropdown panel open/closed. */
|
|
122
|
+
toggle(): void {
|
|
123
|
+
if (this.open) {
|
|
124
|
+
this.hide();
|
|
125
|
+
} else {
|
|
126
|
+
this.show();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private _closePanel(): void {
|
|
131
|
+
if (this.open) this.hide();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Called via declarative @click on the trigger-wrapper div inside the shadow DOM.
|
|
135
|
+
// Events from slotted trigger content bubble through the shadow DOM slot path,
|
|
136
|
+
// so this fires for trigger clicks only — not for panel item clicks.
|
|
137
|
+
private _handleTriggerClick = (): void => {
|
|
138
|
+
this.toggle();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
private _handleItemSelect = (e: CustomEvent): void => {
|
|
142
|
+
this.emit('select', { detail: e.detail });
|
|
143
|
+
if (this.closeOnSelect) {
|
|
144
|
+
this._closePanel();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
private _handleKeydown = (e: KeyboardEvent): void => {
|
|
149
|
+
if (e.key === 'Escape' && this.open) {
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
this._closePanel();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
protected render() {
|
|
156
|
+
const placementClass = `placement-${this.placement}`;
|
|
157
|
+
const hasLabel = !!this.label;
|
|
158
|
+
const dir = this.placement.split('-')[0];
|
|
159
|
+
return html(({ div, slot, button, span }) => {
|
|
160
|
+
div(
|
|
161
|
+
{
|
|
162
|
+
className: 'trigger-wrapper',
|
|
163
|
+
'aria-haspopup': 'menu',
|
|
164
|
+
'aria-expanded': String(this.open),
|
|
165
|
+
'@click': this.disabled ? undefined : this._handleTriggerClick,
|
|
166
|
+
},
|
|
167
|
+
() => {
|
|
168
|
+
if (hasLabel) {
|
|
169
|
+
button(
|
|
170
|
+
{
|
|
171
|
+
className: 'trigger-label',
|
|
172
|
+
type: 'button',
|
|
173
|
+
disabled: this.disabled || undefined,
|
|
174
|
+
},
|
|
175
|
+
() => {
|
|
176
|
+
span({ text: this.label });
|
|
177
|
+
span({
|
|
178
|
+
className: `ae-dropdown-arrow ae-dropdown-arrow--${dir}`,
|
|
179
|
+
'aria-hidden': 'true',
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
slot({ name: 'trigger' });
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
div(
|
|
189
|
+
{
|
|
190
|
+
part: 'panel',
|
|
191
|
+
className: { panel: true, open: this.open, [placementClass]: true },
|
|
192
|
+
role: 'menu',
|
|
193
|
+
},
|
|
194
|
+
() => {
|
|
195
|
+
slot();
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Dropdown.register();
|
|
203
|
+
|
|
204
|
+
declare global {
|
|
205
|
+
interface HTMLElementTagNameMap {
|
|
206
|
+
'ae-dropdown': Dropdown;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export default Dropdown;
|
|
211
|
+
export type DropdownProps = InferProps<typeof Dropdown>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default } from './dropdown';
|
|
2
|
+
export { default as DropdownItem } from './dropdown-item';
|
|
3
|
+
export { default as DropdownButton } from './dropdown-button';
|
|
4
|
+
export type { DropdownProps } from './dropdown';
|
|
5
|
+
export type { DropdownItemProps } from './dropdown-item';
|
|
6
|
+
export type { DropdownButtonProps } from './dropdown-button';
|
|
7
|
+
export type { DropdownPlacement } from './defines';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type IconSize = '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
|
|
2
|
+
|
|
3
|
+
export type IconColor =
|
|
4
|
+
| 'default'
|
|
5
|
+
| 'primary'
|
|
6
|
+
| 'secondary'
|
|
7
|
+
| 'success'
|
|
8
|
+
| 'danger'
|
|
9
|
+
| 'warning'
|
|
10
|
+
| 'info';
|
|
11
|
+
|
|
12
|
+
export interface IconDefinition {
|
|
13
|
+
path: string;
|
|
14
|
+
viewBox?: string;
|
|
15
|
+
stroke?: boolean;
|
|
16
|
+
strokeWidth?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const defaultViewBox = '0 0 24 24';
|
|
20
|
+
|
|
21
|
+
export type IconRegistryData = Record<string, string | IconDefinition>;
|
package/src/icon/icon.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { InferProps, Props } from 'aeico';
|
|
2
|
+
import { SVG_NS } from '../utils';
|
|
3
|
+
import AeicoComponent from '../aeico-component';
|
|
4
|
+
import { html } from 'aeico';
|
|
5
|
+
import styleVariables from '../styles/variables.css?inline';
|
|
6
|
+
import sizeCSS from '../styles/size.css?inline';
|
|
7
|
+
import colorCSS from '../styles/color.css?inline';
|
|
8
|
+
import style from '../styles/components/icon.css?inline';
|
|
9
|
+
import type { IconSize, IconColor } from './defines';
|
|
10
|
+
import { defaultViewBox } from './defines';
|
|
11
|
+
import IconRegistry from './registry';
|
|
12
|
+
|
|
13
|
+
class Icon extends AeicoComponent {
|
|
14
|
+
static tagName = 'icon';
|
|
15
|
+
|
|
16
|
+
static props: Props = {
|
|
17
|
+
name: { type: String },
|
|
18
|
+
size: { type: String },
|
|
19
|
+
color: { type: String },
|
|
20
|
+
stroke: { type: Boolean },
|
|
21
|
+
strokeWidth: { type: Number },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
declare name?: string;
|
|
25
|
+
declare size?: IconSize;
|
|
26
|
+
declare color?: IconColor;
|
|
27
|
+
declare stroke?: boolean;
|
|
28
|
+
declare strokeWidth?: number;
|
|
29
|
+
|
|
30
|
+
protected static styles = [styleVariables, sizeCSS, colorCSS, style];
|
|
31
|
+
|
|
32
|
+
protected render() {
|
|
33
|
+
const def = this.name ? IconRegistry.get(this.name) : undefined;
|
|
34
|
+
|
|
35
|
+
// Numeric size: set font-size directly (string sizes are handled by size.css)
|
|
36
|
+
const numericSize = Number(this.size);
|
|
37
|
+
if (this.size !== undefined && !isNaN(numericSize) && numericSize > 0) {
|
|
38
|
+
this.style.setProperty('font-size', `${numericSize}px`);
|
|
39
|
+
} else {
|
|
40
|
+
this.style.removeProperty('font-size');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Resolve stroke: component prop takes priority over registry definition
|
|
44
|
+
const useStroke = this.stroke ?? def?.stroke ?? false;
|
|
45
|
+
const useStrokeWidth = this.strokeWidth ?? def?.strokeWidth ?? 2;
|
|
46
|
+
|
|
47
|
+
if (useStroke) {
|
|
48
|
+
this.style.setProperty('--icon-fill', 'none');
|
|
49
|
+
this.style.setProperty('--icon-stroke', 'currentColor');
|
|
50
|
+
this.style.setProperty('--icon-stroke-width', String(useStrokeWidth));
|
|
51
|
+
} else {
|
|
52
|
+
this.style.removeProperty('--icon-fill');
|
|
53
|
+
this.style.removeProperty('--icon-stroke');
|
|
54
|
+
this.style.removeProperty('--icon-stroke-width');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!def) return;
|
|
58
|
+
|
|
59
|
+
return html(({ svg, path }) => {
|
|
60
|
+
svg(
|
|
61
|
+
{
|
|
62
|
+
className: 'icon-svg',
|
|
63
|
+
viewBox: def.viewBox ?? defaultViewBox,
|
|
64
|
+
'aria-hidden': 'true',
|
|
65
|
+
xmlns: SVG_NS,
|
|
66
|
+
},
|
|
67
|
+
() => {
|
|
68
|
+
path({ d: def.path });
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Icon.register();
|
|
76
|
+
|
|
77
|
+
declare global {
|
|
78
|
+
interface HTMLElementTagNameMap {
|
|
79
|
+
'ae-icon': Icon;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default Icon;
|
|
84
|
+
export type IconProps = InferProps<typeof Icon>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IconDefinition, IconRegistryData, defaultViewBox } from './defines';
|
|
2
|
+
|
|
3
|
+
class IconRegistry {
|
|
4
|
+
private static _icons: Map<string, IconDefinition> = new Map();
|
|
5
|
+
|
|
6
|
+
static add(icons: IconRegistryData) {
|
|
7
|
+
for (const [name, data] of Object.entries(icons)) {
|
|
8
|
+
if (typeof data === 'string') {
|
|
9
|
+
this._icons.set(name, { path: data, viewBox: defaultViewBox });
|
|
10
|
+
} else {
|
|
11
|
+
this._icons.set(name, data);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static get(name: string): IconDefinition | undefined {
|
|
17
|
+
return this._icons.get(name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static has(name: string): boolean {
|
|
21
|
+
return this._icons.has(name);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default IconRegistry;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { InferProps, Props } from 'aeico';
|
|
2
|
+
import AeicoComponent from '../aeico-component';
|
|
3
|
+
import { html } from 'aeico';
|
|
4
|
+
import type { IconColor } from '../icon/defines';
|
|
5
|
+
// Ensure ae-button and ae-icon are registered
|
|
6
|
+
import '../button/button';
|
|
7
|
+
import '../icon/icon';
|
|
8
|
+
|
|
9
|
+
export type IconButtonVariant = 'filled' | 'outlined' | 'subtle' | 'text';
|
|
10
|
+
export type IconButtonSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated Use `<ae-button>` with an `<ae-icon>` slot instead.
|
|
14
|
+
*
|
|
15
|
+
* ```html
|
|
16
|
+
* <ae-button color="primary"><ae-icon name="star"></ae-icon></ae-button>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
class IconButton extends AeicoComponent {
|
|
20
|
+
static tagName = 'icon-button';
|
|
21
|
+
|
|
22
|
+
static props: Props = {
|
|
23
|
+
icon: { type: String },
|
|
24
|
+
size: { type: String },
|
|
25
|
+
color: { type: String },
|
|
26
|
+
variant: { type: String },
|
|
27
|
+
disabled: { type: Boolean },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
declare icon?: string;
|
|
31
|
+
declare size?: IconButtonSize;
|
|
32
|
+
declare color?: IconColor;
|
|
33
|
+
declare variant?: IconButtonVariant;
|
|
34
|
+
declare disabled?: boolean;
|
|
35
|
+
|
|
36
|
+
protected static styles = [':host { display: contents; }'];
|
|
37
|
+
|
|
38
|
+
protected render() {
|
|
39
|
+
return html(({ aeButton, aeIcon }) => {
|
|
40
|
+
aeButton(
|
|
41
|
+
{
|
|
42
|
+
color: this.color,
|
|
43
|
+
variant: this.variant,
|
|
44
|
+
size: this.size,
|
|
45
|
+
disabled: this.disabled,
|
|
46
|
+
},
|
|
47
|
+
() => {
|
|
48
|
+
aeIcon({ name: this.icon });
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
IconButton.register();
|
|
56
|
+
|
|
57
|
+
declare global {
|
|
58
|
+
interface HTMLElementTagNameMap {
|
|
59
|
+
'ae-icon-button': IconButton;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default IconButton;
|
|
64
|
+
export type IconButtonProps = InferProps<typeof IconButton>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Components module exports
|
|
3
|
+
*
|
|
4
|
+
* All Aeico Web Components are available here for on-demand registration.
|
|
5
|
+
* Import only the components you need to keep your bundle size small.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { SelectField, RangeField, Button } from 'aeico/components'
|
|
10
|
+
*
|
|
11
|
+
* // Manually register components
|
|
12
|
+
* SelectField.register()
|
|
13
|
+
* RangeField.register()
|
|
14
|
+
* Button.register()
|
|
15
|
+
*
|
|
16
|
+
* // Or use with custom names
|
|
17
|
+
* SelectField.register('my-select')
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* // Use the static create method for programmatic creation
|
|
23
|
+
* import { Button } from 'aeico/components'
|
|
24
|
+
*
|
|
25
|
+
* const button = Button.create({
|
|
26
|
+
* variant: 'primary',
|
|
27
|
+
* size: 'md'
|
|
28
|
+
* })
|
|
29
|
+
* button.textContent = 'Click me'
|
|
30
|
+
* document.body.appendChild(button)
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// Field components
|
|
35
|
+
export { default as TextInput } from './text-input';
|
|
36
|
+
export { default as Select } from './select';
|
|
37
|
+
export { default as Slider } from './slider';
|
|
38
|
+
export { default as Checkbox } from './checkbox';
|
|
39
|
+
export { default as RadioGroup } from './radio-group';
|
|
40
|
+
export { Radio } from './radio-group';
|
|
41
|
+
export { default as Switch } from './switch';
|
|
42
|
+
|
|
43
|
+
// UI components
|
|
44
|
+
export { default as Breadcrumb, BreadcrumbItem } from './breadcrumb';
|
|
45
|
+
export { default as Button } from './button';
|
|
46
|
+
export { default as Dropdown, DropdownItem, DropdownButton } from './dropdown';
|
|
47
|
+
export { default as ButtonGroup } from './button-group';
|
|
48
|
+
export { default as Badge } from './badge';
|
|
49
|
+
export { default as Tag } from './tag';
|
|
50
|
+
export { default as Alert } from './alert';
|
|
51
|
+
export { default as Dialog } from './dialog';
|
|
52
|
+
export { default as Icon } from './icon/icon';
|
|
53
|
+
export { default as IconRegistry } from './icon/registry';
|
|
54
|
+
export { default as IconButton } from './icon-button';
|
|
55
|
+
export { Tabs, Tab, TabPanel } from './tabs';
|
|
56
|
+
export { default as Divider } from './divider';
|
|
57
|
+
export { default as Card } from './card';
|
|
58
|
+
export { default as Navbar } from './navbar';
|
|
59
|
+
export { default as Detail } from './detail';
|
|
60
|
+
|
|
61
|
+
// Component types
|
|
62
|
+
export type { SelectProps, SelectOption, SelectOptions, SelectOptionValue } from './select';
|
|
63
|
+
export type { SliderProps, SliderOption, SliderOptions, SliderOptionValue } from './slider';
|
|
64
|
+
export type { CheckboxProps, CheckboxVariant } from './checkbox';
|
|
65
|
+
export type {
|
|
66
|
+
RadioGroupProps,
|
|
67
|
+
RadioGroupMode,
|
|
68
|
+
RadioGroupOption,
|
|
69
|
+
RadioGroupOptions,
|
|
70
|
+
} from './radio-group';
|
|
71
|
+
export type { RadioProps } from './radio-group';
|
|
72
|
+
export type { SwitchProps } from './switch';
|
|
73
|
+
export type { TextInputProps } from './text-input';
|
|
74
|
+
export type { ButtonProps, ButtonColor, ButtonSize, ButtonVariant } from './button';
|
|
75
|
+
export type { ButtonGroupProps } from './button-group';
|
|
76
|
+
export type { AlertProps, AlertColor, AlertSize, AlertVariant } from './alert';
|
|
77
|
+
export type { BadgeProps, BadgeColor, BadgeSize, BadgeVariant } from './badge';
|
|
78
|
+
export type { TagProps, TagColor, TagSize, TagVariant } from './tag';
|
|
79
|
+
export type { DialogProps } from './dialog';
|
|
80
|
+
export type { IconProps, IconSize, IconColor, IconDefinition, IconRegistryData } from './icon';
|
|
81
|
+
export type { IconButtonProps, IconButtonVariant, IconButtonSize } from './icon-button';
|
|
82
|
+
export type { DividerProps } from './divider';
|
|
83
|
+
export type { CardProps, CardVariant, CardColor } from './card';
|
|
84
|
+
export type { NavbarProps, NavbarColor, NavbarAppearance } from './navbar';
|
|
85
|
+
export type { DetailProps, DetailVariant, DetailColor } from './detail';
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { InferProps } from 'aeico';
|
|
2
|
+
import styleVariables from '../styles/variables.css?inline';
|
|
3
|
+
import colorCSS from '../styles/color.css?inline';
|
|
4
|
+
import navbarStyle from '../styles/components/navbar.css?inline';
|
|
5
|
+
import AeicoComponent from '../aeico-component';
|
|
6
|
+
import { html } from 'aeico';
|
|
7
|
+
import { prop } from 'aeico';
|
|
8
|
+
import type { NavbarColor, NavbarAppearance } from './defines';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Navbar Component
|
|
12
|
+
*
|
|
13
|
+
* A sticky top navigation bar with three slot regions and built-in
|
|
14
|
+
* mobile hamburger collapse.
|
|
15
|
+
*
|
|
16
|
+
* Slots:
|
|
17
|
+
* - `brand` — leftmost area, typically a logo or site name link
|
|
18
|
+
* - `start` — main navigation links / dropdowns
|
|
19
|
+
* - `end` — right-side actions (login button, avatar, etc.)
|
|
20
|
+
*
|
|
21
|
+
* Slotted `<a>` elements receive default link styling controlled via
|
|
22
|
+
* CSS custom properties. Mark the active link with `aria-current="page"`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```html
|
|
26
|
+
* <ae-navbar color="primary" appearance="block">
|
|
27
|
+
* <a slot="brand" href="/">MyApp</a>
|
|
28
|
+
* <a slot="start" href="/" aria-current="page">Home</a>
|
|
29
|
+
* <a slot="start" href="/docs">Docs</a>
|
|
30
|
+
* <ae-button slot="end" size="sm" variant="outlined">Sign in</ae-button>
|
|
31
|
+
* </ae-navbar>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```css
|
|
36
|
+
* /* Custom height and transparent background *\/
|
|
37
|
+
* ae-navbar {
|
|
38
|
+
* --ae-navbar-height: 4rem;
|
|
39
|
+
* --ae-navbar-bg: transparent;
|
|
40
|
+
* --ae-navbar-border-width: 0;
|
|
41
|
+
* --ae-navbar-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
class Navbar extends AeicoComponent {
|
|
46
|
+
static tagName = 'navbar';
|
|
47
|
+
|
|
48
|
+
protected static styles = [styleVariables, colorCSS, navbarStyle];
|
|
49
|
+
|
|
50
|
+
/** Background color using the design-system color token set. */
|
|
51
|
+
@prop({ type: String })
|
|
52
|
+
accessor color: NavbarColor | undefined;
|
|
53
|
+
|
|
54
|
+
/** When true (default), the navbar sticks to the top of the viewport while scrolling. Set to false to let it scroll with the page. */
|
|
55
|
+
@prop({ type: Boolean })
|
|
56
|
+
accessor sticky: boolean = false;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Hover style preset for slotted `<a>` links.
|
|
60
|
+
* - `text` — only the font color changes on hover (default)
|
|
61
|
+
* - `block` — a subtle filled background block appears on hover
|
|
62
|
+
*
|
|
63
|
+
* Fine-tune further with `--ae-navbar-link-hover-color` /
|
|
64
|
+
* `--ae-navbar-link-hover-bg` CSS variables.
|
|
65
|
+
*/
|
|
66
|
+
@prop({ type: String })
|
|
67
|
+
accessor appearance: NavbarAppearance = 'text';
|
|
68
|
+
|
|
69
|
+
/** Whether the mobile menu is expanded. Reflects as the `open` attribute. */
|
|
70
|
+
@prop({ type: Boolean })
|
|
71
|
+
accessor open: boolean = false;
|
|
72
|
+
|
|
73
|
+
private _outsideClickHandler: ((e: MouseEvent) => void) | null = null;
|
|
74
|
+
|
|
75
|
+
connectedCallback() {
|
|
76
|
+
super.connectedCallback();
|
|
77
|
+
// Close menu when a nav link is clicked on mobile
|
|
78
|
+
this.listen('click', this._handleInnerClick);
|
|
79
|
+
// Close menu when clicking outside the navbar
|
|
80
|
+
this._outsideClickHandler = (e: MouseEvent) => {
|
|
81
|
+
// Event retargeting in shadow DOM means e.target is the host element
|
|
82
|
+
// when the click originates inside the shadow root, so this check is safe.
|
|
83
|
+
if (!this.contains(e.target as Node) && !this.shadowRoot?.contains(e.target as Node)) {
|
|
84
|
+
this._closeMenu();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
document.addEventListener('click', this._outsideClickHandler);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
disconnectedCallback() {
|
|
91
|
+
super.disconnectedCallback();
|
|
92
|
+
if (this._outsideClickHandler) {
|
|
93
|
+
document.removeEventListener('click', this._outsideClickHandler);
|
|
94
|
+
this._outsideClickHandler = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Toggle the mobile menu open/closed. */
|
|
99
|
+
toggleMenu(): void {
|
|
100
|
+
this.open = !this.open;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private _handleInnerClick = (e: Event) => {
|
|
104
|
+
if (!this.open) return;
|
|
105
|
+
// Close mobile menu when a slotted <a> link is clicked
|
|
106
|
+
const path = e.composedPath() as Element[];
|
|
107
|
+
if (path.some((el) => (el as HTMLElement).tagName === 'A')) {
|
|
108
|
+
this._closeMenu();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
private _toggleMenu = () => {
|
|
113
|
+
this.open = !this.open;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
private _closeMenu = () => {
|
|
117
|
+
if (this.open) this.open = false;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
protected render() {
|
|
121
|
+
return html(({ div, nav, button, span, slot }) => {
|
|
122
|
+
div({ class: 'inner' }, () => {
|
|
123
|
+
div({ part: 'brand' }, () => {
|
|
124
|
+
slot({ name: 'brand' });
|
|
125
|
+
});
|
|
126
|
+
nav({ part: 'nav', 'aria-label': 'Main navigation' }, () => {
|
|
127
|
+
div({ part: 'start' }, () => {
|
|
128
|
+
slot({ name: 'start' });
|
|
129
|
+
});
|
|
130
|
+
div({ part: 'end' }, () => {
|
|
131
|
+
slot({ name: 'end' });
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
button(
|
|
135
|
+
{
|
|
136
|
+
part: 'hamburger',
|
|
137
|
+
type: 'button',
|
|
138
|
+
'aria-expanded': String(this.open),
|
|
139
|
+
'aria-label': 'Toggle navigation',
|
|
140
|
+
'@click': this._toggleMenu,
|
|
141
|
+
},
|
|
142
|
+
() => {
|
|
143
|
+
span({ 'aria-hidden': 'true' });
|
|
144
|
+
span({ 'aria-hidden': 'true' });
|
|
145
|
+
span({ 'aria-hidden': 'true' });
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Navbar.register();
|
|
154
|
+
|
|
155
|
+
declare global {
|
|
156
|
+
interface HTMLElementTagNameMap {
|
|
157
|
+
'ae-navbar': Navbar;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default Navbar;
|
|
162
|
+
export type NavbarProps = InferProps<typeof Navbar>;
|