@sondor-technology/sondor-components 1.0.0 → 1.0.1-develop.2
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 +8 -0
- package/dist/components/alert/alert.d.ts +13 -0
- package/dist/components/alert/alert.d.ts.map +1 -0
- package/dist/components/alert/alert.js +105 -0
- package/dist/components/badge/badge.d.ts +13 -0
- package/dist/components/badge/badge.d.ts.map +1 -0
- package/dist/components/badge/badge.js +101 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts +19 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb/breadcrumb.js +99 -0
- package/dist/components/button/button.d.ts +23 -0
- package/dist/components/button/button.d.ts.map +1 -0
- package/dist/components/button/button.js +304 -0
- package/dist/components/button-group/button-group.d.ts +6 -0
- package/dist/components/button-group/button-group.d.ts.map +1 -0
- package/dist/components/button-group/button-group.js +74 -0
- package/dist/components/card/card.d.ts +11 -0
- package/dist/components/card/card.d.ts.map +1 -0
- package/dist/components/card/card.js +54 -0
- package/dist/components/checkbox/checkbox.d.ts +28 -0
- package/dist/components/checkbox/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox/checkbox.js +247 -0
- package/dist/components/code-block/code-block.d.ts +20 -0
- package/dist/components/code-block/code-block.d.ts.map +1 -0
- package/dist/components/code-block/code-block.js +260 -0
- package/dist/components/content-card/content-card.d.ts +13 -0
- package/dist/components/content-card/content-card.d.ts.map +1 -0
- package/dist/components/content-card/content-card.js +140 -0
- package/dist/components/divider/divider.d.ts +16 -0
- package/dist/components/divider/divider.d.ts.map +1 -0
- package/dist/components/divider/divider.js +82 -0
- package/dist/components/drawer/drawer.d.ts +22 -0
- package/dist/components/drawer/drawer.d.ts.map +1 -0
- package/dist/components/drawer/drawer.js +226 -0
- package/dist/components/field/field.d.ts +11 -0
- package/dist/components/field/field.d.ts.map +1 -0
- package/dist/components/field/field.js +32 -0
- package/dist/components/footer/footer.d.ts +6 -0
- package/dist/components/footer/footer.d.ts.map +1 -0
- package/dist/components/footer/footer.js +65 -0
- package/dist/components/form/form-step.d.ts +13 -0
- package/dist/components/form/form-step.d.ts.map +1 -0
- package/dist/components/form/form-step.js +41 -0
- package/dist/components/form/form.d.ts +25 -0
- package/dist/components/form/form.d.ts.map +1 -0
- package/dist/components/form/form.js +358 -0
- package/dist/components/grid/grid-row.d.ts +17 -0
- package/dist/components/grid/grid-row.d.ts.map +1 -0
- package/dist/components/grid/grid-row.js +92 -0
- package/dist/components/grid/grid.d.ts +13 -0
- package/dist/components/grid/grid.d.ts.map +1 -0
- package/dist/components/grid/grid.js +42 -0
- package/dist/components/icon/icon-paths.d.ts +116 -0
- package/dist/components/icon/icon-paths.d.ts.map +1 -0
- package/dist/components/icon/icon-paths.js +121 -0
- package/dist/components/icon/icon.d.ts +16 -0
- package/dist/components/icon/icon.d.ts.map +1 -0
- package/dist/components/icon/icon.js +95 -0
- package/dist/components/input/input.d.ts +31 -0
- package/dist/components/input/input.d.ts.map +1 -0
- package/dist/components/input/input.js +268 -0
- package/dist/components/link/link.d.ts +16 -0
- package/dist/components/link/link.d.ts.map +1 -0
- package/dist/components/link/link.js +116 -0
- package/dist/components/metadata/metadata.d.ts +18 -0
- package/dist/components/metadata/metadata.d.ts.map +1 -0
- package/dist/components/metadata/metadata.js +71 -0
- package/dist/components/modal/modal.d.ts +24 -0
- package/dist/components/modal/modal.d.ts.map +1 -0
- package/dist/components/modal/modal.js +163 -0
- package/dist/components/page-layout/page-layout.d.ts +46 -0
- package/dist/components/page-layout/page-layout.d.ts.map +1 -0
- package/dist/components/page-layout/page-layout.js +697 -0
- package/dist/components/radio/radio.d.ts +16 -0
- package/dist/components/radio/radio.d.ts.map +1 -0
- package/dist/components/radio/radio.js +152 -0
- package/dist/components/radio-group/radio-group.d.ts +27 -0
- package/dist/components/radio-group/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group/radio-group.js +161 -0
- package/dist/components/router/router.d.ts +7 -0
- package/dist/components/router/router.d.ts.map +1 -0
- package/dist/components/router/router.js +37 -0
- package/dist/components/select/select.d.ts +16 -0
- package/dist/components/select/select.d.ts.map +1 -0
- package/dist/components/select/select.js +172 -0
- package/dist/components/skeleton/skeleton.d.ts +17 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton/skeleton.js +78 -0
- package/dist/components/switch/switch.d.ts +25 -0
- package/dist/components/switch/switch.d.ts.map +1 -0
- package/dist/components/switch/switch.js +185 -0
- package/dist/components/table/data-table.d.ts +72 -0
- package/dist/components/table/data-table.d.ts.map +1 -0
- package/dist/components/table/data-table.js +551 -0
- package/dist/components/table/table.d.ts +22 -0
- package/dist/components/table/table.d.ts.map +1 -0
- package/dist/components/table/table.js +167 -0
- package/dist/components/tabs/tab-panel.d.ts +8 -0
- package/dist/components/tabs/tab-panel.d.ts.map +1 -0
- package/dist/components/tabs/tab-panel.js +46 -0
- package/dist/components/tabs/tab.d.ts +9 -0
- package/dist/components/tabs/tab.d.ts.map +1 -0
- package/dist/components/tabs/tab.js +91 -0
- package/dist/components/tabs/tabs.d.ts +14 -0
- package/dist/components/tabs/tabs.d.ts.map +1 -0
- package/dist/components/tabs/tabs.js +85 -0
- package/dist/components/textarea/textarea.d.ts +36 -0
- package/dist/components/textarea/textarea.d.ts.map +1 -0
- package/dist/components/textarea/textarea.js +231 -0
- package/dist/components/theme-toggle/theme-toggle.d.ts +15 -0
- package/dist/components/theme-toggle/theme-toggle.d.ts.map +1 -0
- package/dist/components/theme-toggle/theme-toggle.js +130 -0
- package/dist/components/vertical-nav/vertical-nav-group.d.ts +9 -0
- package/dist/components/vertical-nav/vertical-nav-group.d.ts.map +1 -0
- package/dist/components/vertical-nav/vertical-nav-group.js +111 -0
- package/dist/components/vertical-nav/vertical-nav-item.d.ts +8 -0
- package/dist/components/vertical-nav/vertical-nav-item.d.ts.map +1 -0
- package/dist/components/vertical-nav/vertical-nav-item.js +65 -0
- package/dist/styles/index.css +171 -0
- package/dist/styles/reset.d.ts +2 -0
- package/dist/styles/reset.d.ts.map +1 -0
- package/dist/styles/reset.js +50 -0
- package/package.json +3 -2
- package/src/components/alert/alert.ts +100 -100
- package/src/components/badge/badge.ts +96 -96
- package/src/components/breadcrumb/breadcrumb.ts +98 -98
- package/src/components/button/button.ts +281 -281
- package/src/components/button-group/button-group.ts +67 -67
- package/src/components/card/card.ts +52 -52
- package/src/components/checkbox/checkbox.ts +238 -238
- package/src/components/code-block/code-block.ts +256 -256
- package/src/components/content-card/content-card.ts +133 -133
- package/src/components/divider/divider.ts +68 -68
- package/src/components/drawer/drawer.ts +217 -217
- package/src/components/field/field.ts +30 -30
- package/src/components/footer/footer.ts +58 -58
- package/src/components/form/form-step.ts +32 -32
- package/src/components/form/form.ts +376 -376
- package/src/components/grid/grid-row.ts +75 -75
- package/src/components/grid/grid.ts +36 -36
- package/src/components/icon/icon-paths.ts +124 -124
- package/src/components/icon/icon.ts +82 -82
- package/src/components/input/input.ts +248 -248
- package/src/components/link/link.ts +97 -97
- package/src/components/metadata/metadata.ts +66 -66
- package/src/components/modal/modal.ts +158 -158
- package/src/components/page-layout/page-layout.ts +704 -704
- package/src/components/radio/radio.ts +142 -142
- package/src/components/radio-group/radio-group.ts +157 -157
- package/src/components/router/router.ts +46 -46
- package/src/components/select/select.ts +161 -161
- package/src/components/skeleton/skeleton.ts +67 -67
- package/src/components/switch/switch.ts +181 -181
- package/src/components/table/data-table.ts +556 -556
- package/src/components/table/table.ts +153 -153
- package/src/components/tabs/tab-panel.ts +30 -30
- package/src/components/tabs/tab.ts +76 -76
- package/src/components/tabs/tabs.ts +88 -88
- package/src/components/textarea/textarea.ts +215 -215
- package/src/components/theme-toggle/theme-toggle.ts +131 -131
- package/src/components/vertical-nav/vertical-nav-group.ts +96 -96
- package/src/components/vertical-nav/vertical-nav-item.ts +49 -49
- package/src/styles/globals.css +58 -58
- package/src/styles/index.css +80 -80
- package/src/styles/reset.ts +50 -50
- package/src/styles/theme.css +58 -58
- package/src/styles/typography.css +25 -25
- package/tsconfig.json +15 -15
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import { LitElement, html, css } from 'lit';
|
|
2
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
-
import { iconPaths, IconName } from './icon-paths.js';
|
|
4
|
-
import { resetStyles } from '../../styles/reset.js';
|
|
5
|
-
|
|
6
|
-
@customElement('st-icon')
|
|
7
|
-
export class StIcon extends LitElement {
|
|
8
|
-
@property({ type: String }) name: IconName | string = '';
|
|
9
|
-
@property({ type: String }) variant: 'outline' | 'solid' = 'outline';
|
|
10
|
-
|
|
11
|
-
// Semantic Colors
|
|
12
|
-
@property({ type: String, reflect: true }) color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' = 'default';
|
|
13
|
-
|
|
14
|
-
// Standardized T-Shirt Sizes
|
|
15
|
-
@property({ type: String, reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'md';
|
|
16
|
-
|
|
17
|
-
static styles = [
|
|
18
|
-
resetStyles,
|
|
19
|
-
css`
|
|
20
|
-
:host {
|
|
21
|
-
display: inline-flex;
|
|
22
|
-
align-items: center;
|
|
23
|
-
justify-content: center;
|
|
24
|
-
color: inherit;
|
|
25
|
-
|
|
26
|
-
/* Defaults to md (1.5rem) if no size attribute is matched */
|
|
27
|
-
width: var(--icon-size, 1.5rem);
|
|
28
|
-
height: var(--icon-size, 1.5rem);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/* ==========================================================
|
|
32
|
-
Size Variants
|
|
33
|
-
========================================================== */
|
|
34
|
-
:host([size="sm"]) { --icon-size: 1.25rem; } /* 20px - Matches small buttons/inputs */
|
|
35
|
-
:host([size="md"]) { --icon-size: 1.5rem; } /* 24px - Default standard size */
|
|
36
|
-
:host([size="lg"]) { --icon-size: 2rem; } /* 32px - Large callouts */
|
|
37
|
-
:host([size="xl"]) { --icon-size: 2.5rem; } /* 40px - Hero sections/Cards */
|
|
38
|
-
|
|
39
|
-
/* ==========================================================
|
|
40
|
-
Color Variants
|
|
41
|
-
========================================================== */
|
|
42
|
-
:host([color="primary"]) { color: var(--st-color-primary); }
|
|
43
|
-
:host([color="secondary"]) { color: var(--st-color-secondary); }
|
|
44
|
-
:host([color="success"]) { color: var(--st-color-success); }
|
|
45
|
-
:host([color="warning"]) { color: var(--st-color-warning); }
|
|
46
|
-
:host([color="error"]) { color: var(--st-color-error); }
|
|
47
|
-
:host([color="info"]) { color: var(--st-color-info); }
|
|
48
|
-
|
|
49
|
-
svg {
|
|
50
|
-
width: 100%;
|
|
51
|
-
height: 100%;
|
|
52
|
-
display: block;
|
|
53
|
-
}
|
|
54
|
-
`
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
render() {
|
|
58
|
-
const iconData = (iconPaths as Record<string, any>)[this.name];
|
|
59
|
-
if (!iconData) return html``;
|
|
60
|
-
|
|
61
|
-
const pathData = iconData[this.variant] || iconData.outline;
|
|
62
|
-
|
|
63
|
-
return html`
|
|
64
|
-
<svg
|
|
65
|
-
viewBox="0 0 24 24"
|
|
66
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
67
|
-
fill="${this.variant === 'solid' ? 'currentColor' : 'none'}"
|
|
68
|
-
stroke="${this.variant === 'outline' ? 'currentColor' : 'none'}"
|
|
69
|
-
stroke-width="${this.variant === 'outline' ? '2' : '0'}"
|
|
70
|
-
stroke-linecap="round"
|
|
71
|
-
stroke-linejoin="round"
|
|
72
|
-
>
|
|
73
|
-
${pathData}
|
|
74
|
-
</svg>
|
|
75
|
-
`;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
declare global {
|
|
80
|
-
interface HTMLElementTagNameMap {
|
|
81
|
-
'st-icon': StIcon;
|
|
82
|
-
}
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { iconPaths, IconName } from './icon-paths.js';
|
|
4
|
+
import { resetStyles } from '../../styles/reset.js';
|
|
5
|
+
|
|
6
|
+
@customElement('st-icon')
|
|
7
|
+
export class StIcon extends LitElement {
|
|
8
|
+
@property({ type: String }) name: IconName | string = '';
|
|
9
|
+
@property({ type: String }) variant: 'outline' | 'solid' = 'outline';
|
|
10
|
+
|
|
11
|
+
// Semantic Colors
|
|
12
|
+
@property({ type: String, reflect: true }) color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' = 'default';
|
|
13
|
+
|
|
14
|
+
// Standardized T-Shirt Sizes
|
|
15
|
+
@property({ type: String, reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'md';
|
|
16
|
+
|
|
17
|
+
static styles = [
|
|
18
|
+
resetStyles,
|
|
19
|
+
css`
|
|
20
|
+
:host {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
color: inherit;
|
|
25
|
+
|
|
26
|
+
/* Defaults to md (1.5rem) if no size attribute is matched */
|
|
27
|
+
width: var(--icon-size, 1.5rem);
|
|
28
|
+
height: var(--icon-size, 1.5rem);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ==========================================================
|
|
32
|
+
Size Variants
|
|
33
|
+
========================================================== */
|
|
34
|
+
:host([size="sm"]) { --icon-size: 1.25rem; } /* 20px - Matches small buttons/inputs */
|
|
35
|
+
:host([size="md"]) { --icon-size: 1.5rem; } /* 24px - Default standard size */
|
|
36
|
+
:host([size="lg"]) { --icon-size: 2rem; } /* 32px - Large callouts */
|
|
37
|
+
:host([size="xl"]) { --icon-size: 2.5rem; } /* 40px - Hero sections/Cards */
|
|
38
|
+
|
|
39
|
+
/* ==========================================================
|
|
40
|
+
Color Variants
|
|
41
|
+
========================================================== */
|
|
42
|
+
:host([color="primary"]) { color: var(--st-color-primary); }
|
|
43
|
+
:host([color="secondary"]) { color: var(--st-color-secondary); }
|
|
44
|
+
:host([color="success"]) { color: var(--st-color-success); }
|
|
45
|
+
:host([color="warning"]) { color: var(--st-color-warning); }
|
|
46
|
+
:host([color="error"]) { color: var(--st-color-error); }
|
|
47
|
+
:host([color="info"]) { color: var(--st-color-info); }
|
|
48
|
+
|
|
49
|
+
svg {
|
|
50
|
+
width: 100%;
|
|
51
|
+
height: 100%;
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
`
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
render() {
|
|
58
|
+
const iconData = (iconPaths as Record<string, any>)[this.name];
|
|
59
|
+
if (!iconData) return html``;
|
|
60
|
+
|
|
61
|
+
const pathData = iconData[this.variant] || iconData.outline;
|
|
62
|
+
|
|
63
|
+
return html`
|
|
64
|
+
<svg
|
|
65
|
+
viewBox="0 0 24 24"
|
|
66
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
67
|
+
fill="${this.variant === 'solid' ? 'currentColor' : 'none'}"
|
|
68
|
+
stroke="${this.variant === 'outline' ? 'currentColor' : 'none'}"
|
|
69
|
+
stroke-width="${this.variant === 'outline' ? '2' : '0'}"
|
|
70
|
+
stroke-linecap="round"
|
|
71
|
+
stroke-linejoin="round"
|
|
72
|
+
>
|
|
73
|
+
${pathData}
|
|
74
|
+
</svg>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare global {
|
|
80
|
+
interface HTMLElementTagNameMap {
|
|
81
|
+
'st-icon': StIcon;
|
|
82
|
+
}
|
|
83
83
|
}
|
|
@@ -1,248 +1,248 @@
|
|
|
1
|
-
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
|
-
import {customElement, property, query} from "lit/decorators.js";
|
|
3
|
-
import {ifDefined} from "lit/directives/if-defined.js"; // Helper for optional attributes
|
|
4
|
-
import {resetStyles} from "../../styles/reset.js";
|
|
5
|
-
|
|
6
|
-
export type InputType = "text" | "password" | "email" | "number" | "search" | "url";
|
|
7
|
-
|
|
8
|
-
@customElement("st-input")
|
|
9
|
-
export class StInput extends LitElement {
|
|
10
|
-
static formAssociated = true;
|
|
11
|
-
private _internals: ElementInternals;
|
|
12
|
-
|
|
13
|
-
@query("input") private _inputEl!: HTMLInputElement;
|
|
14
|
-
@property({type: String, reflect: true}) name = "";
|
|
15
|
-
@property({type: String}) label = "";
|
|
16
|
-
@property({type: String}) value = "";
|
|
17
|
-
@property({type: String}) placeholder = "";
|
|
18
|
-
@property({type: String}) type: InputType = "text";
|
|
19
|
-
@property({attribute: "help-text"}) helpText = "";
|
|
20
|
-
|
|
21
|
-
// Validation Properties
|
|
22
|
-
@property({type: Boolean, reflect: true}) required = false;
|
|
23
|
-
@property({type: Number}) minlength?: number;
|
|
24
|
-
@property({type: Number}) maxlength?: number;
|
|
25
|
-
@property({type: String}) pattern?: string;
|
|
26
|
-
|
|
27
|
-
@property({type: Boolean, reflect: true}) disabled = false;
|
|
28
|
-
@property({type: Boolean, reflect: true}) invalid = false;
|
|
29
|
-
@property({type: Boolean, reflect: true}) readonly = false;
|
|
30
|
-
|
|
31
|
-
constructor() {
|
|
32
|
-
super();
|
|
33
|
-
this._internals = this.attachInternals();
|
|
34
|
-
|
|
35
|
-
// 1. Trigger error state if the user clicks away and the field is invalid
|
|
36
|
-
this.addEventListener("focusout", () => {
|
|
37
|
-
this.invalid = !this._inputEl.validity.valid;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// 2. Trigger error state if the st-form tries to submit and fails
|
|
41
|
-
this.addEventListener("invalid", () => {
|
|
42
|
-
this.invalid = true;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static styles = [
|
|
47
|
-
resetStyles,
|
|
48
|
-
css`
|
|
49
|
-
:host {
|
|
50
|
-
display: block;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.form-control {
|
|
54
|
-
display: flex;
|
|
55
|
-
flex-direction: column;
|
|
56
|
-
gap: var(--st-spacing-xs);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
label {
|
|
60
|
-
font-size: var(--st-font-size-sm);
|
|
61
|
-
font-weight: var(--st-font-weight-medium);
|
|
62
|
-
color: var(--st-color-text);
|
|
63
|
-
margin-bottom: 0.5em;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/* 1. The Wrapper acts as the visual input field */
|
|
67
|
-
.input-wrapper {
|
|
68
|
-
display: flex;
|
|
69
|
-
align-items: center;
|
|
70
|
-
gap: var(--st-spacing-sm);
|
|
71
|
-
width: 100%;
|
|
72
|
-
|
|
73
|
-
background-color: var(--st-color-bg);
|
|
74
|
-
border: 1px solid var(--st-color-border);
|
|
75
|
-
border-radius: 4px;
|
|
76
|
-
|
|
77
|
-
/* Horizontal padding on the wrapper, vertical on the input */
|
|
78
|
-
padding: 0 var(--st-spacing-md);
|
|
79
|
-
transition:
|
|
80
|
-
border-color 0.2s ease,
|
|
81
|
-
box-shadow 0.2s ease;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* 2. Apply focus ring to the wrapper when the internal input is focused */
|
|
85
|
-
.input-wrapper:focus-within {
|
|
86
|
-
border-color: var(--st-color-primary);
|
|
87
|
-
box-shadow: 0 0 0 1px var(--st-color-primary);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/* 3. The actual input becomes transparent and fills the wrapper */
|
|
91
|
-
input {
|
|
92
|
-
flex: 1;
|
|
93
|
-
width: 100%;
|
|
94
|
-
border: none;
|
|
95
|
-
background: transparent;
|
|
96
|
-
outline: none;
|
|
97
|
-
|
|
98
|
-
/* Vertical padding stretches the wrapper */
|
|
99
|
-
padding: var(--st-spacing-sm) 0;
|
|
100
|
-
font-size: var(--st-font-size-base);
|
|
101
|
-
color: var(--st-color-text);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
input::placeholder {
|
|
105
|
-
color: var(--st-color-text-muted);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* Slotted Icons */
|
|
109
|
-
::slotted(svg) {
|
|
110
|
-
width: 1.25em;
|
|
111
|
-
height: 1.25em;
|
|
112
|
-
/* Default icons to the muted text color */
|
|
113
|
-
color: var(--st-color-text-muted);
|
|
114
|
-
fill: currentColor;
|
|
115
|
-
flex-shrink: 0;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* Interactive Slotted Elements (like clear buttons) */
|
|
119
|
-
::slotted(st-button) {
|
|
120
|
-
/* Pull the button back slightly so it aligns nicely with the input padding */
|
|
121
|
-
margin-right: calc(var(--st-spacing-sm) * -1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/* Invalid State */
|
|
125
|
-
:host([invalid]) .input-wrapper {
|
|
126
|
-
border-color: var(--st-color-error);
|
|
127
|
-
}
|
|
128
|
-
:host([invalid]) .input-wrapper:focus-within {
|
|
129
|
-
box-shadow: 0 0 0 1px var(--st-color-error);
|
|
130
|
-
}
|
|
131
|
-
:host([invalid]) .help-text {
|
|
132
|
-
color: var(--st-color-error);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/* Disabled & Readonly States */
|
|
136
|
-
:host([disabled]) .input-wrapper {
|
|
137
|
-
opacity: 0.5;
|
|
138
|
-
cursor: not-allowed;
|
|
139
|
-
background-color: var(--st-color-surface);
|
|
140
|
-
}
|
|
141
|
-
:host([disabled]) input {
|
|
142
|
-
cursor: not-allowed;
|
|
143
|
-
}
|
|
144
|
-
:host([readonly]) .input-wrapper {
|
|
145
|
-
background-color: var(--st-color-surface);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.help-text {
|
|
149
|
-
font-size: var(--st-font-size-xs);
|
|
150
|
-
color: var(--st-color-text-muted);
|
|
151
|
-
margin: 0;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
:host([invalid]) .input-wrapper {
|
|
155
|
-
border-color: var(--st-color-error);
|
|
156
|
-
}
|
|
157
|
-
:host([invalid]) .help-text {
|
|
158
|
-
color: var(--st-color-error);
|
|
159
|
-
}
|
|
160
|
-
`,
|
|
161
|
-
];
|
|
162
|
-
|
|
163
|
-
override updated(changedProperties: PropertyValues<this>) {
|
|
164
|
-
super.updated(changedProperties);
|
|
165
|
-
|
|
166
|
-
// Sync the value to the form engine
|
|
167
|
-
if (changedProperties.has("value")) {
|
|
168
|
-
this._internals.setFormValue(this.value);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Sync validity whenever properties change (like value or required status)
|
|
172
|
-
this._syncValidity();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private _syncValidity() {
|
|
176
|
-
if (!this._inputEl) return;
|
|
177
|
-
|
|
178
|
-
// setValidity arguments: (ValidityState object, Custom Error Message, Anchor Element for Tooltip)
|
|
179
|
-
this._internals.setValidity(
|
|
180
|
-
this._inputEl.validity,
|
|
181
|
-
this._inputEl.validationMessage,
|
|
182
|
-
this._inputEl,
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
formResetCallback() {
|
|
187
|
-
this.value = "";
|
|
188
|
-
this._internals.setFormValue("");
|
|
189
|
-
// Need to wait for the render cycle to finish before checking validity on reset
|
|
190
|
-
this.updateComplete.then(() => this._syncValidity());
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Expose standard native methods so external scripts can call them on the component
|
|
194
|
-
checkValidity() {
|
|
195
|
-
return this._internals.checkValidity();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
reportValidity() {
|
|
199
|
-
return this._internals.reportValidity();
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private _handleInput(e: Event) {
|
|
203
|
-
const target = e.target as HTMLTextAreaElement;
|
|
204
|
-
this.value = target.value;
|
|
205
|
-
|
|
206
|
-
if (this.invalid && target.validity.valid) {
|
|
207
|
-
this.invalid = false;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private _handleChange(e: Event) {
|
|
214
|
-
this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
render() {
|
|
218
|
-
return html`
|
|
219
|
-
<div class="form-control">
|
|
220
|
-
${this.label ? html`<label for="input">${this.label}</label>` : ""}
|
|
221
|
-
|
|
222
|
-
<div class="input-wrapper">
|
|
223
|
-
<slot name="prefix"></slot>
|
|
224
|
-
|
|
225
|
-
<input
|
|
226
|
-
id="input"
|
|
227
|
-
type=${this.type}
|
|
228
|
-
.name=${this.name}
|
|
229
|
-
.value=${this.value}
|
|
230
|
-
placeholder=${this.placeholder}
|
|
231
|
-
?disabled=${this.disabled}
|
|
232
|
-
?readonly=${this.readonly}
|
|
233
|
-
?required=${this.required}
|
|
234
|
-
minlength=${ifDefined(this.minlength)}
|
|
235
|
-
maxlength=${ifDefined(this.maxlength)}
|
|
236
|
-
pattern=${ifDefined(this.pattern)}
|
|
237
|
-
@input=${this._handleInput}
|
|
238
|
-
@change=${this._handleChange}
|
|
239
|
-
/>
|
|
240
|
-
|
|
241
|
-
<slot name="suffix"></slot>
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
${this.helpText ? html`<div class="help-text">${this.helpText}</div>` : ""}
|
|
245
|
-
</div>
|
|
246
|
-
`;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
1
|
+
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
|
+
import {customElement, property, query} from "lit/decorators.js";
|
|
3
|
+
import {ifDefined} from "lit/directives/if-defined.js"; // Helper for optional attributes
|
|
4
|
+
import {resetStyles} from "../../styles/reset.js";
|
|
5
|
+
|
|
6
|
+
export type InputType = "text" | "password" | "email" | "number" | "search" | "url";
|
|
7
|
+
|
|
8
|
+
@customElement("st-input")
|
|
9
|
+
export class StInput extends LitElement {
|
|
10
|
+
static formAssociated = true;
|
|
11
|
+
private _internals: ElementInternals;
|
|
12
|
+
|
|
13
|
+
@query("input") private _inputEl!: HTMLInputElement;
|
|
14
|
+
@property({type: String, reflect: true}) name = "";
|
|
15
|
+
@property({type: String}) label = "";
|
|
16
|
+
@property({type: String}) value = "";
|
|
17
|
+
@property({type: String}) placeholder = "";
|
|
18
|
+
@property({type: String}) type: InputType = "text";
|
|
19
|
+
@property({attribute: "help-text"}) helpText = "";
|
|
20
|
+
|
|
21
|
+
// Validation Properties
|
|
22
|
+
@property({type: Boolean, reflect: true}) required = false;
|
|
23
|
+
@property({type: Number}) minlength?: number;
|
|
24
|
+
@property({type: Number}) maxlength?: number;
|
|
25
|
+
@property({type: String}) pattern?: string;
|
|
26
|
+
|
|
27
|
+
@property({type: Boolean, reflect: true}) disabled = false;
|
|
28
|
+
@property({type: Boolean, reflect: true}) invalid = false;
|
|
29
|
+
@property({type: Boolean, reflect: true}) readonly = false;
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this._internals = this.attachInternals();
|
|
34
|
+
|
|
35
|
+
// 1. Trigger error state if the user clicks away and the field is invalid
|
|
36
|
+
this.addEventListener("focusout", () => {
|
|
37
|
+
this.invalid = !this._inputEl.validity.valid;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 2. Trigger error state if the st-form tries to submit and fails
|
|
41
|
+
this.addEventListener("invalid", () => {
|
|
42
|
+
this.invalid = true;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static styles = [
|
|
47
|
+
resetStyles,
|
|
48
|
+
css`
|
|
49
|
+
:host {
|
|
50
|
+
display: block;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.form-control {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
gap: var(--st-spacing-xs);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
label {
|
|
60
|
+
font-size: var(--st-font-size-sm);
|
|
61
|
+
font-weight: var(--st-font-weight-medium);
|
|
62
|
+
color: var(--st-color-text);
|
|
63
|
+
margin-bottom: 0.5em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* 1. The Wrapper acts as the visual input field */
|
|
67
|
+
.input-wrapper {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: var(--st-spacing-sm);
|
|
71
|
+
width: 100%;
|
|
72
|
+
|
|
73
|
+
background-color: var(--st-color-bg);
|
|
74
|
+
border: 1px solid var(--st-color-border);
|
|
75
|
+
border-radius: 4px;
|
|
76
|
+
|
|
77
|
+
/* Horizontal padding on the wrapper, vertical on the input */
|
|
78
|
+
padding: 0 var(--st-spacing-md);
|
|
79
|
+
transition:
|
|
80
|
+
border-color 0.2s ease,
|
|
81
|
+
box-shadow 0.2s ease;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* 2. Apply focus ring to the wrapper when the internal input is focused */
|
|
85
|
+
.input-wrapper:focus-within {
|
|
86
|
+
border-color: var(--st-color-primary);
|
|
87
|
+
box-shadow: 0 0 0 1px var(--st-color-primary);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* 3. The actual input becomes transparent and fills the wrapper */
|
|
91
|
+
input {
|
|
92
|
+
flex: 1;
|
|
93
|
+
width: 100%;
|
|
94
|
+
border: none;
|
|
95
|
+
background: transparent;
|
|
96
|
+
outline: none;
|
|
97
|
+
|
|
98
|
+
/* Vertical padding stretches the wrapper */
|
|
99
|
+
padding: var(--st-spacing-sm) 0;
|
|
100
|
+
font-size: var(--st-font-size-base);
|
|
101
|
+
color: var(--st-color-text);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
input::placeholder {
|
|
105
|
+
color: var(--st-color-text-muted);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Slotted Icons */
|
|
109
|
+
::slotted(svg) {
|
|
110
|
+
width: 1.25em;
|
|
111
|
+
height: 1.25em;
|
|
112
|
+
/* Default icons to the muted text color */
|
|
113
|
+
color: var(--st-color-text-muted);
|
|
114
|
+
fill: currentColor;
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Interactive Slotted Elements (like clear buttons) */
|
|
119
|
+
::slotted(st-button) {
|
|
120
|
+
/* Pull the button back slightly so it aligns nicely with the input padding */
|
|
121
|
+
margin-right: calc(var(--st-spacing-sm) * -1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Invalid State */
|
|
125
|
+
:host([invalid]) .input-wrapper {
|
|
126
|
+
border-color: var(--st-color-error);
|
|
127
|
+
}
|
|
128
|
+
:host([invalid]) .input-wrapper:focus-within {
|
|
129
|
+
box-shadow: 0 0 0 1px var(--st-color-error);
|
|
130
|
+
}
|
|
131
|
+
:host([invalid]) .help-text {
|
|
132
|
+
color: var(--st-color-error);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Disabled & Readonly States */
|
|
136
|
+
:host([disabled]) .input-wrapper {
|
|
137
|
+
opacity: 0.5;
|
|
138
|
+
cursor: not-allowed;
|
|
139
|
+
background-color: var(--st-color-surface);
|
|
140
|
+
}
|
|
141
|
+
:host([disabled]) input {
|
|
142
|
+
cursor: not-allowed;
|
|
143
|
+
}
|
|
144
|
+
:host([readonly]) .input-wrapper {
|
|
145
|
+
background-color: var(--st-color-surface);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.help-text {
|
|
149
|
+
font-size: var(--st-font-size-xs);
|
|
150
|
+
color: var(--st-color-text-muted);
|
|
151
|
+
margin: 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
:host([invalid]) .input-wrapper {
|
|
155
|
+
border-color: var(--st-color-error);
|
|
156
|
+
}
|
|
157
|
+
:host([invalid]) .help-text {
|
|
158
|
+
color: var(--st-color-error);
|
|
159
|
+
}
|
|
160
|
+
`,
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
override updated(changedProperties: PropertyValues<this>) {
|
|
164
|
+
super.updated(changedProperties);
|
|
165
|
+
|
|
166
|
+
// Sync the value to the form engine
|
|
167
|
+
if (changedProperties.has("value")) {
|
|
168
|
+
this._internals.setFormValue(this.value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Sync validity whenever properties change (like value or required status)
|
|
172
|
+
this._syncValidity();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private _syncValidity() {
|
|
176
|
+
if (!this._inputEl) return;
|
|
177
|
+
|
|
178
|
+
// setValidity arguments: (ValidityState object, Custom Error Message, Anchor Element for Tooltip)
|
|
179
|
+
this._internals.setValidity(
|
|
180
|
+
this._inputEl.validity,
|
|
181
|
+
this._inputEl.validationMessage,
|
|
182
|
+
this._inputEl,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
formResetCallback() {
|
|
187
|
+
this.value = "";
|
|
188
|
+
this._internals.setFormValue("");
|
|
189
|
+
// Need to wait for the render cycle to finish before checking validity on reset
|
|
190
|
+
this.updateComplete.then(() => this._syncValidity());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Expose standard native methods so external scripts can call them on the component
|
|
194
|
+
checkValidity() {
|
|
195
|
+
return this._internals.checkValidity();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
reportValidity() {
|
|
199
|
+
return this._internals.reportValidity();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private _handleInput(e: Event) {
|
|
203
|
+
const target = e.target as HTMLTextAreaElement;
|
|
204
|
+
this.value = target.value;
|
|
205
|
+
|
|
206
|
+
if (this.invalid && target.validity.valid) {
|
|
207
|
+
this.invalid = false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _handleChange(e: Event) {
|
|
214
|
+
this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
render() {
|
|
218
|
+
return html`
|
|
219
|
+
<div class="form-control">
|
|
220
|
+
${this.label ? html`<label for="input">${this.label}</label>` : ""}
|
|
221
|
+
|
|
222
|
+
<div class="input-wrapper">
|
|
223
|
+
<slot name="prefix"></slot>
|
|
224
|
+
|
|
225
|
+
<input
|
|
226
|
+
id="input"
|
|
227
|
+
type=${this.type}
|
|
228
|
+
.name=${this.name}
|
|
229
|
+
.value=${this.value}
|
|
230
|
+
placeholder=${this.placeholder}
|
|
231
|
+
?disabled=${this.disabled}
|
|
232
|
+
?readonly=${this.readonly}
|
|
233
|
+
?required=${this.required}
|
|
234
|
+
minlength=${ifDefined(this.minlength)}
|
|
235
|
+
maxlength=${ifDefined(this.maxlength)}
|
|
236
|
+
pattern=${ifDefined(this.pattern)}
|
|
237
|
+
@input=${this._handleInput}
|
|
238
|
+
@change=${this._handleChange}
|
|
239
|
+
/>
|
|
240
|
+
|
|
241
|
+
<slot name="suffix"></slot>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
${this.helpText ? html`<div class="help-text">${this.helpText}</div>` : ""}
|
|
245
|
+
</div>
|
|
246
|
+
`;
|
|
247
|
+
}
|
|
248
|
+
}
|