@pairbo/ui-kit 0.0.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/.husky/pre-commit +1 -0
- package/.prettierignore +16 -0
- package/.prettierrc.json +17 -0
- package/README.md +61 -0
- package/cspell.json +9 -0
- package/dev.html +101 -0
- package/docs/README.md +1 -0
- package/docs/_includes/component.njk +16 -0
- package/docs/_includes/default.njk +39 -0
- package/docs/_includes/sidebar.njk +16 -0
- package/docs/eleventy.config.mjs +72 -0
- package/docs/pages/components/message-selector.md +17 -0
- package/docs/pages/fabric-example.html +46 -0
- package/docs/pages/fabric-example.js +28 -0
- package/docs/pages/index.md +76 -0
- package/eslint.config.mjs +32 -0
- package/ignote_temp +3 -0
- package/index.html +162 -0
- package/lint-stage.confg.js +6 -0
- package/package.json +66 -0
- package/pages/card-selection.html +65 -0
- package/pages/drawer.html +47 -0
- package/pages/editor.html +45 -0
- package/pages/page-mgn.html +51 -0
- package/pages/test_build.html +47 -0
- package/public/Greeting Card from Pairbo.png +0 -0
- package/scripts/plop/plopfile.js +51 -0
- package/scripts/plop/templates/components/component.hbs +34 -0
- package/scripts/plop/templates/components/define.hbs +10 -0
- package/scripts/plop/templates/components/styles.hbs +7 -0
- package/src/components/button/button.component.ts +93 -0
- package/src/components/button/button.styles.ts +273 -0
- package/src/components/button/button.ts +10 -0
- package/src/components/button-group/button-group.component.ts +36 -0
- package/src/components/button-group/button-group.styles.ts +7 -0
- package/src/components/button-group/button-group.ts +10 -0
- package/src/components/card-selection/card-selection.component.ts +43 -0
- package/src/components/card-selection/card-selection.styles.ts +7 -0
- package/src/components/card-selection/card-selection.ts +10 -0
- package/src/components/category/category.component.ts +91 -0
- package/src/components/category/category.styles.ts +27 -0
- package/src/components/category/category.ts +10 -0
- package/src/components/category-image/category-image.component.ts +38 -0
- package/src/components/category-image/category-image.styles.ts +11 -0
- package/src/components/category-image/category-image.ts +10 -0
- package/src/components/drawer/drawer.component.ts +82 -0
- package/src/components/drawer/drawer.styles.ts +54 -0
- package/src/components/drawer/drawer.ts +10 -0
- package/src/components/editor/editor.component.ts +135 -0
- package/src/components/editor/editor.styles.ts +13 -0
- package/src/components/editor/editor.ts +10 -0
- package/src/components/fabric-example/fabric-example.component.ts +268 -0
- package/src/components/fabric-example/fabric-example.styles.ts +23 -0
- package/src/components/fabric-example/fabric-example.test.ts +0 -0
- package/src/components/fabric-example/fabric-example.ts +12 -0
- package/src/components/image-slider/editor-card-slider.component.ts +136 -0
- package/src/components/image-slider/editor-card-slider.styles.ts +46 -0
- package/src/components/image-slider/editor-card-slider.ts +9 -0
- package/src/components/main.ts +17 -0
- package/src/components/message-selector/message-selector.component.ts +154 -0
- package/src/components/message-selector/message-selector.styles.ts +16 -0
- package/src/components/message-selector/message-selector.test.ts +64 -0
- package/src/components/message-selector/message-selector.ts +13 -0
- package/src/components/page-manager/page-manager.component.ts +228 -0
- package/src/components/page-manager/page-manager.styles.ts +9 -0
- package/src/components/page-manager/page-manager.ts +10 -0
- package/src/components/radio-button/radio-button.component.ts +118 -0
- package/src/components/radio-button/radio-button.styles.ts +13 -0
- package/src/components/radio-button/radio-button.ts +10 -0
- package/src/components/radio-group/radio-group.component.ts +203 -0
- package/src/components/radio-group/radio-group.styles.ts +19 -0
- package/src/components/radio-group/radio-group.ts +10 -0
- package/src/components/selector/selector.component.ts +115 -0
- package/src/components/selector/selector.styles.ts +9 -0
- package/src/components/selector/selector.ts +10 -0
- package/src/components/textarea/textarea.component.ts +234 -0
- package/src/components/textarea/textarea.styles.ts +178 -0
- package/src/components/textarea/textarea.ts +10 -0
- package/src/components/type-form/type-form.component.ts +121 -0
- package/src/components/type-form/type-form.styles.ts +7 -0
- package/src/components/type-form/type-form.ts +10 -0
- package/src/declaration.d.ts +44 -0
- package/src/events/events.ts +1 -0
- package/src/events/pbo-category-card-select.ts +7 -0
- package/src/internal/form.ts +376 -0
- package/src/internal/pairbo-element.ts +85 -0
- package/src/internal/slots.ts +54 -0
- package/src/internal/watch.ts +79 -0
- package/src/styles/component.styles.ts +17 -0
- package/src/styles/form-control.styles.ts +59 -0
- package/src/themes/default.css +414 -0
- package/temp +20 -0
- package/tsconfig.json +28 -0
- package/vite.config.ts +26 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
2
|
+
import { html, LitElement } from "lit";
|
|
3
|
+
import type { CSSResultGroup, PropertyValues } from "lit";
|
|
4
|
+
import componentStyles from "../../styles/component.styles.js";
|
|
5
|
+
import PairboElement from "../../internal/pairbo-element.js";
|
|
6
|
+
import styles from "./page-manager.styles.js";
|
|
7
|
+
import { PboCategoryCardSelectEvent } from "../../events/pbo-category-card-select.js";
|
|
8
|
+
import type PboCardSelection from "../card-selection/card-selection.component.js";
|
|
9
|
+
import { watch } from "../../internal/watch.js";
|
|
10
|
+
import PboEditor from "../editor/editor.component.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @summary Short summary of the component's intended use.
|
|
14
|
+
* @status experimental
|
|
15
|
+
*
|
|
16
|
+
* @dependency pbo-example
|
|
17
|
+
*
|
|
18
|
+
* @event pbo-event-name - Emitted as an example.
|
|
19
|
+
*
|
|
20
|
+
* @slot - The default slot.
|
|
21
|
+
* @slot example - An example slot.
|
|
22
|
+
*
|
|
23
|
+
* @csspart base - The component's base wrapper.
|
|
24
|
+
*
|
|
25
|
+
* @cssproperty --example - An example CSS custom property.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
@customElement("pbo-page-manager")
|
|
29
|
+
export default class PboPageManager extends PairboElement {
|
|
30
|
+
static styles: CSSResultGroup = [componentStyles, styles];
|
|
31
|
+
@query("pbo-card-selection") cardSelectionEl!: PboCardSelection;
|
|
32
|
+
@query("pbo-editor") editorEl!: PboEditor;
|
|
33
|
+
|
|
34
|
+
@state() currentPage: "selection" | "editor" = "selection";
|
|
35
|
+
@state() selectedCard: string | null = null;
|
|
36
|
+
@state() categories: Category[] = [
|
|
37
|
+
{
|
|
38
|
+
id: "birthday",
|
|
39
|
+
name: "Birthday",
|
|
40
|
+
cards: [
|
|
41
|
+
{
|
|
42
|
+
id: "birthday-1",
|
|
43
|
+
name: "Card 1",
|
|
44
|
+
category: "birthday",
|
|
45
|
+
medias: {
|
|
46
|
+
cover: { url: "https://picsum.photos/id/10/300/400", alt: "Cover image" },
|
|
47
|
+
back: { url: "https://picsum.photos/id/30/300/400", alt: "Back image" },
|
|
48
|
+
inner: { url: "https://picsum.photos/id/40/300/400", alt: "Inner image" },
|
|
49
|
+
render_1: { url: "https://picsum.photos/id/55/300/400", alt: "Render 1" },
|
|
50
|
+
render_2: { url: "https://picsum.photos/id/12/300/400", alt: "Render 2" },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "birthday-2",
|
|
55
|
+
name: "Card 2",
|
|
56
|
+
category: "birthday",
|
|
57
|
+
medias: {
|
|
58
|
+
cover: { url: "https://picsum.photos/id/43/300/400", alt: "Cover image" },
|
|
59
|
+
back: { url: "https://picsum.photos/id/38/300/400", alt: "Back image" },
|
|
60
|
+
inner: { url: "https://picsum.photos/id/16/300/400", alt: "Inner image" },
|
|
61
|
+
render_1: { url: "https://picsum.photos/id/90/300/400", alt: "Render 1" },
|
|
62
|
+
render_2: { url: "https://picsum.photos/id/17/300/400", alt: "Render 2" },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "birthday-3",
|
|
67
|
+
name: "Card 3",
|
|
68
|
+
category: "birthday",
|
|
69
|
+
medias: {
|
|
70
|
+
cover: { url: "https://picsum.photos/id/51/300/400", alt: "Cover image" },
|
|
71
|
+
back: { url: "https://picsum.photos/id/32/300/400", alt: "Back image" },
|
|
72
|
+
inner: { url: "https://picsum.photos/id/12/300/400", alt: "Inner image" },
|
|
73
|
+
render_1: { url: "https://picsum.photos/id/65/300/400", alt: "Render 1" },
|
|
74
|
+
render_2: { url: "https://picsum.photos/id/98/300/400", alt: "Render 2" },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "birthday-4",
|
|
79
|
+
name: "Card 4",
|
|
80
|
+
category: "birthday",
|
|
81
|
+
medias: {
|
|
82
|
+
cover: { url: "https://picsum.photos/id/100/300/400", alt: "Cover image" },
|
|
83
|
+
back: { url: "https://picsum.photos/id/80/300/400", alt: "Back image" },
|
|
84
|
+
inner: { url: "https://picsum.photos/id/70/300/400", alt: "Inner image" },
|
|
85
|
+
render_1: { url: "https://picsum.photos/id/35/300/400", alt: "Render 1" },
|
|
86
|
+
render_2: { url: "https://picsum.photos/id/59/300/400", alt: "Render 2" },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "birthday-5",
|
|
91
|
+
name: "Card 5",
|
|
92
|
+
category: "birthday",
|
|
93
|
+
medias: {
|
|
94
|
+
cover: { url: "https://picsum.photos/id/87/300/400", alt: "Cover image" },
|
|
95
|
+
back: { url: "https://picsum.photos/id/16/300/400", alt: "Back image" },
|
|
96
|
+
inner: { url: "https://picsum.photos/id/5/300/400", alt: "Inner image" },
|
|
97
|
+
render_1: { url: "https://picsum.photos/id/14/300/400", alt: "Render 1" },
|
|
98
|
+
render_2: { url: "https://picsum.photos/id/83/300/400", alt: "Render 2" },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "anniversary",
|
|
105
|
+
name: "Anniversary",
|
|
106
|
+
cards: [
|
|
107
|
+
{
|
|
108
|
+
id: "anniversary-1",
|
|
109
|
+
name: "Card 1",
|
|
110
|
+
category: "anniversary",
|
|
111
|
+
medias: {
|
|
112
|
+
cover: { url: "https://picsum.photos/id/10/300/400", alt: "Cover image" },
|
|
113
|
+
back: { url: "https://picsum.photos/id/30/300/400", alt: "Back image" },
|
|
114
|
+
inner: { url: "https://picsum.photos/id/40/300/400", alt: "Inner image" },
|
|
115
|
+
render_1: { url: "https://picsum.photos/id/55/300/400", alt: "Render 1" },
|
|
116
|
+
render_2: { url: "https://picsum.photos/id/12/300/400", alt: "Render 2" },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "anniversary-2",
|
|
121
|
+
name: "Card 2",
|
|
122
|
+
category: "anniversary",
|
|
123
|
+
medias: {
|
|
124
|
+
cover: { url: "https://picsum.photos/id/43/300/400", alt: "Cover image" },
|
|
125
|
+
back: { url: "https://picsum.photos/id/38/300/400", alt: "Back image" },
|
|
126
|
+
inner: { url: "https://picsum.photos/id/16/300/400", alt: "Inner image" },
|
|
127
|
+
render_1: { url: "https://picsum.photos/id/90/300/400", alt: "Render 1" },
|
|
128
|
+
render_2: { url: "https://picsum.photos/id/17/300/400", alt: "Render 2" },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "anniversary-3",
|
|
133
|
+
name: "Card 3",
|
|
134
|
+
category: "anniversary",
|
|
135
|
+
medias: {
|
|
136
|
+
cover: { url: "https://picsum.photos/id/51/300/400", alt: "Cover image" },
|
|
137
|
+
back: { url: "https://picsum.photos/id/32/300/400", alt: "Back image" },
|
|
138
|
+
inner: { url: "https://picsum.photos/id/12/300/400", alt: "Inner image" },
|
|
139
|
+
render_1: { url: "https://picsum.photos/id/65/300/400", alt: "Render 1" },
|
|
140
|
+
render_2: { url: "https://picsum.photos/id/98/300/400", alt: "Render 2" },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "anniversary-4",
|
|
145
|
+
name: "Card 4",
|
|
146
|
+
category: "anniversary",
|
|
147
|
+
medias: {
|
|
148
|
+
cover: { url: "https://picsum.photos/id/100/300/400", alt: "Cover image" },
|
|
149
|
+
back: { url: "https://picsum.photos/id/80/300/400", alt: "Back image" },
|
|
150
|
+
inner: { url: "https://picsum.photos/id/70/300/400", alt: "Inner image" },
|
|
151
|
+
render_1: { url: "https://picsum.photos/id/35/300/400", alt: "Render 1" },
|
|
152
|
+
render_2: { url: "https://picsum.photos/id/59/300/400", alt: "Render 2" },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "anniversary-5",
|
|
157
|
+
name: "Card 5",
|
|
158
|
+
category: "anniversary",
|
|
159
|
+
medias: {
|
|
160
|
+
cover: { url: "https://picsum.photos/id/87/300/400", alt: "Cover image" },
|
|
161
|
+
back: { url: "https://picsum.photos/id/16/300/400", alt: "Back image" },
|
|
162
|
+
inner: { url: "https://picsum.photos/id/5/300/400", alt: "Inner image" },
|
|
163
|
+
render_1: { url: "https://picsum.photos/id/14/300/400", alt: "Render 1" },
|
|
164
|
+
render_2: { url: "https://picsum.photos/id/83/300/400", alt: "Render 2" },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
@state() selectedCardId: string | null = null;
|
|
172
|
+
@state() selectedCardDetails: { cover_url: string; id: string } | null = null;
|
|
173
|
+
|
|
174
|
+
private goToEditor() {
|
|
175
|
+
this.currentPage = "editor";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private goToSelector() {
|
|
179
|
+
this.currentPage = "selection";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
connectedCallback() {
|
|
183
|
+
super.connectedCallback();
|
|
184
|
+
this.shadowRoot?.addEventListener("pbo-category-card-selected", this.handleCardSelected);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
disconnectedCallback() {
|
|
188
|
+
super.disconnectedCallback();
|
|
189
|
+
this.shadowRoot?.removeEventListener("pbo-category-card-selected", this.handleCardSelected);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected firstUpdated() {
|
|
193
|
+
this.cardSelectionEl.categories = this.categories;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
updated(changedProperties: PropertyValues<this>) {
|
|
197
|
+
this.cardSelectionEl.categories = this.categories;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
findCardInCategoryById(cardId: string) {
|
|
201
|
+
for (const category of this.categories) {
|
|
202
|
+
const card = category.cards.find(card => card.id === cardId);
|
|
203
|
+
if (card) {
|
|
204
|
+
return card;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private handleCardSelected = (event: PboCategoryCardSelectEvent) => {
|
|
211
|
+
// Get the Id
|
|
212
|
+
const { cardId } = event.detail;
|
|
213
|
+
// Find the card
|
|
214
|
+
const card = this.findCardInCategoryById(cardId);
|
|
215
|
+
this.editorEl.card = card;
|
|
216
|
+
// console.log(JSON.stringify(card));
|
|
217
|
+
this.goToEditor();
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
render() {
|
|
221
|
+
return html`
|
|
222
|
+
<button @click=${this.goToSelector}>Prev</button>
|
|
223
|
+
<button @click=${this.goToEditor}>Next</button>
|
|
224
|
+
<pbo-card-selection name="selection" ?hidden=${this.currentPage !== "selection"}></pbo-card-selection>
|
|
225
|
+
<pbo-editor name="editor" ?hidden=${this.currentPage !== "editor"}></pbo-editor>
|
|
226
|
+
`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
2
|
+
import { html, LitElement } from "lit";
|
|
3
|
+
import type { CSSResultGroup } from "lit";
|
|
4
|
+
import componentStyles from "../../styles/component.styles.js";
|
|
5
|
+
import styles from "./radio-button.styles.js";
|
|
6
|
+
import { HasSlotController } from "../../internal/slots.js";
|
|
7
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
8
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
9
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @summary Short summary of the component's intended use.
|
|
13
|
+
* @documentation https://shoelace.style/components/radio-button
|
|
14
|
+
* @status experimental
|
|
15
|
+
*
|
|
16
|
+
* @dependency sl-example
|
|
17
|
+
*
|
|
18
|
+
* @event sl-event-name - Emitted as an example.
|
|
19
|
+
*
|
|
20
|
+
* @slot - The default slot.
|
|
21
|
+
* @slot example - An example slot.
|
|
22
|
+
*
|
|
23
|
+
* @csspart base - The component's base wrapper.
|
|
24
|
+
*
|
|
25
|
+
* @cssproperty --example - An example CSS custom property.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
@customElement("pbo-radio-button")
|
|
29
|
+
export default class PboRadioButton extends LitElement {
|
|
30
|
+
static styles: CSSResultGroup = [componentStyles, styles];
|
|
31
|
+
private readonly hasSlotController = new HasSlotController(this, "[prefix]", "prefix", "suffix");
|
|
32
|
+
|
|
33
|
+
@query(".button") input: HTMLInputElement;
|
|
34
|
+
@query(".hidden-input") hiddenInput: HTMLInputElement;
|
|
35
|
+
|
|
36
|
+
@state() protected hasFocus = false;
|
|
37
|
+
|
|
38
|
+
@property({ type: Boolean, reflect: true }) checked = false;
|
|
39
|
+
@property() value: string;
|
|
40
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
41
|
+
|
|
42
|
+
@property({ reflect: true }) size: "small" | "medium" | "large" = "medium";
|
|
43
|
+
@property({ type: Boolean, reflect: true }) circle = false;
|
|
44
|
+
|
|
45
|
+
connectedCallback(): void {
|
|
46
|
+
super.connectedCallback();
|
|
47
|
+
this.setAttribute("role", "presentation");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private handleBlur() {}
|
|
51
|
+
|
|
52
|
+
private handleClick = () => {
|
|
53
|
+
console.log(this.isColorString(this.value));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
private handleFocus() {}
|
|
57
|
+
|
|
58
|
+
focus(options?: FocusOptions) {
|
|
59
|
+
this.input.focus(options);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
blur() {
|
|
63
|
+
this.input.blur();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private isColorString(value: string): boolean {
|
|
67
|
+
if (!value) return false;
|
|
68
|
+
|
|
69
|
+
// Check for hex colors
|
|
70
|
+
if (value.startsWith("#")) {
|
|
71
|
+
return /^#([A-Fa-f0-9]{3}){1,2}$/.test(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if it's a valid CSS color name or rgb/rgba/hsl/hsla value
|
|
75
|
+
const style = new Option().style;
|
|
76
|
+
style.color = value;
|
|
77
|
+
return style.color !== "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render() {
|
|
81
|
+
return html`
|
|
82
|
+
<div part="base" role="presentation">
|
|
83
|
+
<button
|
|
84
|
+
part="${`button${this.checked ? "button--checked" : ""}`}"
|
|
85
|
+
role="radio"
|
|
86
|
+
aria-checked="${this.checked}"
|
|
87
|
+
class=${classMap({
|
|
88
|
+
button: true,
|
|
89
|
+
"button--default": true,
|
|
90
|
+
"button--color-block": this.isColorString(this.value) ? true : false,
|
|
91
|
+
"button--small": this.size === "small",
|
|
92
|
+
"button--medium": this.size === "medium",
|
|
93
|
+
"button--large": this.size === "large",
|
|
94
|
+
"button--checked": this.checked,
|
|
95
|
+
"button--disabled": this.disabled,
|
|
96
|
+
"button--outline": true,
|
|
97
|
+
"button--has-label": this.hasSlotController.test("[default]"),
|
|
98
|
+
"button--has-prefix": this.hasSlotController.test("prefix"),
|
|
99
|
+
"button--has-suffix": this.hasSlotController.test("suffix"),
|
|
100
|
+
"button--circle": this.circle,
|
|
101
|
+
})}
|
|
102
|
+
type="button"
|
|
103
|
+
value=${ifDefined(this.value)}
|
|
104
|
+
@blur=${this.handleBlur}
|
|
105
|
+
@click=${this.handleClick}
|
|
106
|
+
@focus=${this.handleFocus}
|
|
107
|
+
style=${styleMap({
|
|
108
|
+
backgroundColor: this.isColorString(this.value) ? this.value : "",
|
|
109
|
+
})}
|
|
110
|
+
>
|
|
111
|
+
<slot name="prefix" part="prefix" class="button__prefix"></slot>
|
|
112
|
+
<slot part="label" class="button__label"></slot>
|
|
113
|
+
<slot name="suffix" part="suffix" class="button__suffix"></slot>
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
import buttonStyles from "../button/button.styles.js";
|
|
3
|
+
export default css`
|
|
4
|
+
${buttonStyles}
|
|
5
|
+
|
|
6
|
+
.button__prefix,
|
|
7
|
+
.button__suffix,
|
|
8
|
+
.button__label {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
position: relative;
|
|
11
|
+
align-items: center;
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
2
|
+
import { html, LitElement } from "lit";
|
|
3
|
+
import type { CSSResultGroup } from "lit";
|
|
4
|
+
import componentStyles from "../../styles/component.styles.js";
|
|
5
|
+
import styles from "./radio-group.styles.js";
|
|
6
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
7
|
+
import PairboElement, { PairboFormControl } from "../../internal/pairbo-element.js";
|
|
8
|
+
import {
|
|
9
|
+
customErrorValidityState,
|
|
10
|
+
FormControlController,
|
|
11
|
+
validValidityState,
|
|
12
|
+
valueMissingValidityState,
|
|
13
|
+
} from "../../internal/form.js";
|
|
14
|
+
import { HasSlotController } from "../../internal/slots.js";
|
|
15
|
+
import formControlStyles from "../../styles/form-control.styles.js";
|
|
16
|
+
import PboRadioButton from "../radio-button/radio-button.component.js";
|
|
17
|
+
import PboButtonGroup from "../button-group/button-group.component.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @summary Short summary of the component's intended use.
|
|
21
|
+
* @documentation https://shoelace.style/components/radio-group
|
|
22
|
+
* @status experimental
|
|
23
|
+
*
|
|
24
|
+
* @dependency sl-example
|
|
25
|
+
*
|
|
26
|
+
* @event sl-event-name - Emitted as an example.
|
|
27
|
+
*
|
|
28
|
+
* @slot - The default slot.
|
|
29
|
+
* @slot example - An example slot.
|
|
30
|
+
*
|
|
31
|
+
* @csspart base - The component's base wrapper.
|
|
32
|
+
*
|
|
33
|
+
* @cssproperty --example - An example CSS custom property.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
@customElement("pbo-radio-group")
|
|
37
|
+
export default class PboRadioGroup extends PairboElement implements PairboFormControl {
|
|
38
|
+
static styles: CSSResultGroup = [componentStyles, styles, formControlStyles];
|
|
39
|
+
static dependencies = { "pbo-button-group": PboButtonGroup };
|
|
40
|
+
|
|
41
|
+
protected readonly formControlController = new FormControlController(this);
|
|
42
|
+
private readonly hasSlotController = new HasSlotController(this, "help-text", "label");
|
|
43
|
+
private customValidityMessage = "";
|
|
44
|
+
private validationTimeout: number;
|
|
45
|
+
|
|
46
|
+
private _value = "";
|
|
47
|
+
@query("slot:not([name])") defaultSlot: HTMLSlotElement;
|
|
48
|
+
@query(".radio-group__validation-input") validationInput: HTMLInputElement;
|
|
49
|
+
|
|
50
|
+
@state() private hasButtonGroup = true;
|
|
51
|
+
@state() private errorMessage = "";
|
|
52
|
+
@state() defaultValue = "";
|
|
53
|
+
|
|
54
|
+
@property({ type: Boolean, reflect: true }) required = false;
|
|
55
|
+
@property() name = "option";
|
|
56
|
+
@property({ reflect: true })
|
|
57
|
+
get value() {
|
|
58
|
+
return this._value;
|
|
59
|
+
}
|
|
60
|
+
set value(value) {
|
|
61
|
+
const oldValue = this._value;
|
|
62
|
+
this._value = value;
|
|
63
|
+
if (this.hasUpdated && oldValue !== value) {
|
|
64
|
+
this.updateCheckedRadio();
|
|
65
|
+
}
|
|
66
|
+
this.requestUpdate("value", oldValue);
|
|
67
|
+
}
|
|
68
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
69
|
+
@property({ reflect: true }) form = "";
|
|
70
|
+
get validity() {
|
|
71
|
+
const isRequiredAndEmpty = this.required && !this.value;
|
|
72
|
+
const hasCustomValidityMessage = this.customValidityMessage !== "";
|
|
73
|
+
if (hasCustomValidityMessage) {
|
|
74
|
+
return customErrorValidityState;
|
|
75
|
+
} else if (isRequiredAndEmpty) {
|
|
76
|
+
return valueMissingValidityState;
|
|
77
|
+
}
|
|
78
|
+
return validValidityState;
|
|
79
|
+
}
|
|
80
|
+
validationMessage: string;
|
|
81
|
+
checkValidity() {
|
|
82
|
+
const isRequiredAndEmpty = this.required && !this.value;
|
|
83
|
+
const hasCustomValidityMessage = this.customValidityMessage !== "";
|
|
84
|
+
|
|
85
|
+
if (isRequiredAndEmpty || hasCustomValidityMessage) {
|
|
86
|
+
this.formControlController.emitInvalidEvent();
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
getForm(): HTMLFormElement | null {
|
|
92
|
+
return this.formControlController.getForm();
|
|
93
|
+
}
|
|
94
|
+
reportValidity(): boolean {
|
|
95
|
+
const isValid = this.validity.valid;
|
|
96
|
+
|
|
97
|
+
this.errorMessage = this.customValidityMessage || isValid ? "" : this.validationInput.validationMessage;
|
|
98
|
+
this.formControlController.setValidity(isValid);
|
|
99
|
+
this.validationInput.hidden = true;
|
|
100
|
+
clearTimeout(this.validationTimeout);
|
|
101
|
+
|
|
102
|
+
if (!isValid) {
|
|
103
|
+
// Show the browser's constraint validation message
|
|
104
|
+
this.validationInput.hidden = false;
|
|
105
|
+
this.validationInput.reportValidity();
|
|
106
|
+
this.validationTimeout = setTimeout(() => (this.validationInput.hidden = true), 10000) as unknown as number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return isValid;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setCustomValidity(message: string) {
|
|
113
|
+
this.customValidityMessage = message;
|
|
114
|
+
this.errorMessage = message;
|
|
115
|
+
this.validationInput.setCustomValidity(message);
|
|
116
|
+
this.formControlController.updateValidity();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private getAllRadios() {
|
|
120
|
+
return [...this.querySelectorAll<PboRadioButton>("pbo-radio, pbo-radio-button")];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private updateCheckedRadio() {
|
|
124
|
+
const radios = this.getAllRadios();
|
|
125
|
+
radios.forEach(radio => (radio.checked = radio.value === this.value));
|
|
126
|
+
this.formControlController.setValidity(this.validity.valid);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private handleRadioClick(event: MouseEvent) {
|
|
130
|
+
const target = (event.target as HTMLElement).closest<PboRadioButton>("pbo-radio-button")!;
|
|
131
|
+
const radios = this.getAllRadios();
|
|
132
|
+
const oldValue = this.value;
|
|
133
|
+
|
|
134
|
+
if (!target || target.disabled) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.value = target.value;
|
|
138
|
+
radios.forEach(radio => (radio.checked = radio === target));
|
|
139
|
+
|
|
140
|
+
if (this.value !== oldValue) {
|
|
141
|
+
this.emit("pbo-change");
|
|
142
|
+
this.emit("pbo-input");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// TODO: Implement keyboard navigation
|
|
147
|
+
private handleKeyDown(event: KeyboardEvent) {}
|
|
148
|
+
// TODO: Implement radio sync
|
|
149
|
+
private syncRadios() {}
|
|
150
|
+
|
|
151
|
+
private handleInvalid(event: Event) {
|
|
152
|
+
this.formControlController.setValidity(false);
|
|
153
|
+
this.formControlController.emitInvalidEvent(event);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
handleValueChange() {
|
|
157
|
+
if (this.hasUpdated) {
|
|
158
|
+
this.updateCheckedRadio();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
render() {
|
|
163
|
+
return html`
|
|
164
|
+
<fieldset
|
|
165
|
+
role="radiogroup"
|
|
166
|
+
part="form-control"
|
|
167
|
+
class=${classMap({
|
|
168
|
+
"form-control": true,
|
|
169
|
+
// "form-control--small": this.size === "small",
|
|
170
|
+
// "form-control--medium": this.size === "medium",
|
|
171
|
+
// "form-control--large": this.size === "large",
|
|
172
|
+
// "form-control--radio-group": true,
|
|
173
|
+
// "form-control--has-label": hasLabel,
|
|
174
|
+
// "form-control--has-help-text": hasHelpText,
|
|
175
|
+
})}
|
|
176
|
+
role="radiogroup"
|
|
177
|
+
aria-labelledby="label"
|
|
178
|
+
aria-describedby="help-text"
|
|
179
|
+
aria-errormessage="error-message"
|
|
180
|
+
>
|
|
181
|
+
<div part="form-control-input" class="form-control-input">
|
|
182
|
+
<div class="visually-hidden">
|
|
183
|
+
<div id="error-message" aria-live="assertive">${this.errorMessage}</div>
|
|
184
|
+
<label class="radio-group__validation">
|
|
185
|
+
<input
|
|
186
|
+
type="text"
|
|
187
|
+
class="radio-group__validation-input"
|
|
188
|
+
?required=${this.required}
|
|
189
|
+
tabindex="-1"
|
|
190
|
+
hidden
|
|
191
|
+
@invalid=${this.handleInvalid}
|
|
192
|
+
/>
|
|
193
|
+
</label>
|
|
194
|
+
</div>
|
|
195
|
+
<pbo-button-group>
|
|
196
|
+
<slot @slotchange=${this.syncRadios} @click=${this.handleRadioClick} @keydown=${this.handleKeyDown}></slot>
|
|
197
|
+
</pbo-button-group>
|
|
198
|
+
</div>
|
|
199
|
+
${this._value}
|
|
200
|
+
</fieldset>
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
|
|
3
|
+
export default css`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.visually-hidden {
|
|
9
|
+
position: absolute;
|
|
10
|
+
width: 1px;
|
|
11
|
+
height: 1px;
|
|
12
|
+
padding: 0;
|
|
13
|
+
margin: -1px;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
clip: rect(0, 0, 0, 0);
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
border: 0;
|
|
18
|
+
}
|
|
19
|
+
`;
|