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,61 @@
1
+ import type { InferProps } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import style from '../styles/components/breadcrumb-item.css?inline';
4
+ import AeicoComponent from '../aeico-component';
5
+ import { html } from 'aeico';
6
+ import { prop } from 'aeico';
7
+
8
+ /**
9
+ * BreadcrumbItem Component
10
+ *
11
+ * A single item in the `ae-breadcrumb` navigation trail.
12
+ * Renders as a link (`<a>`) when `href` is provided, otherwise as plain text.
13
+ * The separator is injected by the parent `ae-breadcrumb`.
14
+ *
15
+ * @example
16
+ * ```html
17
+ * <ae-breadcrumb>
18
+ * <ae-breadcrumb-item href="/">Home</ae-breadcrumb-item>
19
+ * <ae-breadcrumb-item href="/docs">Docs</ae-breadcrumb-item>
20
+ * <ae-breadcrumb-item>Current Page</ae-breadcrumb-item>
21
+ * </ae-breadcrumb>
22
+ * ```
23
+ */
24
+ class BreadcrumbItem extends AeicoComponent {
25
+ static tagName = 'breadcrumb-item';
26
+
27
+ protected static styles = [styleVariables, style];
28
+
29
+ @prop({ type: String })
30
+ accessor href: string | undefined;
31
+
32
+ protected render() {
33
+ return html(({ li, span, slot, a }) => {
34
+ li({ part: 'item', className: 'item' }, () => {
35
+ span({ part: 'separator', className: 'sep', 'aria-hidden': 'true' }, () => {
36
+ slot({ name: 'separator' });
37
+ });
38
+ span({ part: 'label', className: 'label' }, () => {
39
+ if (this.href) {
40
+ a({ href: this.href, part: 'link' }, () => {
41
+ slot();
42
+ });
43
+ } else {
44
+ slot();
45
+ }
46
+ });
47
+ });
48
+ });
49
+ }
50
+ }
51
+
52
+ BreadcrumbItem.register();
53
+
54
+ declare global {
55
+ interface HTMLElementTagNameMap {
56
+ 'ae-breadcrumb-item': BreadcrumbItem;
57
+ }
58
+ }
59
+
60
+ export default BreadcrumbItem;
61
+ export type BreadcrumbItemProps = InferProps<typeof BreadcrumbItem>;
@@ -0,0 +1,138 @@
1
+ import type { InferProps } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import colorCSS from '../styles/color.css?inline';
4
+ import style from '../styles/components/breadcrumb.css?inline';
5
+ import AeicoComponent from '../aeico-component';
6
+ import { html } from 'aeico';
7
+ import { prop } from 'aeico';
8
+ import type BreadcrumbItem from './breadcrumb-item';
9
+
10
+ /**
11
+ * Breadcrumb Component
12
+ *
13
+ * A navigation trail that shows the user's location in a hierarchy.
14
+ * Automatically injects separators between items and marks the last item
15
+ * as `aria-current="page"`.
16
+ *
17
+ * The separator is configurable via:
18
+ * - `separator` attribute (text, default `/`) — simple and concise
19
+ * - `slot="separator"` (any element, e.g. `ae-icon`) — takes priority over the attribute
20
+ *
21
+ * Supports `color` for theming item link colors. The separator intentionally
22
+ * uses a fixed muted color and does NOT respond to the `color` prop.
23
+ *
24
+ * @example
25
+ * ```html
26
+ * <!-- Default separator "/" -->
27
+ * <ae-breadcrumb>
28
+ * <ae-breadcrumb-item href="/">Home</ae-breadcrumb-item>
29
+ * <ae-breadcrumb-item href="/docs">Docs</ae-breadcrumb-item>
30
+ * <ae-breadcrumb-item>Getting Started</ae-breadcrumb-item>
31
+ * </ae-breadcrumb>
32
+ *
33
+ * <!-- Custom text separator -->
34
+ * <ae-breadcrumb separator=">">
35
+ * <ae-breadcrumb-item href="/">Home</ae-breadcrumb-item>
36
+ * <ae-breadcrumb-item>Current</ae-breadcrumb-item>
37
+ * </ae-breadcrumb>
38
+ *
39
+ * <!-- Icon separator (slot takes priority over separator attribute) -->
40
+ * <ae-breadcrumb>
41
+ * <ae-icon slot="separator" name="chevron-right" size="xs"></ae-icon>
42
+ * <ae-breadcrumb-item href="/">Home</ae-breadcrumb-item>
43
+ * <ae-breadcrumb-item>Current</ae-breadcrumb-item>
44
+ * </ae-breadcrumb>
45
+ * ```
46
+ */
47
+ class Breadcrumb extends AeicoComponent {
48
+ static tagName = 'breadcrumb';
49
+
50
+ protected static styles = [styleVariables, colorCSS, style];
51
+
52
+ /** Text separator shown between items. Ignored when `slot="separator"` is provided. */
53
+ @prop({ type: String })
54
+ accessor separator: string = '/';
55
+
56
+ @prop({ type: String })
57
+ accessor color: string | undefined;
58
+
59
+ private _itemsSlot: HTMLSlotElement | null = null;
60
+ private _sepSlot: HTMLSlotElement | null = null;
61
+
62
+ protected render() {
63
+ return html(({ nav, ol, slot }) => {
64
+ nav({ 'aria-label': 'breadcrumb', part: 'nav' }, () => {
65
+ ol({ part: 'list', className: 'list' }, () => {
66
+ this._itemsSlot = slot({
67
+ '@slotchange': () => this._syncSeparators(),
68
+ });
69
+ });
70
+ });
71
+ this._sepSlot = slot({
72
+ name: 'separator',
73
+ className: 'sep-template',
74
+ '@slotchange': () => this._syncSeparators(),
75
+ });
76
+ });
77
+ }
78
+
79
+ protected onUpdated() {
80
+ this._syncSeparators();
81
+ }
82
+
83
+ private _getItems(): BreadcrumbItem[] {
84
+ return (this._itemsSlot?.assignedElements() ?? []) as BreadcrumbItem[];
85
+ }
86
+
87
+ private _getSepElement(): Element | null {
88
+ return this._sepSlot?.assignedElements()[0] ?? null;
89
+ }
90
+
91
+ private _syncSeparators = () => {
92
+ const items = this._getItems();
93
+ const sepEl = this._getSepElement();
94
+
95
+ items.forEach((item, i) => {
96
+ // Remove previously injected separators to avoid duplicates
97
+ item.querySelectorAll('[data-ae-sep]').forEach((n) => n.remove());
98
+
99
+ const isLast = i === items.length - 1;
100
+
101
+ // Mark the last item as the current page for accessibility
102
+ if (isLast) {
103
+ item.setAttribute('aria-current', 'page');
104
+ } else {
105
+ item.removeAttribute('aria-current');
106
+ }
107
+
108
+ // First item gets no separator
109
+ if (i === 0) return;
110
+
111
+ const wrapper = document.createElement('span');
112
+ wrapper.setAttribute('slot', 'separator');
113
+ wrapper.setAttribute('data-ae-sep', '');
114
+ wrapper.setAttribute('aria-hidden', 'true');
115
+
116
+ if (sepEl) {
117
+ // Clone the slotted separator element (e.g. ae-icon)
118
+ wrapper.appendChild(sepEl.cloneNode(true));
119
+ } else {
120
+ // Fall back to text separator
121
+ wrapper.textContent = this.separator;
122
+ }
123
+
124
+ item.prepend(wrapper);
125
+ });
126
+ };
127
+ }
128
+
129
+ Breadcrumb.register();
130
+
131
+ declare global {
132
+ interface HTMLElementTagNameMap {
133
+ 'ae-breadcrumb': Breadcrumb;
134
+ }
135
+ }
136
+
137
+ export default Breadcrumb;
138
+ export type BreadcrumbProps = InferProps<typeof Breadcrumb>;
@@ -0,0 +1,10 @@
1
+ export type BreadcrumbColor =
2
+ | 'default'
3
+ | 'primary'
4
+ | 'secondary'
5
+ | 'success'
6
+ | 'danger'
7
+ | 'warning'
8
+ | 'info'
9
+ | 'light'
10
+ | 'dark';
@@ -0,0 +1,5 @@
1
+ export { default, default as Breadcrumb } from './breadcrumb';
2
+ export { default as BreadcrumbItem } from './breadcrumb-item';
3
+ export type { BreadcrumbProps } from './breadcrumb';
4
+ export type { BreadcrumbItemProps } from './breadcrumb-item';
5
+ export type { BreadcrumbColor } from './defines';
@@ -0,0 +1,147 @@
1
+ import type { InferProps } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import sizeCSS from '../styles/size.css?inline';
4
+ import colorCSS from '../styles/color.css?inline';
5
+ import buttonStyle from '../styles/components/button.css?inline';
6
+ import AeicoComponent from '../aeico-component';
7
+ import { html } from 'aeico';
8
+ import { ButtonColor, ButtonSize, ButtonVariant } from './defines';
9
+ import { prop } from 'aeico';
10
+
11
+ /**
12
+ * Button Component
13
+ *
14
+ * A customizable button component with multiple variants and sizes.
15
+ * Supports theme and internationalization through mixins.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // Using the static create method
20
+ * const button = Button.create({
21
+ * variant: 'primary',
22
+ * size: 'md'
23
+ * })
24
+ * ```
25
+ *
26
+ * @example
27
+ * ```html
28
+ * <!-- Using as Web Component -->
29
+ * <ae-button variant="primary" size="md">Save</ae-button>
30
+ * <ae-button variant="danger" size="sm">Delete</ae-button>
31
+ * <ae-button variant="subtle">Cancel</ae-button>
32
+ * ```
33
+ */
34
+ class Button extends AeicoComponent {
35
+ protected static styles = [styleVariables, sizeCSS, colorCSS, buttonStyle];
36
+
37
+ @prop({ type: String })
38
+ color?: ButtonColor;
39
+
40
+ @prop({ type: String })
41
+ variant?: ButtonVariant;
42
+
43
+ @prop({ type: String })
44
+ size?: ButtonSize;
45
+
46
+ @prop({ type: Boolean })
47
+ disabled?: boolean;
48
+
49
+ @prop({ type: String })
50
+ type?: 'button' | 'submit' | 'reset';
51
+
52
+ @prop({ type: Boolean })
53
+ active?: boolean;
54
+
55
+ @prop({ type: Boolean })
56
+ block?: boolean;
57
+
58
+ private buttonElement: HTMLButtonElement | null = null;
59
+ private _autoAriaLabel = false;
60
+
61
+ protected onMounted() {
62
+ const slot = this.shadowRoot?.querySelector('slot:not([name])');
63
+ if (slot) this.listen(slot, 'slotchange', this._handleSlotChange);
64
+ this._handleSlotChange();
65
+ }
66
+
67
+ private _handleSlotChange = () => {
68
+ const slot = this.shadowRoot?.querySelector('slot:not([name])') as HTMLSlotElement | null;
69
+ const nodes = slot?.assignedNodes() ?? [];
70
+ // Icon-only: exactly one element (ae-icon) and no meaningful text nodes
71
+ const elements = nodes.filter((n): n is Element => n.nodeType === Node.ELEMENT_NODE);
72
+ const hasText = nodes.some(
73
+ (n) => n.nodeType === Node.TEXT_NODE && n.textContent!.trim() !== '',
74
+ );
75
+ const isIconOnly =
76
+ !hasText && elements.length === 1 && elements[0].tagName.toLowerCase() === 'ae-icon';
77
+
78
+ if (isIconOnly) {
79
+ this.setAttribute('icon-only', '');
80
+ if (!this.hasAttribute('aria-label') || this._autoAriaLabel) {
81
+ this.setAttribute('aria-label', elements[0].getAttribute('name') ?? '');
82
+ this._autoAriaLabel = true;
83
+ }
84
+ } else {
85
+ this.removeAttribute('icon-only');
86
+ if (this._autoAriaLabel) {
87
+ this.removeAttribute('aria-label');
88
+ this._autoAriaLabel = false;
89
+ }
90
+ }
91
+ };
92
+
93
+ protected render() {
94
+ return html(({ button, slot }) => {
95
+ this.buttonElement = button(
96
+ {
97
+ type: this.type || 'button',
98
+ disabled: this.disabled,
99
+ part: 'button',
100
+ 'aria-pressed': this.active,
101
+ 'aria-disabled': this.disabled,
102
+ },
103
+ () => {
104
+ slot();
105
+ },
106
+ );
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Programmatically click the button
112
+ */
113
+ click() {
114
+ if (!this.disabled && this.buttonElement) {
115
+ this.buttonElement.click();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Focus the button
121
+ */
122
+ focus() {
123
+ if (this.buttonElement) {
124
+ this.buttonElement.focus();
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Blur the button
130
+ */
131
+ blur() {
132
+ if (this.buttonElement) {
133
+ this.buttonElement.blur();
134
+ }
135
+ }
136
+ }
137
+
138
+ Button.register();
139
+
140
+ declare global {
141
+ interface HTMLElementTagNameMap {
142
+ 'ae-button': Button;
143
+ }
144
+ }
145
+
146
+ export default Button;
147
+ export type ButtonProps = InferProps<typeof Button>;
@@ -0,0 +1,12 @@
1
+ export type ButtonColor =
2
+ | 'default'
3
+ | 'primary'
4
+ | 'secondary'
5
+ | 'success'
6
+ | 'danger'
7
+ | 'warning'
8
+ | 'info'
9
+ | 'light'
10
+ | 'dark';
11
+ export type ButtonVariant = 'filled' | 'outlined' | 'faint' | 'subtle' | 'text';
12
+ export type ButtonSize = '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg';
@@ -0,0 +1,3 @@
1
+ export { default, default as Button } from './button';
2
+ export type { ButtonProps } from './button';
3
+ export type { ButtonColor, ButtonSize, ButtonVariant } from './defines';
@@ -0,0 +1,140 @@
1
+ import type { InferProps, Props } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import buttonGroupStyle from '../styles/components/button-group.css?inline';
4
+ import AeicoComponent from '../aeico-component';
5
+ import { html } from 'aeico';
6
+ import type { ButtonColor, ButtonVariant, ButtonSize } from '../button';
7
+ import Button from '../button/button';
8
+ import DropdownButton from '../dropdown/dropdown-button';
9
+
10
+ /**
11
+ * ButtonGroup Component
12
+ *
13
+ * Groups multiple `ae-button` elements, propagating shared `variant`, `color`,
14
+ * `size`, and `disabled` props to each child. Supports a `compact` mode that
15
+ * joins buttons into a seamless connected strip (like Bootstrap's button group).
16
+ *
17
+ * @example
18
+ * ```html
19
+ * <!-- Loose group (gap between buttons) -->
20
+ * <ae-button-group variant="outlined" color="primary">
21
+ * <ae-button>One</ae-button>
22
+ * <ae-button>Two</ae-button>
23
+ * <ae-button>Three</ae-button>
24
+ * </ae-button-group>
25
+ *
26
+ * <!-- Compact — joined strip -->
27
+ * <ae-button-group compact color="primary">
28
+ * <ae-button>Left</ae-button>
29
+ * <ae-button>Middle</ae-button>
30
+ * <ae-button>Right</ae-button>
31
+ * </ae-button-group>
32
+ *
33
+ * <!-- Full-width -->
34
+ * <ae-button-group block color="danger" variant="outlined">
35
+ * <ae-button>Delete</ae-button>
36
+ * <ae-button>Archive</ae-button>
37
+ * </ae-button-group>
38
+ * ```
39
+ */
40
+ class ButtonGroup extends AeicoComponent {
41
+ static props: Props = {
42
+ variant: { type: String },
43
+ color: { type: String },
44
+ size: { type: String },
45
+ compact: { type: Boolean },
46
+ block: { type: Boolean },
47
+ disabled: { type: Boolean },
48
+ };
49
+
50
+ protected static styles = [styleVariables, buttonGroupStyle];
51
+
52
+ declare variant?: ButtonVariant;
53
+ declare color?: ButtonColor;
54
+ declare size?: ButtonSize;
55
+ declare compact?: boolean;
56
+ declare block?: boolean;
57
+ declare disabled?: boolean;
58
+
59
+ private slotEl: HTMLSlotElement | null = null;
60
+
61
+ connectedCallback() {
62
+ super.connectedCallback();
63
+
64
+ if (this.variant === undefined) this.variant = 'filled';
65
+ if (this.color === undefined) this.color = 'default';
66
+ if (this.size === undefined) this.size = 'md';
67
+ }
68
+
69
+ protected render() {
70
+ return html(({ slot }) => {
71
+ this.slotEl = slot({
72
+ '@slotchange': () => this._syncChildren(),
73
+ });
74
+ this._syncChildren();
75
+ });
76
+ }
77
+
78
+ private _getButtons(): Array<Button | DropdownButton> {
79
+ if (!this.slotEl) return [];
80
+
81
+ return (
82
+ this.slotEl.assignedElements({ flatten: true }) as Array<Button | DropdownButton>
83
+ ).filter((el) => {
84
+ const tag = el.tagName.toLowerCase();
85
+ return tag === 'ae-button' || tag === 'ae-dropdown-button';
86
+ });
87
+ }
88
+
89
+ private _syncChildren() {
90
+ const buttons = this._getButtons();
91
+ const r = this.size === 'xs' || this.size === 'sm' ? 3 : 4;
92
+
93
+ buttons.forEach((btn: Button | DropdownButton, i) => {
94
+ btn.variant = this.variant;
95
+ btn.color = this.color;
96
+ btn.size = this.size;
97
+
98
+ if (this.disabled) {
99
+ btn.disabled = true;
100
+ } else {
101
+ btn.disabled = false;
102
+ }
103
+
104
+ if (this.compact) {
105
+ const isFirst = i === 0;
106
+ const isLast = i === buttons.length - 1;
107
+
108
+ // Overlap adjacent borders by pulling non-first buttons left 1px
109
+ btn.style.marginLeft = isFirst ? '' : '-1px';
110
+
111
+ // Shape corners: only the outer edges of the strip keep radius
112
+ btn.style.setProperty('--_btn-r-tl', isFirst ? `${r}px` : '0');
113
+ btn.style.setProperty('--_btn-r-bl', isFirst ? `${r}px` : '0');
114
+ btn.style.setProperty('--_btn-r-tr', isLast ? `${r}px` : '0');
115
+ btn.style.setProperty('--_btn-r-br', isLast ? `${r}px` : '0');
116
+ } else {
117
+ btn.style.marginLeft = '';
118
+ this._clearRadius(btn);
119
+ }
120
+ });
121
+ }
122
+
123
+ private _clearRadius(btn: HTMLElement) {
124
+ btn.style.removeProperty('--_btn-r-tl');
125
+ btn.style.removeProperty('--_btn-r-tr');
126
+ btn.style.removeProperty('--_btn-r-br');
127
+ btn.style.removeProperty('--_btn-r-bl');
128
+ }
129
+ }
130
+
131
+ ButtonGroup.register();
132
+
133
+ declare global {
134
+ interface HTMLElementTagNameMap {
135
+ 'ae-button-group': ButtonGroup;
136
+ }
137
+ }
138
+
139
+ export default ButtonGroup;
140
+ export type ButtonGroupProps = InferProps<typeof ButtonGroup>;
@@ -0,0 +1,2 @@
1
+ export { default, default as ButtonGroup } from './button-group';
2
+ export type { ButtonGroupProps } from './button-group';
@@ -0,0 +1,57 @@
1
+ import type { InferProps } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import colorCSS from '../styles/color.css?inline';
4
+ import cardStyle from '../styles/components/card.css?inline';
5
+ import AeicoComponent from '../aeico-component';
6
+ import { html } from 'aeico';
7
+ import type { CardVariant, CardColor } from './defines';
8
+ import { prop } from 'aeico';
9
+
10
+ class Card extends AeicoComponent {
11
+ static tagName = 'card';
12
+
13
+ protected static styles = [styleVariables, colorCSS, cardStyle];
14
+
15
+ @prop({ type: String })
16
+ accessor color: CardColor = 'default';
17
+
18
+ @prop({ type: String })
19
+ accessor variant: CardVariant = 'filled';
20
+
21
+ protected render() {
22
+ return html(({ div, header, footer, slot }) => {
23
+ div({ className: 'card', part: 'card' }, () => {
24
+ header({ className: 'header', part: 'header' }, () => {
25
+ slot({ name: 'header', '@slotchange': (e: Event) => this._onHeaderSlotChange(e) });
26
+ });
27
+ div({ className: 'body', part: 'body' }, () => {
28
+ slot();
29
+ });
30
+ footer({ className: 'footer', part: 'footer' }, () => {
31
+ slot({ name: 'footer', '@slotchange': (e: Event) => this._onFooterSlotChange(e) });
32
+ });
33
+ });
34
+ });
35
+ }
36
+
37
+ private _onHeaderSlotChange(e: Event) {
38
+ const slot = e.target as HTMLSlotElement;
39
+ this.toggleAttribute('has-header', slot.assignedNodes({ flatten: true }).length > 0);
40
+ }
41
+
42
+ private _onFooterSlotChange(e: Event) {
43
+ const slot = e.target as HTMLSlotElement;
44
+ this.toggleAttribute('has-footer', slot.assignedNodes({ flatten: true }).length > 0);
45
+ }
46
+ }
47
+
48
+ Card.register();
49
+
50
+ declare global {
51
+ interface HTMLElementTagNameMap {
52
+ 'ae-card': Card;
53
+ }
54
+ }
55
+
56
+ export default Card;
57
+ export type CardProps = InferProps<typeof Card>;
@@ -0,0 +1,11 @@
1
+ export type CardVariant = 'subtle' | 'faint' | 'filled' | 'outlined';
2
+ export type CardColor =
3
+ | 'default'
4
+ | 'primary'
5
+ | 'secondary'
6
+ | 'success'
7
+ | 'danger'
8
+ | 'warning'
9
+ | 'info'
10
+ | 'light'
11
+ | 'dark';
@@ -0,0 +1,3 @@
1
+ export { default, default as Card } from './card';
2
+ export type { CardProps } from './card';
3
+ export type { CardVariant, CardColor } from './defines';
@@ -0,0 +1,90 @@
1
+ import AeicoField from '../aeico-field';
2
+ import type { InferProps, Props } from 'aeico';
3
+ import { html } from 'aeico';
4
+ import styleVariables from '../styles/variables.css?inline';
5
+ import sizeCSS from '../styles/size.css?inline';
6
+ import colorCSS from '../styles/color.css?inline';
7
+ import styles from '../styles/components/checkbox.css?inline';
8
+ import { CheckboxVariant } from './defines';
9
+
10
+ class Checkbox extends AeicoField {
11
+ protected fieldElement: HTMLInputElement | null = null;
12
+
13
+ static tagName = 'checkbox';
14
+
15
+ static props: Props = {
16
+ checked: { type: Boolean },
17
+ defaultChecked: { type: Boolean },
18
+ variant: { type: String },
19
+ };
20
+
21
+ declare checked?: boolean;
22
+ declare defaultChecked?: boolean;
23
+ declare variant?: CheckboxVariant;
24
+
25
+ protected static styles = [styleVariables, sizeCSS, colorCSS, styles];
26
+
27
+ protected getValue(): boolean {
28
+ return this.fieldElement?.checked ?? false;
29
+ }
30
+
31
+ protected writeValue(checked: boolean): void {
32
+ if (this.fieldElement) {
33
+ this.fieldElement.checked = Boolean(checked);
34
+ }
35
+ }
36
+
37
+ protected getEventPayload(checked: boolean, oldChecked: boolean, action: any) {
38
+ return { checked, oldChecked, action };
39
+ }
40
+
41
+ protected setValue(checked: boolean, options?: { silent?: boolean; action?: any }): void {
42
+ const oldChecked = this.getValue();
43
+ this.checked = checked;
44
+ this.writeValue(checked);
45
+ if (options?.silent === false) {
46
+ this.emit('change', {
47
+ detail: this.getEventPayload(checked, oldChecked, options.action || 'change'),
48
+ });
49
+ }
50
+ }
51
+
52
+ public reset(checked?: boolean, options?: { silent?: boolean }): void {
53
+ this.setValue(checked !== undefined ? checked : (this.defaultChecked ?? false), {
54
+ ...options,
55
+ action: 'reset',
56
+ });
57
+ }
58
+
59
+ public clear(options?: { silent?: boolean }): void {
60
+ this.setValue(false, { ...options, action: 'clear' });
61
+ }
62
+
63
+ render() {
64
+ return html(({ div, input }) => {
65
+ div({ className: 'checkbox-container', variant: this.variant }, () => {
66
+ div({ className: 'checkbox-wrapper' }, () => {
67
+ this.fieldElement = input({
68
+ type: 'checkbox',
69
+ className: 'field-input',
70
+ checked: Boolean(this.checked),
71
+ disabled: Boolean(this.disabled),
72
+ '@change': this.boundOnChange,
73
+ });
74
+ });
75
+ this.renderActionButtons();
76
+ });
77
+ });
78
+ }
79
+ }
80
+
81
+ Checkbox.register();
82
+
83
+ declare global {
84
+ interface HTMLElementTagNameMap {
85
+ 'ae-checkbox': Checkbox;
86
+ }
87
+ }
88
+
89
+ export default Checkbox;
90
+ export type CheckboxProps = InferProps<typeof Checkbox>;
@@ -0,0 +1 @@
1
+ export type CheckboxVariant = 'checkbox';