@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,238 @@
|
|
|
1
|
+
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
|
+
import {customElement, property, query} from "lit/decorators.js";
|
|
3
|
+
import {resetStyles} from "../../styles/reset.js";
|
|
4
|
+
|
|
5
|
+
@customElement("st-checkbox")
|
|
6
|
+
export class StCheckbox extends LitElement {
|
|
7
|
+
static formAssociated = true;
|
|
8
|
+
private _internals: ElementInternals;
|
|
9
|
+
|
|
10
|
+
@query("input") private _inputEl!: HTMLInputElement;
|
|
11
|
+
|
|
12
|
+
@property({type: String, reflect: true}) size: "sm" | "md" | "lg" = "md";
|
|
13
|
+
@property({type: String, reflect: true}) name = "";
|
|
14
|
+
// Checkboxes default to 'on' natively if no value is provided
|
|
15
|
+
@property({type: String}) value = "on";
|
|
16
|
+
|
|
17
|
+
@property({type: Boolean, reflect: true}) checked = false;
|
|
18
|
+
@property({type: Boolean, reflect: true}) indeterminate = false;
|
|
19
|
+
|
|
20
|
+
@property({type: Boolean, reflect: true}) disabled = false;
|
|
21
|
+
@property({type: Boolean, reflect: true}) invalid = false;
|
|
22
|
+
@property({type: Boolean, reflect: true}) required = false;
|
|
23
|
+
|
|
24
|
+
@property({attribute: "aria-label"}) ariaLabel = "";
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this._internals = this.attachInternals();
|
|
29
|
+
|
|
30
|
+
// 1. Trigger error state if the user clicks away and the field is invalid
|
|
31
|
+
this.addEventListener('focusout', () => {
|
|
32
|
+
this.invalid = !this._inputEl.validity.valid;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 2. Trigger error state if the st-form tries to submit and fails
|
|
36
|
+
this.addEventListener('invalid', () => {
|
|
37
|
+
this.invalid = true;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static styles = [
|
|
42
|
+
resetStyles,
|
|
43
|
+
css`
|
|
44
|
+
:host {
|
|
45
|
+
display: inline-block;
|
|
46
|
+
outline: none;
|
|
47
|
+
user-select: none;
|
|
48
|
+
--checkbox-size: 1.25rem;
|
|
49
|
+
--checkbox-icon-size: 0.875rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:host([size="sm"]) {
|
|
53
|
+
--checkbox-size: 1rem;
|
|
54
|
+
--checkbox-icon-size: 0.75rem;
|
|
55
|
+
}
|
|
56
|
+
:host([size="lg"]) {
|
|
57
|
+
--checkbox-size: 1.5rem;
|
|
58
|
+
--checkbox-icon-size: 1rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
label {
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: flex-start;
|
|
64
|
+
gap: var(--st-spacing-sm);
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
color: var(--st-color-text);
|
|
67
|
+
font-size: var(--st-font-size-base);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Visually hide the native input but keep it in the DOM for focus/screen readers */
|
|
71
|
+
input {
|
|
72
|
+
position: absolute;
|
|
73
|
+
width: var(--checkbox-size);
|
|
74
|
+
height: var(--checkbox-size);
|
|
75
|
+
padding: 0;
|
|
76
|
+
margin: -1px;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
clip: rect(0, 0, 0, 0);
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
border-width: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Custom Checkbox Box */
|
|
84
|
+
.control {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
width: var(--checkbox-size);
|
|
89
|
+
height: var(--checkbox-size);
|
|
90
|
+
border: 1px solid var(--st-color-border);
|
|
91
|
+
border-radius: 4px;
|
|
92
|
+
background-color: var(--st-color-bg);
|
|
93
|
+
transition: all 0.2s ease;
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
/* Aligns the box nicely with standard text line-height */
|
|
96
|
+
margin-top: 0.125rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Hover State */
|
|
100
|
+
label:hover .control {
|
|
101
|
+
border-color: var(--st-color-primary-hover);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Focus State */
|
|
105
|
+
input:focus-visible + .control {
|
|
106
|
+
box-shadow:
|
|
107
|
+
0 0 0 2px var(--st-color-bg),
|
|
108
|
+
0 0 0 4px var(--st-color-primary);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Checked & Indeterminate States */
|
|
112
|
+
:host([checked]) .control,
|
|
113
|
+
:host([indeterminate]) .control {
|
|
114
|
+
border-color: var(--st-color-primary);
|
|
115
|
+
background-color: var(--st-color-primary);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* SVG Icons */
|
|
119
|
+
svg {
|
|
120
|
+
width: var(--checkbox-icon-size);
|
|
121
|
+
height: var(--checkbox-icon-size);
|
|
122
|
+
stroke: var(--st-white);
|
|
123
|
+
stroke-width: 3;
|
|
124
|
+
stroke-linecap: round;
|
|
125
|
+
stroke-linejoin: round;
|
|
126
|
+
fill: none;
|
|
127
|
+
opacity: 0;
|
|
128
|
+
transform: scale(0.5);
|
|
129
|
+
transition:
|
|
130
|
+
transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
|
131
|
+
opacity 0.2s ease;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Reveal Icons */
|
|
135
|
+
:host([checked]) svg.icon-check,
|
|
136
|
+
:host([indeterminate]) svg.icon-minus {
|
|
137
|
+
opacity: 1;
|
|
138
|
+
transform: scale(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Disabled State */
|
|
142
|
+
:host([disabled]) {
|
|
143
|
+
pointer-events: none;
|
|
144
|
+
}
|
|
145
|
+
:host([disabled]) label {
|
|
146
|
+
opacity: 0.5;
|
|
147
|
+
cursor: not-allowed;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* Invalid State */
|
|
151
|
+
:host([invalid]) .control {
|
|
152
|
+
border-color: var(--st-color-error);
|
|
153
|
+
}
|
|
154
|
+
:host([invalid]) input:focus-visible + .control {
|
|
155
|
+
box-shadow:
|
|
156
|
+
0 0 0 2px var(--st-color-bg),
|
|
157
|
+
0 0 0 4px var(--st-color-error);
|
|
158
|
+
}
|
|
159
|
+
`,
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
override updated(changedProperties: PropertyValues<this>) {
|
|
163
|
+
super.updated(changedProperties);
|
|
164
|
+
|
|
165
|
+
if (changedProperties.has("checked") || changedProperties.has("value")) {
|
|
166
|
+
// Form API: If checked, submit the value. If unchecked, submit null to remove it from FormData.
|
|
167
|
+
this._internals.setFormValue(this.checked ? this.value : null);
|
|
168
|
+
this._syncValidity();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (changedProperties.has("required")) {
|
|
172
|
+
this._syncValidity();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private _syncValidity() {
|
|
177
|
+
if (!this._inputEl) return;
|
|
178
|
+
this._internals.setValidity(
|
|
179
|
+
this._inputEl.validity,
|
|
180
|
+
this._inputEl.validationMessage,
|
|
181
|
+
this._inputEl,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
formResetCallback() {
|
|
186
|
+
this.checked = false;
|
|
187
|
+
this.indeterminate = false;
|
|
188
|
+
this._internals.setFormValue(null);
|
|
189
|
+
this.updateComplete.then(() => this._syncValidity());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private _handleChange(e: Event) {
|
|
193
|
+
e.stopPropagation();
|
|
194
|
+
|
|
195
|
+
if (this.disabled) return;
|
|
196
|
+
|
|
197
|
+
// Standard native behavior: clicking an indeterminate checkbox unsets the indeterminate state
|
|
198
|
+
if (this.indeterminate) {
|
|
199
|
+
this.indeterminate = false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.checked = this._inputEl.checked;
|
|
203
|
+
this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
render() {
|
|
207
|
+
return html`
|
|
208
|
+
<label>
|
|
209
|
+
<input
|
|
210
|
+
type="checkbox"
|
|
211
|
+
aria-label=${this.ariaLabel || ""}
|
|
212
|
+
.name=${this.name}
|
|
213
|
+
.value=${this.value}
|
|
214
|
+
.checked=${this.checked}
|
|
215
|
+
.indeterminate=${this.indeterminate}
|
|
216
|
+
?disabled=${this.disabled}
|
|
217
|
+
?required=${this.required}
|
|
218
|
+
@change=${this._handleChange}
|
|
219
|
+
/>
|
|
220
|
+
<div class="control">
|
|
221
|
+
<!-- Render both SVGs and use CSS to conditionally reveal them based on host attributes -->
|
|
222
|
+
${this.indeterminate
|
|
223
|
+
? html`<svg class="icon-minus" viewBox="0 0 24 24"><path d="M5 12h14"></path></svg>`
|
|
224
|
+
: html`<svg class="icon-check" viewBox="0 0 24 24">
|
|
225
|
+
<path d="M20 6L9 17l-5-5"></path>
|
|
226
|
+
</svg>`}
|
|
227
|
+
</div>
|
|
228
|
+
<span class="label"><slot></slot></span>
|
|
229
|
+
</label>
|
|
230
|
+
`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
declare global {
|
|
235
|
+
interface HTMLElementTagNameMap {
|
|
236
|
+
"st-checkbox": StCheckbox;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement, property, state, query } from 'lit/decorators.js';
|
|
3
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
4
|
+
import { resetStyles } from '../../styles/reset.js';
|
|
5
|
+
|
|
6
|
+
import Prism from 'prismjs';
|
|
7
|
+
import 'prismjs/components/prism-typescript.js';
|
|
8
|
+
import 'prismjs/components/prism-javascript.js';
|
|
9
|
+
import 'prismjs/components/prism-css.js';
|
|
10
|
+
import 'prismjs/components/prism-json.js';
|
|
11
|
+
import 'prismjs/components/prism-bash.js';
|
|
12
|
+
import 'prismjs/components/prism-csharp.js';
|
|
13
|
+
|
|
14
|
+
interface CodeSnippet {
|
|
15
|
+
lang: string;
|
|
16
|
+
label: string;
|
|
17
|
+
rawCode: string;
|
|
18
|
+
highlightedCode: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@customElement('st-code-block')
|
|
22
|
+
export class StCodeBlock extends LitElement {
|
|
23
|
+
// Fallback language if a template doesn't provide one
|
|
24
|
+
@property({ type: String }) language = 'typescript';
|
|
25
|
+
|
|
26
|
+
@state() private _snippets: CodeSnippet[] = [];
|
|
27
|
+
@state() private _activeIndex = 0;
|
|
28
|
+
@state() private _copied = false;
|
|
29
|
+
|
|
30
|
+
@query('slot[name="raw"]') private _slot!: HTMLSlotElement;
|
|
31
|
+
|
|
32
|
+
static styles = [
|
|
33
|
+
resetStyles,
|
|
34
|
+
css`
|
|
35
|
+
:host {
|
|
36
|
+
display: block;
|
|
37
|
+
margin: var(--st-spacing-md) 0;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
background-color: var(--st-color-surface);
|
|
40
|
+
border: 1px solid var(--st-color-border);
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.header {
|
|
45
|
+
display: flex;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
align-items: center;
|
|
48
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
49
|
+
border-bottom: 1px solid var(--st-color-border);
|
|
50
|
+
padding: 0 var(--st-spacing-md);
|
|
51
|
+
min-height: 2.5rem; /* 40px */
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Single language label */
|
|
55
|
+
.language-label {
|
|
56
|
+
font-size: var(--st-font-size-xs);
|
|
57
|
+
color: var(--st-color-text-muted);
|
|
58
|
+
text-transform: uppercase;
|
|
59
|
+
letter-spacing: 0.05em;
|
|
60
|
+
font-weight: var(--st-font-weight-bold);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Multi-language Tabs */
|
|
64
|
+
.tabs {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: var(--st-spacing-md);
|
|
67
|
+
height: 100%;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.tab {
|
|
71
|
+
background: transparent;
|
|
72
|
+
border: none;
|
|
73
|
+
border-bottom: 2px solid transparent;
|
|
74
|
+
color: var(--st-color-text-muted);
|
|
75
|
+
font-size: var(--st-font-size-xs);
|
|
76
|
+
font-weight: var(--st-font-weight-medium);
|
|
77
|
+
padding: var(--st-spacing-xs) 0;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
transition: all 0.2s ease;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.tab:hover {
|
|
83
|
+
color: var(--st-color-text);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.tab.active {
|
|
87
|
+
color: var(--st-color-primary);
|
|
88
|
+
border-bottom-color: var(--st-color-primary);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Copy Button */
|
|
92
|
+
.copy-button {
|
|
93
|
+
background: transparent;
|
|
94
|
+
border: 1px solid transparent;
|
|
95
|
+
color: var(--st-color-text-muted);
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
gap: var(--st-spacing-xs);
|
|
100
|
+
padding: 4px 8px;
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
font-size: var(--st-font-size-xs);
|
|
103
|
+
transition: all 0.2s ease;
|
|
104
|
+
margin-left: auto; /* Pushes it to the right */
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.copy-button:hover {
|
|
108
|
+
color: var(--st-color-text);
|
|
109
|
+
background-color: rgba(255, 255, 255, 0.05);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.copy-button svg {
|
|
113
|
+
width: 14px;
|
|
114
|
+
height: 14px;
|
|
115
|
+
fill: none;
|
|
116
|
+
stroke: currentColor;
|
|
117
|
+
stroke-width: 2;
|
|
118
|
+
stroke-linecap: round;
|
|
119
|
+
stroke-linejoin: round;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.copy-button.copied {
|
|
123
|
+
color: var(--st-color-success);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pre {
|
|
127
|
+
margin: 0;
|
|
128
|
+
padding: var(--st-spacing-md);
|
|
129
|
+
overflow-x: auto;
|
|
130
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
131
|
+
font-size: 0.875rem;
|
|
132
|
+
line-height: 1.5;
|
|
133
|
+
color: var(--st-color-text);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.raw-content {
|
|
137
|
+
display: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* ==========================================================
|
|
141
|
+
Prism.js Theme Mapping (Space Grey / Purple Aesthetic)
|
|
142
|
+
========================================================== */
|
|
143
|
+
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: var(--st-color-text-muted); font-style: italic; }
|
|
144
|
+
.token.punctuation { color: #8b949e; }
|
|
145
|
+
.token.namespace { opacity: .7; }
|
|
146
|
+
.token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: #79c0ff; }
|
|
147
|
+
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #a5d6ff; }
|
|
148
|
+
.token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: var(--st-color-text); }
|
|
149
|
+
.token.atrule, .token.attr-value, .token.keyword { color: var(--st-color-primary); }
|
|
150
|
+
.token.function, .token.class-name { color: #d2a8ff; }
|
|
151
|
+
.token.regex, .token.important, .token.variable { color: #ffa657; }
|
|
152
|
+
`
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
private _handleSlotChange() {
|
|
156
|
+
const nodes = this._slot.assignedNodes({ flatten: true });
|
|
157
|
+
|
|
158
|
+
// Find ALL template tags passed into the slot
|
|
159
|
+
const templates = nodes.filter(n => n instanceof HTMLTemplateElement) as HTMLTemplateElement[];
|
|
160
|
+
|
|
161
|
+
if (templates.length > 0) {
|
|
162
|
+
// Map over every template and parse its code and attributes
|
|
163
|
+
this._snippets = templates.map(template => {
|
|
164
|
+
const lang = template.getAttribute('data-lang') || this.language;
|
|
165
|
+
const label = template.getAttribute('data-label') || lang; // Fallback to lang if no label provided
|
|
166
|
+
const rawCode = this._dedent(template.innerHTML);
|
|
167
|
+
|
|
168
|
+
const grammar = Prism.languages[lang] || Prism.languages.javascript;
|
|
169
|
+
const highlightedCode = Prism.highlight(rawCode, grammar, lang);
|
|
170
|
+
|
|
171
|
+
return { lang, label, rawCode, highlightedCode };
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
// Fallback: If no template is used, process raw text just like before
|
|
175
|
+
const rawText = nodes.map(n => n.textContent).join('');
|
|
176
|
+
const rawCode = this._dedent(rawText);
|
|
177
|
+
|
|
178
|
+
if (rawCode) {
|
|
179
|
+
const grammar = Prism.languages[this.language] || Prism.languages.javascript;
|
|
180
|
+
const highlightedCode = Prism.highlight(rawCode, grammar, this.language);
|
|
181
|
+
this._snippets = [{ lang: this.language, label: this.language, rawCode, highlightedCode }];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Reset to the first tab whenever new code is injected
|
|
186
|
+
this._activeIndex = 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private _dedent(text: string): string {
|
|
190
|
+
const lines = text.split('\n');
|
|
191
|
+
const minIndent = lines
|
|
192
|
+
.filter(line => line.trim().length > 0)
|
|
193
|
+
.reduce((min, line) => {
|
|
194
|
+
const indent = line.match(/^\s*/)?.[0].length || 0;
|
|
195
|
+
return Math.min(min, indent);
|
|
196
|
+
}, Infinity);
|
|
197
|
+
|
|
198
|
+
if (minIndent === Infinity || minIndent === 0) return text.trim();
|
|
199
|
+
return lines.map(line => line.slice(minIndent)).join('\n').trim();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private async _copyToClipboard() {
|
|
203
|
+
// Copy the code from the currently active tab
|
|
204
|
+
const activeSnippet = this._snippets[this._activeIndex];
|
|
205
|
+
if (!activeSnippet) return;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
await navigator.clipboard.writeText(activeSnippet.rawCode);
|
|
209
|
+
this._copied = true;
|
|
210
|
+
setTimeout(() => { this._copied = false; }, 2000);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error('Failed to copy text: ', err);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
render() {
|
|
217
|
+
const activeSnippet = this._snippets[this._activeIndex];
|
|
218
|
+
|
|
219
|
+
return html`
|
|
220
|
+
<div class="header">
|
|
221
|
+
${this._snippets.length > 1
|
|
222
|
+
? html`
|
|
223
|
+
<div class="tabs">
|
|
224
|
+
${this._snippets.map((snippet, index) => html`
|
|
225
|
+
<button
|
|
226
|
+
class="tab ${index === this._activeIndex ? 'active' : ''}"
|
|
227
|
+
@click=${() => this._activeIndex = index}
|
|
228
|
+
>
|
|
229
|
+
${snippet.label}
|
|
230
|
+
</button>
|
|
231
|
+
`)}
|
|
232
|
+
</div>
|
|
233
|
+
`
|
|
234
|
+
: html`
|
|
235
|
+
<span class="language-label">${activeSnippet?.label || this.language}</span>
|
|
236
|
+
`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
<button class="copy-button ${this._copied ? 'copied' : ''}" @click=${this._copyToClipboard}>
|
|
240
|
+
${this._copied
|
|
241
|
+
? html`<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg> Copied!`
|
|
242
|
+
: html`<svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> Copy`
|
|
243
|
+
}
|
|
244
|
+
</button>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<!-- Render the currently active snippet -->
|
|
248
|
+
${activeSnippet ? html`
|
|
249
|
+
<pre><code class="language-${activeSnippet.lang}">${unsafeHTML(activeSnippet.highlightedCode)}</code></pre>
|
|
250
|
+
` : ''}
|
|
251
|
+
|
|
252
|
+
<div class="raw-content">
|
|
253
|
+
<slot name="raw" @slotchange=${this._handleSlotChange}></slot>
|
|
254
|
+
</div>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
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-content-card')
|
|
6
|
+
export class StContentCard extends LitElement {
|
|
7
|
+
// Use href to align with native HTML web standards
|
|
8
|
+
@property({ type: String }) href?: string;
|
|
9
|
+
@property({ type: String }) target: '_blank' | '_self' = '_self';
|
|
10
|
+
|
|
11
|
+
static styles = [
|
|
12
|
+
resetStyles,
|
|
13
|
+
css`
|
|
14
|
+
:host {
|
|
15
|
+
display: block;
|
|
16
|
+
/* Ensures cards stretch to equal heights if placed in a flex/grid container */
|
|
17
|
+
height: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.card {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
height: 100%;
|
|
24
|
+
background-color: var(--st-color-surface);
|
|
25
|
+
border: 1px solid var(--st-color-border);
|
|
26
|
+
border-radius: var(--st-radius-md, 0.5rem);
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
color: inherit;
|
|
30
|
+
|
|
31
|
+
/* Smooth transitions for the hover effects */
|
|
32
|
+
transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ==========================================================
|
|
36
|
+
Hover Effects (Only applied if it is a link)
|
|
37
|
+
========================================================== */
|
|
38
|
+
a.card {
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
a.card:hover,
|
|
43
|
+
a.card:focus-visible {
|
|
44
|
+
outline: none;
|
|
45
|
+
border-color: var(--st-color-primary);
|
|
46
|
+
transform: translateY(-2px);
|
|
47
|
+
/* Subtle shadow to lift the card off the background */
|
|
48
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Dark mode specific shadow adjustment */
|
|
52
|
+
:host-context([data-theme="dark"]) a.card:hover {
|
|
53
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ==========================================================
|
|
57
|
+
Internal Layout
|
|
58
|
+
========================================================== */
|
|
59
|
+
.media {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: center;
|
|
63
|
+
aspect-ratio: 16 / 9;
|
|
64
|
+
background-color: var(--st-color-bg);
|
|
65
|
+
border-bottom: 1px solid var(--st-color-border);
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Image formatting */
|
|
70
|
+
::slotted(img[slot="media"]) {
|
|
71
|
+
width: 100%;
|
|
72
|
+
height: 100%;
|
|
73
|
+
object-fit: cover;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* SVG formatting (for the icons we just created) */
|
|
77
|
+
::slotted(svg[slot="media"]) {
|
|
78
|
+
width: 3rem;
|
|
79
|
+
height: 3rem;
|
|
80
|
+
color: var(--st-color-primary);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.content {
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
padding: var(--st-spacing-md, 1.25rem);
|
|
87
|
+
gap: var(--st-spacing-sm, 0.5rem);
|
|
88
|
+
flex: 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.title {
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
font-size: 1.125rem;
|
|
94
|
+
color: var(--st-color-text);
|
|
95
|
+
margin: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.body {
|
|
99
|
+
color: var(--st-color-text-muted);
|
|
100
|
+
font-size: 0.875rem;
|
|
101
|
+
margin: 0;
|
|
102
|
+
line-height: 1.5;
|
|
103
|
+
}
|
|
104
|
+
`
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
render() {
|
|
108
|
+
// 1. Define the inner content once to avoid duplication
|
|
109
|
+
const innerContent = html`
|
|
110
|
+
<div class="media">
|
|
111
|
+
<slot name="media"></slot>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="content">
|
|
114
|
+
<div class="title">
|
|
115
|
+
<slot name="title"></slot>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="body">
|
|
118
|
+
<slot></slot>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
// 2. Conditionally wrap it in an anchor or a div
|
|
124
|
+
return this.href
|
|
125
|
+
? html`<a href="${this.href}" target="${this.target}" class="card">${innerContent}</a>`
|
|
126
|
+
: html`<div class="card">${innerContent}</div>`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
declare global {
|
|
131
|
+
interface HTMLElementTagNameMap {
|
|
132
|
+
'st-content-card': StContentCard;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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-divider')
|
|
6
|
+
export class StDivider extends LitElement {
|
|
7
|
+
@property({ type: String, reflect: true }) direction: 'horizontal' | 'vertical' = 'horizontal';
|
|
8
|
+
@property({ type: String }) thickness = '1px';
|
|
9
|
+
@property({ type: String }) margin = '0';
|
|
10
|
+
@property({ type: String, reflect: true }) color: 'default' | 'primary' | 'border' = 'border';
|
|
11
|
+
|
|
12
|
+
static styles = [
|
|
13
|
+
resetStyles,
|
|
14
|
+
css`
|
|
15
|
+
:host {
|
|
16
|
+
display: block;
|
|
17
|
+
background-color: var(--divider-color, var(--st-color-border));
|
|
18
|
+
margin: var(--divider-margin, 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Horizontal Layout */
|
|
22
|
+
:host([direction="horizontal"]) {
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: var(--divider-thickness, 1px);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Vertical Layout */
|
|
28
|
+
:host([direction="vertical"]) {
|
|
29
|
+
width: var(--divider-thickness, 1px);
|
|
30
|
+
height: 100%;
|
|
31
|
+
display: inline-block;
|
|
32
|
+
vertical-align: middle;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Color Variants */
|
|
36
|
+
:host([color="primary"]) {
|
|
37
|
+
background-color: var(--st-color-primary);
|
|
38
|
+
}
|
|
39
|
+
:host([color="default"]) {
|
|
40
|
+
background-color: var(--st-color-text);
|
|
41
|
+
opacity: 0.2;
|
|
42
|
+
}
|
|
43
|
+
`
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
updated(changedProperties: Map<string, any>) {
|
|
47
|
+
super.updated(changedProperties);
|
|
48
|
+
|
|
49
|
+
// Sync properties to CSS variables for dynamic adjustment
|
|
50
|
+
this.style.setProperty('--divider-thickness', this.thickness);
|
|
51
|
+
this.style.setProperty('--divider-margin', this.margin);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
// Accessibility: Set role and aria-orientation
|
|
56
|
+
return html`
|
|
57
|
+
<div
|
|
58
|
+
role="separator"
|
|
59
|
+
aria-orientation="${this.direction === 'horizontal' ? 'horizontal' : 'vertical'}"
|
|
60
|
+
></div>
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare global {
|
|
66
|
+
interface HTMLElementTagNameMap {
|
|
67
|
+
'st-divider': StDivider;
|
|
68
|
+
}
|
|
69
|
+
}
|