@sondor-technology/sondor-components 1.0.0-1165-initial-setup.11
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 +192 -0
- package/src/components/alert/alert.ts +101 -0
- package/src/components/badge/badge.ts +97 -0
- package/src/components/breadcrumb/breadcrumb.ts +99 -0
- package/src/components/button/button.ts +281 -0
- package/src/components/button-group/button-group.ts +67 -0
- package/src/components/card/card.ts +53 -0
- package/src/components/checkbox/checkbox.ts +238 -0
- package/src/components/code-block/code-block.ts +257 -0
- package/src/components/content-card/content-card.ts +134 -0
- package/src/components/divider/divider.ts +69 -0
- package/src/components/drawer/drawer.ts +218 -0
- package/src/components/field/field.ts +31 -0
- package/src/components/footer/footer.ts +58 -0
- package/src/components/form/form-step.ts +32 -0
- package/src/components/form/form.ts +376 -0
- package/src/components/grid/grid-row.ts +76 -0
- package/src/components/grid/grid.ts +37 -0
- package/src/components/icon/icon-paths.ts +124 -0
- package/src/components/icon/icon.ts +83 -0
- package/src/components/input/input.ts +248 -0
- package/src/components/link/link.ts +98 -0
- package/src/components/metadata/metadata.ts +67 -0
- package/src/components/modal/modal.ts +159 -0
- package/src/components/page-layout/page-layout.ts +705 -0
- package/src/components/radio/radio.ts +142 -0
- package/src/components/radio-group/radio-group.ts +157 -0
- package/src/components/router/router.ts +47 -0
- package/src/components/select/select.ts +162 -0
- package/src/components/skeleton/skeleton.ts +68 -0
- package/src/components/switch/switch.ts +181 -0
- package/src/components/table/data-table.ts +557 -0
- package/src/components/table/table.ts +154 -0
- package/src/components/tabs/tab-panel.ts +31 -0
- package/src/components/tabs/tab.ts +77 -0
- package/src/components/tabs/tabs.ts +88 -0
- package/src/components/textarea/textarea.ts +215 -0
- package/src/components/theme-toggle/theme-toggle.ts +131 -0
- package/src/components/vertical-nav/vertical-nav-group.ts +97 -0
- package/src/components/vertical-nav/vertical-nav-item.ts +50 -0
- package/src/styles/globals.css +58 -0
- package/src/styles/index.css +80 -0
- package/src/styles/reset.ts +51 -0
- package/src/styles/theme.css +59 -0
- package/src/styles/typography.css +26 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {LitElement, html, css} from "lit";
|
|
2
|
+
import {customElement, property} from "lit/decorators.js";
|
|
3
|
+
import {resetStyles} from "../../styles/reset.js";
|
|
4
|
+
|
|
5
|
+
@customElement("st-radio")
|
|
6
|
+
export class StRadio extends LitElement {
|
|
7
|
+
@property({type: String}) value = "";
|
|
8
|
+
@property({type: Boolean, reflect: true}) checked = false;
|
|
9
|
+
@property({type: Boolean, reflect: true}) disabled = false;
|
|
10
|
+
|
|
11
|
+
@property({attribute: "aria-label"}) ariaLabel = "";
|
|
12
|
+
|
|
13
|
+
static styles = [
|
|
14
|
+
resetStyles,
|
|
15
|
+
css`
|
|
16
|
+
:host {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
outline: none;
|
|
19
|
+
user-select: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
label {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: var(--st-spacing-sm);
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
color: var(--st-color-text);
|
|
28
|
+
font-size: var(--st-font-size-base);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Hide the native input visually, but keep it accessible for screen readers */
|
|
32
|
+
input {
|
|
33
|
+
position: absolute;
|
|
34
|
+
width: 1px;
|
|
35
|
+
height: 1px;
|
|
36
|
+
padding: 0;
|
|
37
|
+
margin: -1px;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
clip: rect(0, 0, 0, 0);
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
border-width: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Custom Radio Circle */
|
|
45
|
+
.control {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
width: 1.25rem;
|
|
50
|
+
height: 1.25rem;
|
|
51
|
+
border: 1px solid var(--st-color-border);
|
|
52
|
+
border-radius: 50%;
|
|
53
|
+
background-color: var(--st-color-bg);
|
|
54
|
+
transition: all 0.2s ease;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Inner Dot (Hidden by default) */
|
|
59
|
+
.control::after {
|
|
60
|
+
content: "";
|
|
61
|
+
width: 0.625rem;
|
|
62
|
+
height: 0.625rem;
|
|
63
|
+
border-radius: 50%;
|
|
64
|
+
background-color: var(--st-color-bg); /* Matches the dot color to background initially */
|
|
65
|
+
transition:
|
|
66
|
+
transform 0.2s ease,
|
|
67
|
+
background-color 0.2s ease;
|
|
68
|
+
transform: scale(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Hover State */
|
|
72
|
+
label:hover .control {
|
|
73
|
+
border-color: var(--st-color-primary-hover);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Focus State (Driven by the hidden input) */
|
|
77
|
+
input:focus-visible + .control {
|
|
78
|
+
box-shadow:
|
|
79
|
+
0 0 0 2px var(--st-color-bg),
|
|
80
|
+
0 0 0 4px var(--st-color-primary);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Checked State */
|
|
84
|
+
:host([checked]) .control {
|
|
85
|
+
border-color: var(--st-color-primary);
|
|
86
|
+
background-color: var(--st-color-primary);
|
|
87
|
+
}
|
|
88
|
+
:host([checked]) .control::after {
|
|
89
|
+
background-color: var(--st-white); /* White dot inside the primary circle */
|
|
90
|
+
transform: scale(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Disabled State */
|
|
94
|
+
:host([disabled]) {
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
}
|
|
97
|
+
:host([disabled]) label {
|
|
98
|
+
opacity: 0.5;
|
|
99
|
+
cursor: not-allowed;
|
|
100
|
+
}
|
|
101
|
+
`,
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
private _handleChange(e: Event) {
|
|
105
|
+
// Prevent the native change event from leaking out
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
|
|
108
|
+
if (!this.disabled) {
|
|
109
|
+
// Dispatch a custom event to tell the parent group this radio was selected
|
|
110
|
+
this.dispatchEvent(
|
|
111
|
+
new CustomEvent("st-radio-select", {
|
|
112
|
+
detail: {value: this.value},
|
|
113
|
+
bubbles: true,
|
|
114
|
+
composed: true,
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
render() {
|
|
121
|
+
return html`
|
|
122
|
+
<label>
|
|
123
|
+
<input
|
|
124
|
+
type="radio"
|
|
125
|
+
aria-label=${this.ariaLabel || ""}
|
|
126
|
+
.value=${this.value}
|
|
127
|
+
.checked=${this.checked}
|
|
128
|
+
?disabled=${this.disabled}
|
|
129
|
+
@change=${this._handleChange}
|
|
130
|
+
/>
|
|
131
|
+
<span class="control"></span>
|
|
132
|
+
<span class="label"><slot></slot></span>
|
|
133
|
+
</label>
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
declare global {
|
|
139
|
+
interface HTMLElementTagNameMap {
|
|
140
|
+
"st-radio": StRadio;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
|
+
import {customElement, property, queryAssignedElements} from "lit/decorators.js";
|
|
3
|
+
import {resetStyles} from "../../styles/reset.js";
|
|
4
|
+
import type {StRadio} from "../radio/radio.js";
|
|
5
|
+
|
|
6
|
+
@customElement("st-radio-group")
|
|
7
|
+
export class StRadioGroup extends LitElement {
|
|
8
|
+
static formAssociated = true;
|
|
9
|
+
private _internals: ElementInternals;
|
|
10
|
+
|
|
11
|
+
@property({type: String, reflect: true}) name = "";
|
|
12
|
+
@property({type: String}) value = "";
|
|
13
|
+
@property({type: String}) label = "";
|
|
14
|
+
@property({attribute: "help-text"}) helpText = "";
|
|
15
|
+
@property({type: Boolean, reflect: true}) disabled = false;
|
|
16
|
+
@property({type: Boolean, reflect: true}) invalid = false;
|
|
17
|
+
|
|
18
|
+
// Grabs all slotted st-radio components automatically
|
|
19
|
+
@queryAssignedElements({selector: "st-radio"})
|
|
20
|
+
private _radios!: StRadio[];
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this._internals = this.attachInternals();
|
|
25
|
+
|
|
26
|
+
// 1. Trigger error state if the user clicks away and the field is invalid
|
|
27
|
+
this.addEventListener("change", () => {
|
|
28
|
+
this.invalid = !this._internals.validity.valid;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 2. Trigger error state if the st-form tries to submit and fails
|
|
32
|
+
this.addEventListener("invalid", () => {
|
|
33
|
+
this.invalid = true;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static styles = [
|
|
38
|
+
resetStyles,
|
|
39
|
+
css`
|
|
40
|
+
:host {
|
|
41
|
+
display: block;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fieldset {
|
|
45
|
+
border: none;
|
|
46
|
+
padding: 0;
|
|
47
|
+
margin: 0;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: var(--st-spacing-xs);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
legend {
|
|
54
|
+
font-size: var(--st-font-size-sm);
|
|
55
|
+
font-weight: var(--st-font-weight-medium);
|
|
56
|
+
color: var(--st-color-text);
|
|
57
|
+
padding: 0;
|
|
58
|
+
margin-bottom: var(--st-spacing-xs);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.options {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: var(--st-spacing-sm);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Horizontal layout modifier */
|
|
68
|
+
:host([layout="horizontal"]) .options {
|
|
69
|
+
flex-direction: row;
|
|
70
|
+
gap: var(--st-spacing-md);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.help-text {
|
|
74
|
+
font-size: var(--st-font-size-xs);
|
|
75
|
+
color: var(--st-color-text-muted);
|
|
76
|
+
margin: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:host([invalid]) .help-text {
|
|
80
|
+
color: var(--st-color-error);
|
|
81
|
+
}
|
|
82
|
+
:host([invalid]) legend {
|
|
83
|
+
color: var(--st-color-error);
|
|
84
|
+
}
|
|
85
|
+
`,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
connectedCallback() {
|
|
89
|
+
super.connectedCallback();
|
|
90
|
+
// Listen for selections from child radios
|
|
91
|
+
this.addEventListener("st-radio-select", this._handleRadioSelect as EventListener);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
disconnectedCallback() {
|
|
95
|
+
super.disconnectedCallback();
|
|
96
|
+
this.removeEventListener("st-radio-select", this._handleRadioSelect as EventListener);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private _handleRadioSelect(e: CustomEvent<{value: string}>) {
|
|
100
|
+
if (this.disabled) return;
|
|
101
|
+
this.value = e.detail.value;
|
|
102
|
+
this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override updated(changedProperties: PropertyValues<this>) {
|
|
106
|
+
super.updated(changedProperties);
|
|
107
|
+
|
|
108
|
+
// When the group's value changes, sync the form and update all child radios
|
|
109
|
+
if (changedProperties.has("value")) {
|
|
110
|
+
this._internals.setFormValue(this.value);
|
|
111
|
+
this._syncRadios();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (changedProperties.has("disabled")) {
|
|
115
|
+
this._syncRadios();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Loops through children and unchecks/checks them based on the group's current value
|
|
120
|
+
private _syncRadios() {
|
|
121
|
+
if (!this._radios) return;
|
|
122
|
+
|
|
123
|
+
this._radios.forEach((radio) => {
|
|
124
|
+
radio.checked = radio.value === this.value;
|
|
125
|
+
if (this.disabled) {
|
|
126
|
+
radio.disabled = true;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Fired natively by the browser if the parent <form> is reset
|
|
132
|
+
formResetCallback() {
|
|
133
|
+
this.value = "";
|
|
134
|
+
this._internals.setFormValue("");
|
|
135
|
+
this._syncRadios();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
render() {
|
|
139
|
+
return html`
|
|
140
|
+
<fieldset ?disabled=${this.disabled}>
|
|
141
|
+
${this.label ? html`<legend>${this.label}</legend>` : ""}
|
|
142
|
+
|
|
143
|
+
<div class="options">
|
|
144
|
+
<slot @slotchange=${this._syncRadios}></slot>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
${this.helpText ? html`<div class="help-text">${this.helpText}</div>` : ""}
|
|
148
|
+
</fieldset>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
declare global {
|
|
154
|
+
interface HTMLElementTagNameMap {
|
|
155
|
+
"st-radio-group": StRadioGroup;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class AppRouter {
|
|
2
|
+
constructor() {
|
|
3
|
+
window.addEventListener('popstate', () => this._notify());
|
|
4
|
+
|
|
5
|
+
document.addEventListener('click', (e) => {
|
|
6
|
+
// 1. Ignore modified clicks (Ctrl+Click, etc)
|
|
7
|
+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 2. Safely traverse the path
|
|
12
|
+
const path = e.composedPath();
|
|
13
|
+
const link = path.find((el: any) =>
|
|
14
|
+
el && typeof el.hasAttribute === 'function' && el.hasAttribute('href')
|
|
15
|
+
) as HTMLElement | undefined;
|
|
16
|
+
|
|
17
|
+
if (!link) return;
|
|
18
|
+
|
|
19
|
+
const href = link.getAttribute('href');
|
|
20
|
+
const target = link.getAttribute('target');
|
|
21
|
+
|
|
22
|
+
// 3. Ignore external links
|
|
23
|
+
if (!href || href.startsWith('http') || href.startsWith('mailto:') || target === '_blank') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 4. Intercept internal links
|
|
28
|
+
if (href.startsWith('/')) {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
window.history.pushState(null, '', href);
|
|
31
|
+
this._notify();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Broadcast initial route on load
|
|
36
|
+
// Use setTimeout to ensure all Lit components have registered first
|
|
37
|
+
setTimeout(() => this._notify(), 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private _notify() {
|
|
41
|
+
window.dispatchEvent(new CustomEvent('route-changed', {
|
|
42
|
+
detail: window.location.pathname
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const router = new AppRouter();
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { resetStyles } from '../../styles/reset';
|
|
4
|
+
|
|
5
|
+
@customElement('st-select')
|
|
6
|
+
export class StSelect extends LitElement {
|
|
7
|
+
@property({ type: String, reflect: true }) size: 'sm' | 'md' | 'lg' = 'md';
|
|
8
|
+
@property({ type: String, reflect: true }) variant: 'outline' | 'filled' = 'outline';
|
|
9
|
+
@property({ type: String, reflect: true }) color: 'default' | 'primary' | 'error' = 'default';
|
|
10
|
+
|
|
11
|
+
// Internal state to handle the chevron animation when the slotted select is focused
|
|
12
|
+
@property({ type: Boolean, reflect: true }) focused = false;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
// Track focus states to animate the chevron
|
|
17
|
+
this.addEventListener('focusin', () => this.focused = true);
|
|
18
|
+
this.addEventListener('focusout', () => this.focused = false);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static styles = [
|
|
22
|
+
resetStyles,
|
|
23
|
+
css`
|
|
24
|
+
:host {
|
|
25
|
+
display: inline-block;
|
|
26
|
+
width: 100%;
|
|
27
|
+
position: relative;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.select-wrapper {
|
|
31
|
+
position: relative;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
width: 100%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
Target the native select provided by the user in the Light DOM.
|
|
39
|
+
This completely bypasses the browser rendering bug.
|
|
40
|
+
*/
|
|
41
|
+
::slotted(select) {
|
|
42
|
+
appearance: none;
|
|
43
|
+
-webkit-appearance: none;
|
|
44
|
+
width: 100%;
|
|
45
|
+
font-family: inherit;
|
|
46
|
+
background-color: transparent;
|
|
47
|
+
border-radius: var(--st-radius-md, 0.5rem);
|
|
48
|
+
color: var(--st-color-text);
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
outline: none;
|
|
51
|
+
transition: border-color 0.2s, box-shadow 0.2s, background-color 0.2s;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
::slotted(select:disabled) {
|
|
55
|
+
opacity: 0.6;
|
|
56
|
+
cursor: not-allowed;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ==========================================================
|
|
60
|
+
Sizes
|
|
61
|
+
========================================================== */
|
|
62
|
+
:host([size="sm"]) ::slotted(select) {
|
|
63
|
+
padding: 0.25rem 2rem 0.25rem 0.75rem;
|
|
64
|
+
font-size: 0.875rem;
|
|
65
|
+
height: 2rem;
|
|
66
|
+
}
|
|
67
|
+
:host([size="md"]) ::slotted(select) {
|
|
68
|
+
padding: 0.5rem 2.5rem 0.5rem 1rem;
|
|
69
|
+
font-size: 1rem;
|
|
70
|
+
height: 2.5rem;
|
|
71
|
+
}
|
|
72
|
+
:host([size="lg"]) ::slotted(select) {
|
|
73
|
+
padding: 0.75rem 3rem 0.75rem 1.25rem;
|
|
74
|
+
font-size: 1.125rem;
|
|
75
|
+
height: 3rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Multiple select formatting (No fixed height, no right padding for chevron) */
|
|
79
|
+
::slotted(select[multiple]) {
|
|
80
|
+
height: auto !important;
|
|
81
|
+
padding-right: 1rem !important;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ==========================================================
|
|
85
|
+
Variants & Colors
|
|
86
|
+
========================================================== */
|
|
87
|
+
:host([variant="outline"]) ::slotted(select) {
|
|
88
|
+
border: 1px solid var(--st-color-border);
|
|
89
|
+
}
|
|
90
|
+
:host([variant="outline"][color="default"]) ::slotted(select:focus-visible) {
|
|
91
|
+
border-color: var(--st-color-primary);
|
|
92
|
+
box-shadow: 0 0 0 2px rgba(var(--st-color-primary-rgb, 99, 102, 241), 0.2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:host([variant="filled"]) ::slotted(select) {
|
|
96
|
+
border: 1px solid transparent;
|
|
97
|
+
background-color: var(--st-color-surface-hover, rgba(0, 0, 0, 0.05));
|
|
98
|
+
}
|
|
99
|
+
:host([variant="filled"]) ::slotted(select:not(:disabled):hover) {
|
|
100
|
+
background-color: var(--st-color-surface-active, rgba(0, 0, 0, 0.1));
|
|
101
|
+
}
|
|
102
|
+
:host([variant="filled"][color="default"]) ::slotted(select:focus-visible) {
|
|
103
|
+
border-color: var(--st-color-primary);
|
|
104
|
+
background-color: transparent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Error Color State */
|
|
108
|
+
:host([color="error"]) ::slotted(select) {
|
|
109
|
+
border-color: var(--st-color-error, #ef4444);
|
|
110
|
+
}
|
|
111
|
+
:host([color="error"]) ::slotted(select:focus-visible) {
|
|
112
|
+
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Primary Color State */
|
|
116
|
+
:host([color="primary"]) ::slotted(select) {
|
|
117
|
+
border-color: var(--st-color-primary);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ==========================================================
|
|
121
|
+
Chevron Icon
|
|
122
|
+
========================================================== */
|
|
123
|
+
.chevron {
|
|
124
|
+
position: absolute;
|
|
125
|
+
right: 0.75rem;
|
|
126
|
+
width: 1.25rem;
|
|
127
|
+
height: 1.25rem;
|
|
128
|
+
pointer-events: none;
|
|
129
|
+
color: var(--st-color-text-muted);
|
|
130
|
+
transition: transform 0.2s ease, color 0.2s ease;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Animate chevron when the wrapper detects focus */
|
|
134
|
+
:host([focused]) .chevron {
|
|
135
|
+
transform: rotate(180deg);
|
|
136
|
+
color: var(--st-color-primary);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Hide chevron if the user slots a native multiple select */
|
|
140
|
+
::slotted(select[multiple]) ~ .chevron {
|
|
141
|
+
display: none;
|
|
142
|
+
}
|
|
143
|
+
`
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
render() {
|
|
147
|
+
return html`
|
|
148
|
+
<div class="select-wrapper">
|
|
149
|
+
<slot></slot>
|
|
150
|
+
<svg class="chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
151
|
+
<polyline points="6 9 12 15 18 9"></polyline>
|
|
152
|
+
</svg>
|
|
153
|
+
</div>
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
declare global {
|
|
159
|
+
interface HTMLElementTagNameMap {
|
|
160
|
+
'st-select': StSelect;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { resetStyles } from '../../styles/reset.js';
|
|
4
|
+
|
|
5
|
+
@customElement('st-skeleton')
|
|
6
|
+
export class StSkeleton extends LitElement {
|
|
7
|
+
/** Variant shape: 'text' | 'circular' | 'rectangular' */
|
|
8
|
+
@property({ type: String }) variant: 'text' | 'circular' | 'rectangular' = 'text';
|
|
9
|
+
|
|
10
|
+
/** Custom width override (e.g., '200px', '50%') */
|
|
11
|
+
@property({ type: String }) width = '';
|
|
12
|
+
|
|
13
|
+
/** Custom height override (e.g., '40px', '1rem') */
|
|
14
|
+
@property({ type: String }) height = '';
|
|
15
|
+
|
|
16
|
+
static styles = [
|
|
17
|
+
resetStyles,
|
|
18
|
+
css`
|
|
19
|
+
:host {
|
|
20
|
+
display: block;
|
|
21
|
+
width: 100%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.skeleton {
|
|
25
|
+
width: var(--st-skeleton-width, 100%);
|
|
26
|
+
height: var(--st-skeleton-height, 1em);
|
|
27
|
+
background: linear-gradient(
|
|
28
|
+
90deg,
|
|
29
|
+
var(--st-color-surface-muted, #f1f5f9) 25%,
|
|
30
|
+
var(--st-color-border, #e2e8f0) 37%,
|
|
31
|
+
var(--st-color-surface-muted, #f1f5f9) 63%
|
|
32
|
+
);
|
|
33
|
+
background-size: 400% 100%;
|
|
34
|
+
animation: st-shimmer 1.4s ease infinite;
|
|
35
|
+
border-radius: var(--st-radius-sm, 0.25rem);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:host([variant="circular"]) .skeleton {
|
|
39
|
+
border-radius: 50%;
|
|
40
|
+
height: var(--st-skeleton-height, 2.5rem);
|
|
41
|
+
width: var(--st-skeleton-width, 2.5rem);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
:host([variant="rectangular"]) .skeleton {
|
|
45
|
+
height: var(--st-skeleton-height, 8rem);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@keyframes st-shimmer {
|
|
49
|
+
0% { background-position: 100% 0; }
|
|
50
|
+
100% { background-position: 0 0; }
|
|
51
|
+
}
|
|
52
|
+
`
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
render() {
|
|
56
|
+
const style = `
|
|
57
|
+
${this.width ? `--st-skeleton-width: ${this.width};` : ''}
|
|
58
|
+
${this.height ? `--st-skeleton-height: ${this.height};` : ''}
|
|
59
|
+
`;
|
|
60
|
+
return html`<div class="skeleton" style=${style}></div>`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
declare global {
|
|
65
|
+
interface HTMLElementTagNameMap {
|
|
66
|
+
'st-skeleton': StSkeleton;
|
|
67
|
+
}
|
|
68
|
+
}
|