@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.
Files changed (168) hide show
  1. package/README.md +8 -0
  2. package/dist/components/alert/alert.d.ts +13 -0
  3. package/dist/components/alert/alert.d.ts.map +1 -0
  4. package/dist/components/alert/alert.js +105 -0
  5. package/dist/components/badge/badge.d.ts +13 -0
  6. package/dist/components/badge/badge.d.ts.map +1 -0
  7. package/dist/components/badge/badge.js +101 -0
  8. package/dist/components/breadcrumb/breadcrumb.d.ts +19 -0
  9. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
  10. package/dist/components/breadcrumb/breadcrumb.js +99 -0
  11. package/dist/components/button/button.d.ts +23 -0
  12. package/dist/components/button/button.d.ts.map +1 -0
  13. package/dist/components/button/button.js +304 -0
  14. package/dist/components/button-group/button-group.d.ts +6 -0
  15. package/dist/components/button-group/button-group.d.ts.map +1 -0
  16. package/dist/components/button-group/button-group.js +74 -0
  17. package/dist/components/card/card.d.ts +11 -0
  18. package/dist/components/card/card.d.ts.map +1 -0
  19. package/dist/components/card/card.js +54 -0
  20. package/dist/components/checkbox/checkbox.d.ts +28 -0
  21. package/dist/components/checkbox/checkbox.d.ts.map +1 -0
  22. package/dist/components/checkbox/checkbox.js +247 -0
  23. package/dist/components/code-block/code-block.d.ts +20 -0
  24. package/dist/components/code-block/code-block.d.ts.map +1 -0
  25. package/dist/components/code-block/code-block.js +260 -0
  26. package/dist/components/content-card/content-card.d.ts +13 -0
  27. package/dist/components/content-card/content-card.d.ts.map +1 -0
  28. package/dist/components/content-card/content-card.js +140 -0
  29. package/dist/components/divider/divider.d.ts +16 -0
  30. package/dist/components/divider/divider.d.ts.map +1 -0
  31. package/dist/components/divider/divider.js +82 -0
  32. package/dist/components/drawer/drawer.d.ts +22 -0
  33. package/dist/components/drawer/drawer.d.ts.map +1 -0
  34. package/dist/components/drawer/drawer.js +226 -0
  35. package/dist/components/field/field.d.ts +11 -0
  36. package/dist/components/field/field.d.ts.map +1 -0
  37. package/dist/components/field/field.js +32 -0
  38. package/dist/components/footer/footer.d.ts +6 -0
  39. package/dist/components/footer/footer.d.ts.map +1 -0
  40. package/dist/components/footer/footer.js +65 -0
  41. package/dist/components/form/form-step.d.ts +13 -0
  42. package/dist/components/form/form-step.d.ts.map +1 -0
  43. package/dist/components/form/form-step.js +41 -0
  44. package/dist/components/form/form.d.ts +25 -0
  45. package/dist/components/form/form.d.ts.map +1 -0
  46. package/dist/components/form/form.js +358 -0
  47. package/dist/components/grid/grid-row.d.ts +17 -0
  48. package/dist/components/grid/grid-row.d.ts.map +1 -0
  49. package/dist/components/grid/grid-row.js +92 -0
  50. package/dist/components/grid/grid.d.ts +13 -0
  51. package/dist/components/grid/grid.d.ts.map +1 -0
  52. package/dist/components/grid/grid.js +42 -0
  53. package/dist/components/icon/icon-paths.d.ts +116 -0
  54. package/dist/components/icon/icon-paths.d.ts.map +1 -0
  55. package/dist/components/icon/icon-paths.js +121 -0
  56. package/dist/components/icon/icon.d.ts +16 -0
  57. package/dist/components/icon/icon.d.ts.map +1 -0
  58. package/dist/components/icon/icon.js +95 -0
  59. package/dist/components/input/input.d.ts +31 -0
  60. package/dist/components/input/input.d.ts.map +1 -0
  61. package/dist/components/input/input.js +268 -0
  62. package/dist/components/link/link.d.ts +16 -0
  63. package/dist/components/link/link.d.ts.map +1 -0
  64. package/dist/components/link/link.js +116 -0
  65. package/dist/components/metadata/metadata.d.ts +18 -0
  66. package/dist/components/metadata/metadata.d.ts.map +1 -0
  67. package/dist/components/metadata/metadata.js +71 -0
  68. package/dist/components/modal/modal.d.ts +24 -0
  69. package/dist/components/modal/modal.d.ts.map +1 -0
  70. package/dist/components/modal/modal.js +163 -0
  71. package/dist/components/page-layout/page-layout.d.ts +46 -0
  72. package/dist/components/page-layout/page-layout.d.ts.map +1 -0
  73. package/dist/components/page-layout/page-layout.js +697 -0
  74. package/dist/components/radio/radio.d.ts +16 -0
  75. package/dist/components/radio/radio.d.ts.map +1 -0
  76. package/dist/components/radio/radio.js +152 -0
  77. package/dist/components/radio-group/radio-group.d.ts +27 -0
  78. package/dist/components/radio-group/radio-group.d.ts.map +1 -0
  79. package/dist/components/radio-group/radio-group.js +161 -0
  80. package/dist/components/router/router.d.ts +7 -0
  81. package/dist/components/router/router.d.ts.map +1 -0
  82. package/dist/components/router/router.js +37 -0
  83. package/dist/components/select/select.d.ts +16 -0
  84. package/dist/components/select/select.d.ts.map +1 -0
  85. package/dist/components/select/select.js +172 -0
  86. package/dist/components/skeleton/skeleton.d.ts +17 -0
  87. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  88. package/dist/components/skeleton/skeleton.js +78 -0
  89. package/dist/components/switch/switch.d.ts +25 -0
  90. package/dist/components/switch/switch.d.ts.map +1 -0
  91. package/dist/components/switch/switch.js +185 -0
  92. package/dist/components/table/data-table.d.ts +72 -0
  93. package/dist/components/table/data-table.d.ts.map +1 -0
  94. package/dist/components/table/data-table.js +551 -0
  95. package/dist/components/table/table.d.ts +22 -0
  96. package/dist/components/table/table.d.ts.map +1 -0
  97. package/dist/components/table/table.js +167 -0
  98. package/dist/components/tabs/tab-panel.d.ts +8 -0
  99. package/dist/components/tabs/tab-panel.d.ts.map +1 -0
  100. package/dist/components/tabs/tab-panel.js +46 -0
  101. package/dist/components/tabs/tab.d.ts +9 -0
  102. package/dist/components/tabs/tab.d.ts.map +1 -0
  103. package/dist/components/tabs/tab.js +91 -0
  104. package/dist/components/tabs/tabs.d.ts +14 -0
  105. package/dist/components/tabs/tabs.d.ts.map +1 -0
  106. package/dist/components/tabs/tabs.js +85 -0
  107. package/dist/components/textarea/textarea.d.ts +36 -0
  108. package/dist/components/textarea/textarea.d.ts.map +1 -0
  109. package/dist/components/textarea/textarea.js +231 -0
  110. package/dist/components/theme-toggle/theme-toggle.d.ts +15 -0
  111. package/dist/components/theme-toggle/theme-toggle.d.ts.map +1 -0
  112. package/dist/components/theme-toggle/theme-toggle.js +130 -0
  113. package/dist/components/vertical-nav/vertical-nav-group.d.ts +9 -0
  114. package/dist/components/vertical-nav/vertical-nav-group.d.ts.map +1 -0
  115. package/dist/components/vertical-nav/vertical-nav-group.js +111 -0
  116. package/dist/components/vertical-nav/vertical-nav-item.d.ts +8 -0
  117. package/dist/components/vertical-nav/vertical-nav-item.d.ts.map +1 -0
  118. package/dist/components/vertical-nav/vertical-nav-item.js +65 -0
  119. package/dist/styles/index.css +171 -0
  120. package/dist/styles/reset.d.ts +2 -0
  121. package/dist/styles/reset.d.ts.map +1 -0
  122. package/dist/styles/reset.js +50 -0
  123. package/package.json +192 -0
  124. package/src/components/alert/alert.ts +101 -0
  125. package/src/components/badge/badge.ts +97 -0
  126. package/src/components/breadcrumb/breadcrumb.ts +99 -0
  127. package/src/components/button/button.ts +281 -0
  128. package/src/components/button-group/button-group.ts +67 -0
  129. package/src/components/card/card.ts +53 -0
  130. package/src/components/checkbox/checkbox.ts +238 -0
  131. package/src/components/code-block/code-block.ts +257 -0
  132. package/src/components/content-card/content-card.ts +134 -0
  133. package/src/components/divider/divider.ts +69 -0
  134. package/src/components/drawer/drawer.ts +218 -0
  135. package/src/components/field/field.ts +31 -0
  136. package/src/components/footer/footer.ts +58 -0
  137. package/src/components/form/form-step.ts +32 -0
  138. package/src/components/form/form.ts +376 -0
  139. package/src/components/grid/grid-row.ts +76 -0
  140. package/src/components/grid/grid.ts +37 -0
  141. package/src/components/icon/icon-paths.ts +124 -0
  142. package/src/components/icon/icon.ts +83 -0
  143. package/src/components/input/input.ts +248 -0
  144. package/src/components/link/link.ts +98 -0
  145. package/src/components/metadata/metadata.ts +67 -0
  146. package/src/components/modal/modal.ts +159 -0
  147. package/src/components/page-layout/page-layout.ts +705 -0
  148. package/src/components/radio/radio.ts +142 -0
  149. package/src/components/radio-group/radio-group.ts +157 -0
  150. package/src/components/router/router.ts +47 -0
  151. package/src/components/select/select.ts +162 -0
  152. package/src/components/skeleton/skeleton.ts +68 -0
  153. package/src/components/switch/switch.ts +181 -0
  154. package/src/components/table/data-table.ts +557 -0
  155. package/src/components/table/table.ts +154 -0
  156. package/src/components/tabs/tab-panel.ts +31 -0
  157. package/src/components/tabs/tab.ts +77 -0
  158. package/src/components/tabs/tabs.ts +88 -0
  159. package/src/components/textarea/textarea.ts +215 -0
  160. package/src/components/theme-toggle/theme-toggle.ts +131 -0
  161. package/src/components/vertical-nav/vertical-nav-group.ts +97 -0
  162. package/src/components/vertical-nav/vertical-nav-item.ts +50 -0
  163. package/src/styles/globals.css +58 -0
  164. package/src/styles/index.css +80 -0
  165. package/src/styles/reset.ts +51 -0
  166. package/src/styles/theme.css +59 -0
  167. package/src/styles/typography.css +26 -0
  168. package/tsconfig.json +16 -0
@@ -0,0 +1,31 @@
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-tab-panel')
6
+ export class StTabPanel extends LitElement {
7
+ @property({ type: String, reflect: true }) name = '';
8
+ @property({ type: Boolean, reflect: true }) active = false;
9
+
10
+ static styles = [
11
+ resetStyles,
12
+ css`
13
+ :host {
14
+ display: block;
15
+ padding: var(--st-spacing-md) 0;
16
+ }
17
+
18
+ :host(:not([active])) {
19
+ display: none;
20
+ }
21
+ `
22
+ ];
23
+
24
+ render() {
25
+ return html`
26
+ <div role="tabpanel" aria-hidden=${this.active ? 'false' : 'true'}>
27
+ <slot></slot>
28
+ </div>
29
+ `;
30
+ }
31
+ }
@@ -0,0 +1,77 @@
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-tab')
6
+ export class StTab extends LitElement {
7
+ @property({ type: String, reflect: true }) panel = '';
8
+ @property({ type: Boolean, reflect: true }) active = false;
9
+
10
+ static styles = [
11
+ resetStyles,
12
+ css`
13
+ :host {
14
+ display: inline-block;
15
+ }
16
+
17
+ button {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: var(--st-spacing-sm);
21
+ background: transparent;
22
+ border: none;
23
+ border-bottom: 2px solid transparent;
24
+ padding: var(--st-spacing-sm) var(--st-spacing-md);
25
+ color: var(--st-color-text-muted);
26
+ font-size: var(--st-font-size-sm);
27
+ font-weight: var(--st-font-weight-medium);
28
+ cursor: pointer;
29
+ transition: all 0.2s ease;
30
+ white-space: nowrap;
31
+ }
32
+
33
+ button:hover {
34
+ color: var(--st-color-text);
35
+ }
36
+
37
+ button:focus-visible {
38
+ outline: none;
39
+ background-color: var(--st-color-surface);
40
+ border-radius: 4px 4px 0 0;
41
+ }
42
+
43
+ /* Active State */
44
+ :host([active]) button {
45
+ color: var(--st-color-primary);
46
+ border-bottom-color: var(--st-color-primary);
47
+ }
48
+
49
+ ::slotted(svg) {
50
+ width: 1.125rem;
51
+ height: 1.125rem;
52
+ fill: currentColor;
53
+ }
54
+ `
55
+ ];
56
+
57
+ private _handleClick() {
58
+ this.dispatchEvent(new CustomEvent('st-tab-select', {
59
+ detail: { panel: this.panel },
60
+ bubbles: true,
61
+ composed: true
62
+ }));
63
+ }
64
+
65
+ render() {
66
+ return html`
67
+ <button
68
+ role="tab"
69
+ aria-selected=${this.active ? 'true' : 'false'}
70
+ tabindex=${this.active ? '0' : '-1'}
71
+ @click=${this._handleClick}
72
+ >
73
+ <slot></slot>
74
+ </button>
75
+ `;
76
+ }
77
+ }
@@ -0,0 +1,88 @@
1
+ import "./tab.js";
2
+ import "./tab-panel.js";
3
+
4
+ import {LitElement, html, css} from "lit";
5
+ import {customElement, queryAssignedElements} from "lit/decorators.js";
6
+ import {resetStyles} from "../../styles/reset.js";
7
+ import type {StTab} from "./tab.js";
8
+ import type {StTabPanel} from "./tab-panel.js";
9
+
10
+ @customElement("st-tabs")
11
+ export class StTabs extends LitElement {
12
+ @queryAssignedElements({slot: "nav", selector: "st-tab"})
13
+ private _tabs!: StTab[];
14
+
15
+ @queryAssignedElements({selector: "st-tab-panel"})
16
+ private _panels!: StTabPanel[];
17
+
18
+ static styles = [
19
+ resetStyles,
20
+ css`
21
+ :host {
22
+ display: block;
23
+ }
24
+
25
+ .nav-container {
26
+ display: flex;
27
+ /* Creates the track line underneath the tabs */
28
+ border-bottom: 1px solid var(--st-color-border);
29
+ gap: var(--st-spacing-xs);
30
+ overflow-x: auto; /* Allows tabs to scroll horizontally on small screens */
31
+ scrollbar-width: none; /* Hides scrollbar on Firefox */
32
+ }
33
+
34
+ .nav-container::-webkit-scrollbar {
35
+ display: none; /* Hides scrollbar on Chrome/Safari */
36
+ }
37
+ `,
38
+ ];
39
+
40
+ connectedCallback() {
41
+ super.connectedCallback();
42
+ this.addEventListener("st-tab-select", this._handleTabSelect as EventListener);
43
+ }
44
+
45
+ disconnectedCallback() {
46
+ super.disconnectedCallback();
47
+ this.removeEventListener("st-tab-select", this._handleTabSelect as EventListener);
48
+ }
49
+
50
+ private _handleTabSelect(e: CustomEvent<{panel: string}>) {
51
+ // 1. Stop the event here so parent <st-tabs> instances don't react to it
52
+ e.stopPropagation();
53
+
54
+ const targetPanel = e.detail.panel;
55
+
56
+ // Update active states on all tabs
57
+ this._tabs.forEach((tab) => {
58
+ tab.active = tab.panel === targetPanel;
59
+ });
60
+
61
+ // Update active states on all panels
62
+ this._panels.forEach((panel) => {
63
+ panel.active = panel.name === targetPanel;
64
+ });
65
+ }
66
+
67
+ // Ensures the first tab is active by default if none are explicitly set
68
+ private _handleSlotChange() {
69
+ if (!this._tabs.length || !this._panels.length) return;
70
+
71
+ const hasActiveTab = this._tabs.some((tab) => tab.active);
72
+ if (!hasActiveTab) {
73
+ this._tabs[0].active = true;
74
+ this._panels[0].active = true;
75
+ }
76
+ }
77
+
78
+ render() {
79
+ return html`
80
+ <div class="nav-container" role="tablist">
81
+ <slot name="nav" @slotchange=${this._handleSlotChange}></slot>
82
+ </div>
83
+ <div class="panels-container">
84
+ <slot></slot>
85
+ </div>
86
+ `;
87
+ }
88
+ }
@@ -0,0 +1,215 @@
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";
4
+ import {resetStyles} from "../../styles/reset.js";
5
+
6
+ export type TextareaResize = "none" | "vertical" | "horizontal" | "both";
7
+
8
+ @customElement("st-textarea")
9
+ export class StTextarea extends LitElement {
10
+ static formAssociated = true;
11
+ private _internals: ElementInternals;
12
+
13
+ @query("textarea") private _textareaEl!: HTMLTextAreaElement;
14
+
15
+ @property({type: String, reflect: true}) name = "";
16
+ @property({type: String}) label = "";
17
+ @property({type: String}) value = "";
18
+ @property({type: String}) placeholder = "";
19
+ @property({attribute: "help-text"}) helpText = "";
20
+
21
+ // Textarea specific properties
22
+ @property({type: Number}) rows = 4;
23
+ @property({type: String, reflect: true}) resize: TextareaResize = "vertical";
24
+
25
+ // Validation
26
+ @property({type: Boolean, reflect: true}) required = false;
27
+ @property({type: Number}) minlength?: number;
28
+ @property({type: Number}) maxlength?: number;
29
+
30
+ // States
31
+ @property({type: Boolean, reflect: true}) disabled = false;
32
+ @property({type: Boolean, reflect: true}) invalid = false;
33
+ @property({type: Boolean, reflect: true}) readonly = false;
34
+
35
+ constructor() {
36
+ super();
37
+ this._internals = this.attachInternals();
38
+
39
+ // 1. Trigger error state if the user clicks away and the field is invalid
40
+ this.addEventListener("focusout", () => {
41
+ this.invalid = !this._textareaEl.validity.valid;
42
+ });
43
+
44
+ // 2. Trigger error state if the st-form tries to submit and fails
45
+ this.addEventListener("invalid", () => {
46
+ this.invalid = true;
47
+ });
48
+ }
49
+
50
+ static styles = [
51
+ resetStyles,
52
+ css`
53
+ :host {
54
+ display: block;
55
+ }
56
+
57
+ .form-control {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: var(--st-spacing-xs);
61
+ }
62
+
63
+ label {
64
+ font-size: var(--st-font-size-sm);
65
+ font-weight: var(--st-font-weight-medium);
66
+ color: var(--st-color-text);
67
+ }
68
+
69
+ textarea {
70
+ width: 100%;
71
+ padding: var(--st-spacing-sm) var(--st-spacing-md);
72
+ font-size: var(--st-font-size-base);
73
+ font-family: inherit;
74
+ color: var(--st-color-text);
75
+ background-color: var(--st-color-bg);
76
+ border: 1px solid var(--st-color-border);
77
+ border-radius: 4px;
78
+ transition:
79
+ border-color 0.2s ease,
80
+ box-shadow 0.2s ease;
81
+
82
+ /* Map the resize property */
83
+ resize: vertical;
84
+ }
85
+
86
+ /* Dynamically handle resize constraints based on the host attribute */
87
+ :host([resize="none"]) textarea {
88
+ resize: none;
89
+ }
90
+ :host([resize="horizontal"]) textarea {
91
+ resize: horizontal;
92
+ }
93
+ :host([resize="both"]) textarea {
94
+ resize: both;
95
+ }
96
+
97
+ textarea::placeholder {
98
+ color: var(--st-color-text-muted);
99
+ }
100
+
101
+ textarea:focus {
102
+ outline: none;
103
+ border-color: var(--st-color-primary);
104
+ box-shadow: 0 0 0 1px var(--st-color-primary);
105
+ }
106
+
107
+ /* Validation States */
108
+ :host([invalid]) textarea {
109
+ border-color: var(--st-color-error);
110
+ }
111
+
112
+ :host([invalid]) textarea:focus {
113
+ box-shadow: 0 0 0 1px var(--st-color-error);
114
+ }
115
+
116
+ :host([invalid]) .help-text {
117
+ color: var(--st-color-error);
118
+ }
119
+
120
+ /* Disabled & Readonly */
121
+ :host([disabled]) textarea {
122
+ opacity: 0.5;
123
+ cursor: not-allowed;
124
+ background-color: var(--st-color-surface);
125
+ }
126
+
127
+ :host([readonly]) textarea {
128
+ background-color: var(--st-color-surface);
129
+ }
130
+
131
+ .help-text {
132
+ font-size: var(--st-font-size-xs);
133
+ color: var(--st-color-text-muted);
134
+ margin: 0;
135
+ }
136
+ `,
137
+ ];
138
+
139
+ override updated(changedProperties: PropertyValues<this>) {
140
+ super.updated(changedProperties);
141
+
142
+ if (changedProperties.has("value")) {
143
+ this._internals.setFormValue(this.value);
144
+ }
145
+ this._syncValidity();
146
+ }
147
+
148
+ private _syncValidity() {
149
+ if (!this._textareaEl) return;
150
+ this._internals.setValidity(
151
+ this._textareaEl.validity,
152
+ this._textareaEl.validationMessage,
153
+ this._textareaEl,
154
+ );
155
+ }
156
+
157
+ formResetCallback() {
158
+ this.value = "";
159
+ this._internals.setFormValue("");
160
+ this.updateComplete.then(() => this._syncValidity());
161
+ }
162
+
163
+ checkValidity() {
164
+ return this._internals.checkValidity();
165
+ }
166
+ reportValidity() {
167
+ return this._internals.reportValidity();
168
+ }
169
+
170
+ private _handleInput(e: Event) {
171
+ const target = e.target as HTMLTextAreaElement;
172
+ this.value = target.value;
173
+
174
+ if (this.invalid && target.validity.valid) {
175
+ this.invalid = false;
176
+ }
177
+
178
+ this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
179
+ }
180
+
181
+ private _handleChange(e: Event) {
182
+ this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
183
+ }
184
+
185
+ render() {
186
+ return html`
187
+ <div class="form-control">
188
+ ${this.label ? html`<label for="textarea">${this.label}</label>` : ""}
189
+
190
+ <textarea
191
+ id="textarea"
192
+ .name=${this.name}
193
+ .value=${this.value}
194
+ placeholder=${this.placeholder}
195
+ rows=${this.rows}
196
+ ?disabled=${this.disabled}
197
+ ?readonly=${this.readonly}
198
+ ?required=${this.required}
199
+ minlength=${ifDefined(this.minlength)}
200
+ maxlength=${ifDefined(this.maxlength)}
201
+ @input=${this._handleInput}
202
+ @change=${this._handleChange}
203
+ ></textarea>
204
+
205
+ ${this.helpText ? html`<div class="help-text">${this.helpText}</div>` : ""}
206
+ </div>
207
+ `;
208
+ }
209
+ }
210
+
211
+ declare global {
212
+ interface HTMLElementTagNameMap {
213
+ "st-textarea": StTextarea;
214
+ }
215
+ }
@@ -0,0 +1,131 @@
1
+ import {LitElement, html, css} from "lit";
2
+ import {customElement, property} from "lit/decorators.js";
3
+ import {resetStyles} from "../../styles/reset.js";
4
+
5
+ export type ThemeMode = "light" | "system" | "dark";
6
+
7
+ @customElement("st-theme-toggle")
8
+ export class StThemeToggle extends LitElement {
9
+ @property({type: String, reflect: true}) theme: ThemeMode = "system";
10
+
11
+ static styles = [
12
+ resetStyles,
13
+ css`
14
+ :host {
15
+ display: inline-flex;
16
+ /* Use semantic tokens for the container */
17
+ background-color: var(--st-color-surface);
18
+ border: 1px solid var(--st-color-border);
19
+ border-radius: 999px;
20
+ padding: 4px;
21
+ gap: 2px;
22
+ }
23
+
24
+ button {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ width: 28px;
29
+ height: 28px;
30
+ border-radius: 50%;
31
+ border: none;
32
+ background: transparent;
33
+ color: var(--st-color-text-muted);
34
+ cursor: pointer;
35
+ transition: all 0.2s ease;
36
+ }
37
+
38
+ button:hover {
39
+ color: var(--st-color-text);
40
+ }
41
+
42
+ /* Active State Styling */
43
+ button[aria-pressed="true"] {
44
+ /* Use the main background color so the active pill "pops" out of the surface */
45
+ background-color: var(--st-color-bg);
46
+ color: var(--st-color-primary);
47
+ /* Subtle shadow to lift the active pill */
48
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
49
+ }
50
+
51
+ svg {
52
+ width: 16px;
53
+ height: 16px;
54
+ fill: none;
55
+ stroke: currentColor;
56
+ stroke-width: 2;
57
+ stroke-linecap: round;
58
+ stroke-linejoin: round;
59
+ }
60
+ `,
61
+ ];
62
+
63
+ private _handleThemeChange(newTheme: ThemeMode) {
64
+ this.theme = newTheme;
65
+
66
+ // Dispatch an event so the consuming app knows to update the document root
67
+ this.dispatchEvent(
68
+ new CustomEvent("st-theme-change", {
69
+ detail: {theme: newTheme},
70
+ bubbles: true,
71
+ composed: true,
72
+ }),
73
+ );
74
+ }
75
+
76
+ override connectedCallback() {
77
+ super.connectedCallback();
78
+ // Check the html tag for the theme injected by your blocking <head> script
79
+ const rootTheme = document.documentElement.getAttribute("data-theme") as ThemeMode;
80
+ if (rootTheme) {
81
+ this.theme = rootTheme;
82
+ }
83
+ }
84
+
85
+ render() {
86
+ return html`
87
+ <button
88
+ aria-pressed=${this.theme === "light"}
89
+ title="Light Mode"
90
+ @click=${() => this._handleThemeChange("light")}
91
+ >
92
+ <!-- Sun Icon -->
93
+ <svg viewBox="0 0 24 24">
94
+ <circle cx="12" cy="12" r="5"></circle>
95
+ <path
96
+ d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
97
+ ></path>
98
+ </svg>
99
+ </button>
100
+
101
+ <button
102
+ aria-pressed=${this.theme === "system"}
103
+ title="System Preference"
104
+ @click=${() => this._handleThemeChange("system")}
105
+ >
106
+ <!-- Monitor/System Icon -->
107
+ <svg viewBox="0 0 24 24">
108
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
109
+ <path d="M8 21h8M12 17v4"></path>
110
+ </svg>
111
+ </button>
112
+
113
+ <button
114
+ aria-pressed=${this.theme === "dark"}
115
+ title="Dark Mode"
116
+ @click=${() => this._handleThemeChange("dark")}
117
+ >
118
+ <!-- Moon Icon -->
119
+ <svg viewBox="0 0 24 24">
120
+ <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
121
+ </svg>
122
+ </button>
123
+ `;
124
+ }
125
+ }
126
+
127
+ declare global {
128
+ interface HTMLElementTagNameMap {
129
+ "st-theme-toggle": StThemeToggle;
130
+ }
131
+ }
@@ -0,0 +1,97 @@
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-vertical-nav-group')
6
+ export class StNavGroup extends LitElement {
7
+ @property({ type: String }) label = '';
8
+ @property({ type: Boolean, reflect: true }) open = false;
9
+
10
+ static styles = [
11
+ resetStyles,
12
+ css`
13
+ :host {
14
+ display: block;
15
+ margin-bottom: var(--st-spacing-sm);
16
+ }
17
+
18
+ button {
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ width: 100%;
23
+ background: transparent;
24
+ border: none;
25
+ padding: var(--st-spacing-xs) var(--st-spacing-lg);
26
+ color: var(--st-color-text);
27
+ font-size: var(--st-font-size-sm);
28
+ font-weight: var(--st-font-weight-bold);
29
+ cursor: pointer;
30
+ text-align: left;
31
+ transition: color 0.2s ease;
32
+ }
33
+
34
+ button:hover {
35
+ color: var(--st-color-primary);
36
+ }
37
+
38
+ svg {
39
+ width: 1rem;
40
+ height: 1rem;
41
+ fill: none;
42
+ stroke: currentColor;
43
+ stroke-width: 2;
44
+ stroke-linecap: round;
45
+ stroke-linejoin: round;
46
+ transition: transform 0.2s ease;
47
+ }
48
+
49
+ /* Rotate Chevron when open */
50
+ :host([open]) svg {
51
+ transform: rotate(180deg);
52
+ }
53
+
54
+ /* Modern CSS Grid Animation trick for accordion height */
55
+ .drawer {
56
+ display: grid;
57
+ grid-template-rows: 0fr;
58
+ transition: grid-template-rows 0.2s ease-in-out;
59
+ }
60
+
61
+ :host([open]) .drawer {
62
+ grid-template-rows: 1fr;
63
+ }
64
+
65
+ .drawer-content {
66
+ overflow: hidden;
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 2px;
70
+ }
71
+ `
72
+ ];
73
+
74
+ private _toggle() {
75
+ this.open = !this.open;
76
+ }
77
+
78
+ render() {
79
+ return html`
80
+ <button @click=${this._toggle} aria-expanded=${this.open}>
81
+ ${this.label}
82
+ <svg viewBox="0 0 24 24">
83
+ <path d="M6 9l6 6 6-6"></path>
84
+ </svg>
85
+ </button>
86
+
87
+ <div class="drawer">
88
+ <div class="drawer-content">
89
+ <!-- We add slight padding top/bottom so the inner links breathe -->
90
+ <div style="padding: 4px 0;">
91
+ <slot></slot>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ `;
96
+ }
97
+ }
@@ -0,0 +1,50 @@
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-vertical-nav-item')
6
+ export class StNavItem extends LitElement {
7
+ @property({ type: String }) href = '#';
8
+ @property({ type: Boolean, reflect: true }) active = false;
9
+
10
+ static styles = [
11
+ resetStyles,
12
+ css`
13
+ :host {
14
+ display: block;
15
+ }
16
+
17
+ a {
18
+ display: flex;
19
+ align-items: center;
20
+ padding: 6px var(--st-spacing-lg);
21
+ color: var(--st-color-text-muted);
22
+ font-size: var(--st-font-size-sm);
23
+ text-decoration: none;
24
+ border-left: 2px solid transparent;
25
+ transition: all 0.15s ease;
26
+ }
27
+
28
+ a:hover {
29
+ color: var(--st-color-text);
30
+ background-color: var(--st-color-surface);
31
+ }
32
+
33
+ /* Active state: Purple text with a left border indicator */
34
+ :host([active]) a {
35
+ color: var(--st-color-primary);
36
+ border-left-color: var(--st-color-primary);
37
+ font-weight: var(--st-font-weight-medium);
38
+ background-color: transparent;
39
+ }
40
+ `
41
+ ];
42
+
43
+ render() {
44
+ return html`
45
+ <a href=${this.href} part="link">
46
+ <slot></slot>
47
+ </a>
48
+ `;
49
+ }
50
+ }