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.
Files changed (174) hide show
  1. package/README.md +0 -0
  2. package/dist/index.cjs +4226 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +4226 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/types/aeico-component.d.ts +8 -0
  7. package/dist/types/aeico-field.d.ts +132 -0
  8. package/dist/types/alert/alert.d.ts +49 -0
  9. package/dist/types/alert/defines.d.ts +3 -0
  10. package/dist/types/alert/index.d.ts +3 -0
  11. package/dist/types/badge/badge.d.ts +34 -0
  12. package/dist/types/badge/defines.d.ts +3 -0
  13. package/dist/types/badge/index.d.ts +3 -0
  14. package/dist/types/breadcrumb/breadcrumb-item.d.ts +31 -0
  15. package/dist/types/breadcrumb/breadcrumb.d.ts +60 -0
  16. package/dist/types/breadcrumb/defines.d.ts +1 -0
  17. package/dist/types/breadcrumb/index.d.ts +5 -0
  18. package/dist/types/button/button.d.ts +60 -0
  19. package/dist/types/button/defines.d.ts +3 -0
  20. package/dist/types/button/index.d.ts +3 -0
  21. package/dist/types/button-group/button-group.d.ts +56 -0
  22. package/dist/types/button-group/index.d.ts +2 -0
  23. package/dist/types/card/card.d.ts +19 -0
  24. package/dist/types/card/defines.d.ts +2 -0
  25. package/dist/types/card/index.d.ts +3 -0
  26. package/dist/types/checkbox/checkbox.d.ts +37 -0
  27. package/dist/types/checkbox/defines.d.ts +1 -0
  28. package/dist/types/checkbox/index.d.ts +3 -0
  29. package/dist/types/detail/defines.d.ts +2 -0
  30. package/dist/types/detail/detail.d.ts +40 -0
  31. package/dist/types/detail/index.d.ts +3 -0
  32. package/dist/types/dialog/dialog.d.ts +29 -0
  33. package/dist/types/dialog/index.d.ts +2 -0
  34. package/dist/types/divider/divider.d.ts +34 -0
  35. package/dist/types/divider/index.d.ts +2 -0
  36. package/dist/types/dropdown/defines.d.ts +1 -0
  37. package/dist/types/dropdown/dropdown-button.d.ts +60 -0
  38. package/dist/types/dropdown/dropdown-item.d.ts +56 -0
  39. package/dist/types/dropdown/dropdown.d.ts +84 -0
  40. package/dist/types/dropdown/index.d.ts +7 -0
  41. package/dist/types/icon/defines.d.ts +10 -0
  42. package/dist/types/icon/icon.d.ts +21 -0
  43. package/dist/types/icon/index.d.ts +4 -0
  44. package/dist/types/icon/registry.d.ts +8 -0
  45. package/dist/types/icon-button/icon-button.d.ts +32 -0
  46. package/dist/types/icon-button/index.d.ts +2 -0
  47. package/dist/types/index.d.ts +74 -0
  48. package/dist/types/navbar/defines.d.ts +2 -0
  49. package/dist/types/navbar/index.d.ts +3 -0
  50. package/dist/types/navbar/navbar.d.ts +73 -0
  51. package/dist/types/radio-group/defines.d.ts +6 -0
  52. package/dist/types/radio-group/index.d.ts +5 -0
  53. package/dist/types/radio-group/radio-group.d.ts +41 -0
  54. package/dist/types/radio-group/radio.d.ts +47 -0
  55. package/dist/types/select/defines.d.ts +8 -0
  56. package/dist/types/select/index.d.ts +5 -0
  57. package/dist/types/select/select-option.d.ts +20 -0
  58. package/dist/types/select/select.d.ts +60 -0
  59. package/dist/types/slider/defines.d.ts +31 -0
  60. package/dist/types/slider/index.d.ts +3 -0
  61. package/dist/types/slider/slider.d.ts +45 -0
  62. package/dist/types/switch/index.d.ts +2 -0
  63. package/dist/types/switch/switch.d.ts +35 -0
  64. package/dist/types/tabs/defines.d.ts +1 -0
  65. package/dist/types/tabs/index.d.ts +3 -0
  66. package/dist/types/tabs/tab-panel.d.ts +11 -0
  67. package/dist/types/tabs/tab.d.ts +18 -0
  68. package/dist/types/tabs/tabs.d.ts +24 -0
  69. package/dist/types/tag/defines.d.ts +3 -0
  70. package/dist/types/tag/index.d.ts +3 -0
  71. package/dist/types/tag/tag.d.ts +36 -0
  72. package/dist/types/text-input/index.d.ts +2 -0
  73. package/dist/types/text-input/text-input.d.ts +26 -0
  74. package/dist/types/utils.d.ts +2 -0
  75. package/package.json +63 -0
  76. package/src/aeico-component.ts +17 -0
  77. package/src/aeico-field.ts +228 -0
  78. package/src/alert/alert.ts +107 -0
  79. package/src/alert/defines.ts +11 -0
  80. package/src/alert/index.ts +3 -0
  81. package/src/badge/badge.ts +62 -0
  82. package/src/badge/defines.ts +12 -0
  83. package/src/badge/index.ts +3 -0
  84. package/src/breadcrumb/breadcrumb-item.ts +61 -0
  85. package/src/breadcrumb/breadcrumb.ts +138 -0
  86. package/src/breadcrumb/defines.ts +10 -0
  87. package/src/breadcrumb/index.ts +5 -0
  88. package/src/button/button.ts +147 -0
  89. package/src/button/defines.ts +12 -0
  90. package/src/button/index.ts +3 -0
  91. package/src/button-group/button-group.ts +140 -0
  92. package/src/button-group/index.ts +2 -0
  93. package/src/card/card.ts +57 -0
  94. package/src/card/defines.ts +11 -0
  95. package/src/card/index.ts +3 -0
  96. package/src/checkbox/checkbox.ts +90 -0
  97. package/src/checkbox/defines.ts +1 -0
  98. package/src/checkbox/index.ts +3 -0
  99. package/src/detail/defines.ts +11 -0
  100. package/src/detail/detail.ts +122 -0
  101. package/src/detail/index.ts +3 -0
  102. package/src/dialog/dialog.ts +149 -0
  103. package/src/dialog/index.ts +2 -0
  104. package/src/divider/divider.ts +56 -0
  105. package/src/divider/index.ts +2 -0
  106. package/src/dropdown/defines.ts +13 -0
  107. package/src/dropdown/dropdown-button.ts +130 -0
  108. package/src/dropdown/dropdown-item.ts +136 -0
  109. package/src/dropdown/dropdown.ts +211 -0
  110. package/src/dropdown/index.ts +7 -0
  111. package/src/icon/defines.ts +21 -0
  112. package/src/icon/icon.ts +84 -0
  113. package/src/icon/index.ts +4 -0
  114. package/src/icon/registry.ts +25 -0
  115. package/src/icon-button/icon-button.ts +64 -0
  116. package/src/icon-button/index.ts +2 -0
  117. package/src/index.ts +85 -0
  118. package/src/navbar/defines.ts +11 -0
  119. package/src/navbar/index.ts +3 -0
  120. package/src/navbar/navbar.ts +162 -0
  121. package/src/radio-group/defines.ts +5 -0
  122. package/src/radio-group/index.ts +5 -0
  123. package/src/radio-group/radio-group.ts +227 -0
  124. package/src/radio-group/radio.ts +58 -0
  125. package/src/select/defines.ts +12 -0
  126. package/src/select/index.ts +5 -0
  127. package/src/select/select-option.ts +59 -0
  128. package/src/select/select.ts +387 -0
  129. package/src/slider/defines.ts +33 -0
  130. package/src/slider/index.ts +3 -0
  131. package/src/slider/slider.ts +364 -0
  132. package/src/styles/color.css +117 -0
  133. package/src/styles/components/alert.css +104 -0
  134. package/src/styles/components/badge.css +67 -0
  135. package/src/styles/components/breadcrumb-item.css +59 -0
  136. package/src/styles/components/breadcrumb.css +19 -0
  137. package/src/styles/components/button-group.css +25 -0
  138. package/src/styles/components/button.css +213 -0
  139. package/src/styles/components/card.css +64 -0
  140. package/src/styles/components/checkbox.css +78 -0
  141. package/src/styles/components/detail.css +127 -0
  142. package/src/styles/components/dialog.css +103 -0
  143. package/src/styles/components/divider.css +18 -0
  144. package/src/styles/components/dropdown-item.css +91 -0
  145. package/src/styles/components/dropdown.css +179 -0
  146. package/src/styles/components/icon-button.css +116 -0
  147. package/src/styles/components/icon.css +29 -0
  148. package/src/styles/components/navbar.css +250 -0
  149. package/src/styles/components/radio-group.css +360 -0
  150. package/src/styles/components/select-option.css +43 -0
  151. package/src/styles/components/select.css +222 -0
  152. package/src/styles/components/slider.css +326 -0
  153. package/src/styles/components/switch.css +117 -0
  154. package/src/styles/components/tab-panel.css +8 -0
  155. package/src/styles/components/tab.css +44 -0
  156. package/src/styles/components/tabs.css +16 -0
  157. package/src/styles/components/tag.css +107 -0
  158. package/src/styles/components/text-input.css +110 -0
  159. package/src/styles/layout.css +43 -0
  160. package/src/styles/size.css +7 -0
  161. package/src/styles/variables.css +368 -0
  162. package/src/switch/index.ts +2 -0
  163. package/src/switch/switch.ts +88 -0
  164. package/src/tabs/defines.ts +1 -0
  165. package/src/tabs/index.ts +3 -0
  166. package/src/tabs/tab-panel.ts +23 -0
  167. package/src/tabs/tab.ts +62 -0
  168. package/src/tabs/tabs.ts +134 -0
  169. package/src/tag/defines.ts +12 -0
  170. package/src/tag/index.ts +3 -0
  171. package/src/tag/tag.ts +85 -0
  172. package/src/text-input/index.ts +2 -0
  173. package/src/text-input/text-input.ts +75 -0
  174. package/src/utils.ts +6 -0
@@ -0,0 +1,3 @@
1
+ export { default, default as Checkbox } from './checkbox';
2
+ export type { CheckboxVariant } from './defines';
3
+ export type { CheckboxProps } from './checkbox';
@@ -0,0 +1,11 @@
1
+ export type DetailVariant = 'subtle' | 'faint' | 'filled' | 'outlined';
2
+ export type DetailColor =
3
+ | 'default'
4
+ | 'primary'
5
+ | 'secondary'
6
+ | 'success'
7
+ | 'danger'
8
+ | 'warning'
9
+ | 'info'
10
+ | 'light'
11
+ | 'dark';
@@ -0,0 +1,122 @@
1
+ import type { InferProps } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import colorCSS from '../styles/color.css?inline';
4
+ import detailStyle from '../styles/components/detail.css?inline';
5
+ import AeicoComponent from '../aeico-component';
6
+ import { html } from 'aeico';
7
+ import { prop } from 'aeico';
8
+ import type { DetailColor, DetailVariant } from './defines';
9
+
10
+ /**
11
+ * Detail component that can be used to show/hide additional content.
12
+ *
13
+ * @example
14
+ * ```html
15
+ * <ae-detail summary="Click Me">
16
+ * <ae-icon name="plus" slot="expand"></ae-icon>
17
+ * <ae-icon name="minus" slot="collapse"></ae-icon>
18
+ * Detail.....
19
+ * </ae-detail>
20
+ */
21
+ class Detail extends AeicoComponent {
22
+ static tagName = 'detail';
23
+
24
+ protected static styles = [styleVariables, colorCSS, detailStyle];
25
+
26
+ @prop({ type: String })
27
+ accessor summary: string = '';
28
+
29
+ @prop({ type: String })
30
+ accessor variant: DetailVariant = 'filled';
31
+
32
+ @prop({ type: String })
33
+ accessor color: DetailColor = 'default';
34
+
35
+ @prop({ type: Boolean })
36
+ accessor disabled: boolean = false;
37
+
38
+ private _open: boolean = false;
39
+
40
+ /** Opens the detail panel. */
41
+ open(): void {
42
+ if (this.disabled || this._open) return;
43
+ this._open = true;
44
+ this.toggleAttribute('open', true);
45
+ this.update();
46
+ this.emit('open');
47
+ }
48
+
49
+ /** Closes the detail panel. */
50
+ close(): void {
51
+ if (!this._open) return;
52
+ this._open = false;
53
+ this.toggleAttribute('open', false);
54
+ this.update();
55
+ this.emit('close');
56
+ }
57
+
58
+ /** Toggles the detail panel open/closed. */
59
+ toggle(): void {
60
+ if (this._open) {
61
+ this.close();
62
+ } else {
63
+ this.open();
64
+ }
65
+ }
66
+
67
+ /** Returns whether the detail panel is currently open. */
68
+ isOpen(): boolean {
69
+ return this._open;
70
+ }
71
+
72
+ private _handleSummaryClick = (): void => {
73
+ this.toggle();
74
+ };
75
+
76
+ protected render() {
77
+ return html(({ div, button, span, slot }) => {
78
+ div({ className: 'detail', part: 'detail' }, () => {
79
+ button(
80
+ {
81
+ className: 'summary',
82
+ part: 'summary',
83
+ type: 'button',
84
+ 'aria-expanded': String(this._open),
85
+ disabled: this.disabled || undefined,
86
+ '@click': this._handleSummaryClick,
87
+ },
88
+ () => {
89
+ slot({ name: 'summary' }, () => {
90
+ span({ className: 'label', textContent: this.summary });
91
+ });
92
+ slot({ name: 'expand' });
93
+ slot({ name: 'collapse' });
94
+ },
95
+ );
96
+ div({ className: 'content-outer' }, () => {
97
+ div(
98
+ {
99
+ className: 'content',
100
+ part: 'content',
101
+ role: 'region',
102
+ },
103
+ () => {
104
+ slot();
105
+ },
106
+ );
107
+ });
108
+ });
109
+ });
110
+ }
111
+ }
112
+
113
+ Detail.register();
114
+
115
+ declare global {
116
+ interface HTMLElementTagNameMap {
117
+ 'ae-detail': Detail;
118
+ }
119
+ }
120
+
121
+ export default Detail;
122
+ export type DetailProps = InferProps<typeof Detail>;
@@ -0,0 +1,3 @@
1
+ export { default, default as Detail } from './detail';
2
+ export type { DetailProps } from './detail';
3
+ export type { DetailVariant, DetailColor } from './defines';
@@ -0,0 +1,149 @@
1
+ import type { InferProps, Props } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import style from '../styles/components/dialog.css?inline';
4
+ import AeicoComponent from '../aeico-component';
5
+ import { html } from 'aeico';
6
+
7
+ class Dialog extends AeicoComponent {
8
+ static props: Props = {
9
+ label: { type: String },
10
+ width: { type: String },
11
+ height: { type: String },
12
+ modal: { type: Boolean },
13
+ closable: { type: Boolean },
14
+ header: { type: Boolean },
15
+ closeOnOverlayClick: { type: Boolean },
16
+ };
17
+
18
+ declare label?: string;
19
+ declare width?: string;
20
+ declare height?: string;
21
+ declare modal?: boolean;
22
+ declare closable?: boolean;
23
+ declare header?: boolean;
24
+ declare closeOnOverlayClick?: boolean;
25
+
26
+ protected static styles = [styleVariables, style];
27
+
28
+ private _dialogEl: HTMLDialogElement | null = null;
29
+ private _hasFooter = false;
30
+
31
+ protected render() {
32
+ return html(({ dialog, div, header, footer, span, button, slot }) => {
33
+ this._dialogEl = dialog(
34
+ {
35
+ '@click': this._handleDialogClick,
36
+ '@close': this._handleNativeClose,
37
+ style: {
38
+ width: this.width || '',
39
+ height: this.height || '',
40
+ },
41
+ },
42
+ () => {
43
+ // Header
44
+ if (this.header !== false) {
45
+ header({}, () => {
46
+ slot({ name: 'header' }, () => {
47
+ span({ className: 'label', textContent: this.label || '' });
48
+ });
49
+ if (this.closable !== false) {
50
+ button({
51
+ className: 'close-btn',
52
+ textContent: '×',
53
+ '@click': () => this.close(),
54
+ });
55
+ }
56
+ });
57
+ }
58
+
59
+ // Body
60
+ div({ className: 'body' }, () => {
61
+ slot();
62
+ });
63
+
64
+ // Footer — always rendered to capture slotchange, hidden when empty
65
+ footer(
66
+ {
67
+ style: { display: this._hasFooter ? '' : 'none' },
68
+ },
69
+ () => {
70
+ slot({ name: 'footer', '@slotchange': this._handleFooterSlotChange });
71
+ },
72
+ );
73
+ },
74
+ );
75
+ });
76
+ }
77
+
78
+ private _handleDialogClick = (e: Event) => {
79
+ const mouseEvent = e as MouseEvent;
80
+ const path = mouseEvent.composedPath();
81
+
82
+ // data-close: any slotted element with [data-close] closes the dialog
83
+ for (const el of path) {
84
+ if (el instanceof Element && el.hasAttribute('data-close')) {
85
+ this.close();
86
+ return;
87
+ }
88
+ if (el === this._dialogEl) break;
89
+ }
90
+
91
+ // Backdrop click (modal mode only)
92
+ if (this.modal !== false && this.closeOnOverlayClick !== false) {
93
+ if (mouseEvent.target === this._dialogEl) {
94
+ const rect = this._dialogEl!.getBoundingClientRect();
95
+ const outside =
96
+ mouseEvent.clientX < rect.left ||
97
+ mouseEvent.clientX > rect.right ||
98
+ mouseEvent.clientY < rect.top ||
99
+ mouseEvent.clientY > rect.bottom;
100
+ if (outside) {
101
+ this.close();
102
+ }
103
+ }
104
+ }
105
+ };
106
+
107
+ private _handleNativeClose = () => {
108
+ this.emit('close', { detail: { target: this } });
109
+ };
110
+
111
+ private _handleFooterSlotChange = (e: Event) => {
112
+ const slotEl = e.target as HTMLSlotElement;
113
+ const hasContent = slotEl.assignedElements().length > 0;
114
+ if (hasContent !== this._hasFooter) {
115
+ this._hasFooter = hasContent;
116
+ this.update();
117
+ }
118
+ };
119
+
120
+ open() {
121
+ if (!this._dialogEl) return;
122
+ if (this.modal !== false) {
123
+ this._dialogEl.showModal();
124
+ } else {
125
+ this._dialogEl.show();
126
+ }
127
+ this.emit('open', { detail: { target: this } });
128
+ }
129
+
130
+ close() {
131
+ this._dialogEl?.close();
132
+ // emit('close') is handled by _handleNativeClose via the native 'close' event
133
+ }
134
+
135
+ isOpen(): boolean {
136
+ return this._dialogEl?.open ?? false;
137
+ }
138
+ }
139
+
140
+ Dialog.register();
141
+
142
+ declare global {
143
+ interface HTMLElementTagNameMap {
144
+ 'ae-dialog': Dialog;
145
+ }
146
+ }
147
+
148
+ export default Dialog;
149
+ export type DialogProps = InferProps<typeof Dialog>;
@@ -0,0 +1,2 @@
1
+ export { default, default as Dialog } from './dialog';
2
+ export type { DialogProps } from './dialog';
@@ -0,0 +1,56 @@
1
+ import AeicoComponent from '../aeico-component';
2
+ import type { InferProps } from 'aeico';
3
+ import styleVariables from '../styles/variables.css?inline';
4
+ import colorCSS from '../styles/color.css?inline';
5
+ import style from '../styles/components/divider.css?inline';
6
+ import { prop } from 'aeico';
7
+ /**
8
+ * A simple divider component that can be used to separate content. It supports both horizontal and vertical orientations, as well as customizable thickness and color.
9
+ * @example
10
+ * ```html
11
+ * <ae-divider></ae-divider>
12
+ * <ae-divider vertical></ae-divider>
13
+ * <ae-divider thickness="4px" color="primary"></ae-divider>
14
+ * ```
15
+ * @props
16
+ * - `vertical` (boolean): If true, the divider will be vertical. Default is false (horizontal).
17
+ * - `thickness` (string): Custom thickness for the divider (e.g., "2px", "0.5rem"). If not provided, it will use the default thickness defined in CSS.
18
+ * - `color` (string): Color variant for the divider (e.g., "primary", "secondary"). If not provided, it will use the default color defined in CSS.
19
+ *
20
+ * @csspart divider - The main divider element that can be styled.
21
+ *
22
+ * @cssproperty --thickness - Custom property to set the thickness of the divider when the `thickness` prop is used.
23
+ */
24
+ class Divider extends AeicoComponent {
25
+ static tagName = 'divider';
26
+
27
+ @prop({ type: Boolean })
28
+ accessor vertical: boolean = false;
29
+
30
+ @prop({ type: String })
31
+ accessor thickness: string | undefined;
32
+
33
+ @prop({ type: String })
34
+ accessor color: string | undefined;
35
+
36
+ protected static styles = [styleVariables, colorCSS, style];
37
+
38
+ protected render(): void {
39
+ if (this.thickness) {
40
+ this.style.setProperty('--thickness', this.thickness);
41
+ } else {
42
+ this.style.removeProperty('--thickness');
43
+ }
44
+ }
45
+ }
46
+
47
+ Divider.register();
48
+
49
+ declare global {
50
+ interface HTMLElementTagNameMap {
51
+ 'ae-divider': Divider;
52
+ }
53
+ }
54
+
55
+ export default Divider;
56
+ export type DividerProps = InferProps<typeof Divider>;
@@ -0,0 +1,2 @@
1
+ export { default, default as Divider } from './divider';
2
+ export type { DividerProps } from './divider';
@@ -0,0 +1,13 @@
1
+ export type DropdownPlacement =
2
+ | 'top'
3
+ | 'top-start'
4
+ | 'top-end'
5
+ | 'bottom'
6
+ | 'bottom-start'
7
+ | 'bottom-end'
8
+ | 'right'
9
+ | 'right-start'
10
+ | 'right-end'
11
+ | 'left'
12
+ | 'left-start'
13
+ | 'left-end';
@@ -0,0 +1,130 @@
1
+ import AeicoComponent from '../aeico-component';
2
+ import type { InferProps } from 'aeico';
3
+ import { html } from 'aeico';
4
+ import { prop } from 'aeico';
5
+ import type { ButtonColor, ButtonSize, ButtonVariant } from '../button/defines';
6
+ import type { DropdownPlacement } from './defines';
7
+ import type Dropdown from './dropdown';
8
+ import './dropdown';
9
+ import '../button/button';
10
+
11
+ /**
12
+ * DropdownButton — a pre-composed trigger + dropdown panel.
13
+ *
14
+ * Renders an `ae-button`-styled trigger with a built-in chevron,
15
+ * and a floating panel for `<ae-dropdown-item>` children.
16
+ * Accepts the same `variant`, `color`, `size`, and `disabled` props
17
+ * as `ae-button`, making it a drop-in inside `ae-button-group`.
18
+ *
19
+ * @example
20
+ * ```html
21
+ * <ae-dropdown-button variant="outlined" color="primary">
22
+ * Actions
23
+ * <ae-dropdown-item value="edit">Edit</ae-dropdown-item>
24
+ * <ae-dropdown-item value="delete">Delete</ae-dropdown-item>
25
+ * </ae-dropdown-button>
26
+ *
27
+ * <!-- Inside ae-button-group -->
28
+ * <ae-button-group compact color="primary">
29
+ * <ae-button>Save</ae-button>
30
+ * <ae-dropdown-button placement="bottom-end">
31
+ * <ae-dropdown-item value="draft">Save as draft</ae-dropdown-item>
32
+ * <ae-dropdown-item value="template">Save as template</ae-dropdown-item>
33
+ * </ae-dropdown-button>
34
+ * </ae-button-group>
35
+ * ```
36
+ *
37
+ * Emits:
38
+ * - `open` — when the panel opens
39
+ * - `close` — when the panel closes
40
+ * - `select` — `{ detail: { value, label } }` when a menu item is selected
41
+ */
42
+ class DropdownButton extends AeicoComponent {
43
+ static tagName = 'dropdown-button';
44
+
45
+ // ae-button and ae-dropdown each carry their own shadow DOM styles.
46
+ // Only the host display is set here so button-group compact layout works.
47
+ protected static styles = [
48
+ ':host { display: inline-block; }',
49
+ '.caret { display: inline-block; width: 0; height: 0; margin-left: 0.3em; vertical-align: 0.2em; flex-shrink: 0; }',
50
+ '.caret--bottom { border-top: 0.35em solid; border-right: 0.35em solid transparent; border-left: 0.35em solid transparent; }',
51
+ '.caret--top { border-bottom: 0.35em solid; border-right: 0.35em solid transparent; border-left: 0.35em solid transparent; }',
52
+ '.caret--right { border-left: 0.35em solid; border-top: 0.35em solid transparent; border-bottom: 0.35em solid transparent; }',
53
+ '.caret--left { border-right: 0.35em solid; border-top: 0.35em solid transparent; border-bottom: 0.35em solid transparent; }',
54
+ ];
55
+
56
+ @prop({ type: String })
57
+ accessor variant: ButtonVariant = 'filled';
58
+
59
+ @prop({ type: String })
60
+ accessor color: ButtonColor = 'default';
61
+
62
+ @prop({ type: String })
63
+ accessor size: ButtonSize = 'md';
64
+
65
+ @prop({ type: Boolean })
66
+ accessor disabled: boolean = false;
67
+
68
+ @prop({ type: String })
69
+ accessor placement: DropdownPlacement = 'bottom-start';
70
+
71
+ @prop({ type: Boolean })
72
+ accessor closeOnSelect: boolean = true;
73
+
74
+ private _dropdownEl: Dropdown | null = null;
75
+
76
+ show(): void {
77
+ if (this.disabled) return;
78
+ this._dropdownEl?.show();
79
+ }
80
+ hide(): void {
81
+ this._dropdownEl?.hide();
82
+ }
83
+ toggle(): void {
84
+ if (this.disabled) return;
85
+ this._dropdownEl?.toggle();
86
+ }
87
+
88
+ get open(): boolean {
89
+ return this._dropdownEl?.open ?? false;
90
+ }
91
+
92
+ protected render() {
93
+ const dir = this.placement.split('-')[0];
94
+ return html(({ aeDropdown, aeButton, slot, span }) => {
95
+ this._dropdownEl = aeDropdown(
96
+ {
97
+ placement: this.placement,
98
+ 'close-on-select': this.closeOnSelect,
99
+ },
100
+ () => {
101
+ aeButton(
102
+ {
103
+ slot: 'trigger',
104
+ variant: this.variant,
105
+ color: this.color,
106
+ size: this.size,
107
+ disabled: this.disabled || undefined,
108
+ },
109
+ () => {
110
+ slot({ name: 'label' });
111
+ span({ className: `caret caret--${dir}`, 'aria-hidden': 'true' });
112
+ },
113
+ );
114
+ slot();
115
+ },
116
+ );
117
+ });
118
+ }
119
+ }
120
+
121
+ DropdownButton.register();
122
+
123
+ declare global {
124
+ interface HTMLElementTagNameMap {
125
+ 'ae-dropdown-button': DropdownButton;
126
+ }
127
+ }
128
+
129
+ export default DropdownButton;
130
+ export type DropdownButtonProps = InferProps<typeof DropdownButton>;
@@ -0,0 +1,136 @@
1
+ import AeicoComponent from '../aeico-component';
2
+ import type { InferProps } from 'aeico';
3
+ import { html } from 'aeico';
4
+ import style from '../styles/components/dropdown-item.css?inline';
5
+ import variables from '../styles/variables.css?inline';
6
+ import { prop } from 'aeico';
7
+ // Ensure ae-icon is registered when icons are used
8
+ import '../icon/icon';
9
+
10
+ /**
11
+ * Dropdown menu item — used as a direct child of `<ae-dropdown>`.
12
+ *
13
+ * Renders as a `<button>` by default, or as an `<a>` anchor when `href` is set.
14
+ * Use `<ae-icon>` inside to add icons, and CSS `color` / `--dropdown-item-color`
15
+ * to apply danger or custom colours.
16
+ *
17
+ * @example
18
+ * ```html
19
+ * <ae-dropdown-item value="edit"><ae-icon name="edit"></ae-icon>Edit</ae-dropdown-item>
20
+ * <ae-dropdown-item value="delete" style="--dropdown-item-color:var(--color-danger)">Delete</ae-dropdown-item>
21
+ * <ae-dropdown-item href="/profile">Profile</ae-dropdown-item>
22
+ * ```
23
+ */
24
+ class DropdownItem extends AeicoComponent {
25
+ static tagName = 'dropdown-item';
26
+
27
+ /** Value emitted in the `select` event detail on the parent dropdown. */
28
+ @prop({ type: String })
29
+ accessor value: string | undefined;
30
+
31
+ /** Disables the item — it becomes non-interactive and visually dimmed. */
32
+ @prop({ type: Boolean })
33
+ accessor disabled: boolean = false;
34
+
35
+ /**
36
+ * When set, the item renders as an `<a>` anchor element instead of a
37
+ * `<button>`. Useful for navigation items.
38
+ */
39
+ @prop({ type: String })
40
+ accessor href: string | undefined;
41
+
42
+ /**
43
+ * When `type="checkbox"`, the item behaves as a toggle: each click flips
44
+ * `checked` and includes the new state in the `select` event detail.
45
+ */
46
+ @prop({ type: String })
47
+ accessor type: 'checkbox' | undefined;
48
+
49
+ /**
50
+ * Whether the item is checked. Only meaningful when `type="checkbox"`.
51
+ * Reflects as the `checked` attribute.
52
+ */
53
+ @prop({ type: Boolean })
54
+ accessor checked: boolean = false;
55
+
56
+ /**
57
+ * Marks the item as the currently active/selected option (e.g. current route,
58
+ * current sort order). Purely visual — applies a highlighted background and
59
+ * accent colour.
60
+ */
61
+ @prop({ type: Boolean })
62
+ accessor active: boolean = false;
63
+
64
+ protected static styles = [variables, style];
65
+
66
+ connectedCallback() {
67
+ super.connectedCallback();
68
+ this.listen('click', this._handleClick);
69
+ this.setAttribute('role', 'menuitem');
70
+ }
71
+
72
+ private _handleClick = (e: Event): void => {
73
+ if (this.disabled) {
74
+ e.preventDefault();
75
+ e.stopPropagation();
76
+
77
+ return;
78
+ }
79
+ if (this.type === 'checkbox') {
80
+ this.checked = !this.checked;
81
+ }
82
+ const label = this.textContent?.trim() ?? '';
83
+ this.dispatchEvent(
84
+ new CustomEvent('_item-select', {
85
+ bubbles: true,
86
+ composed: true,
87
+ detail: { value: this.value ?? '', label, checked: this.checked },
88
+ }),
89
+ );
90
+ };
91
+
92
+ protected render() {
93
+ const isCheckbox = this.type === 'checkbox';
94
+ const sharedProps = {
95
+ part: 'item',
96
+ className: 'item',
97
+ 'aria-checked': isCheckbox ? String(this.checked) : undefined,
98
+ };
99
+ return html(({ button, a, span, slot }) => {
100
+ const children = () => {
101
+ if (isCheckbox) span({ className: 'check-indicator', 'aria-hidden': 'true' });
102
+ slot();
103
+ };
104
+ if (this.href) {
105
+ a(
106
+ {
107
+ ...sharedProps,
108
+ href: this.disabled ? undefined : this.href,
109
+ 'aria-disabled': this.disabled || undefined,
110
+ },
111
+ children,
112
+ );
113
+ } else {
114
+ button(
115
+ {
116
+ ...sharedProps,
117
+ type: 'button',
118
+ disabled: this.disabled,
119
+ },
120
+ children,
121
+ );
122
+ }
123
+ });
124
+ }
125
+ }
126
+
127
+ DropdownItem.register();
128
+
129
+ declare global {
130
+ interface HTMLElementTagNameMap {
131
+ 'ae-dropdown-item': DropdownItem;
132
+ }
133
+ }
134
+
135
+ export default DropdownItem;
136
+ export type DropdownItemProps = InferProps<typeof DropdownItem>;