@sondor-technology/sondor-components 1.0.3 → 1.0.5

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 (42) hide show
  1. package/dist/components/button-group/button-group.d.ts +5 -0
  2. package/dist/components/button-group/button-group.d.ts.map +1 -1
  3. package/dist/components/checkbox/checkbox.d.ts +3 -0
  4. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  5. package/dist/components/checkbox/checkbox.js +38 -20
  6. package/dist/components/code-block/code-block.d.ts +5 -0
  7. package/dist/components/code-block/code-block.d.ts.map +1 -1
  8. package/dist/components/form/form-step.d.ts +1 -0
  9. package/dist/components/form/form-step.d.ts.map +1 -1
  10. package/dist/components/form/form-step.js +3 -0
  11. package/dist/components/form/form.d.ts +7 -2
  12. package/dist/components/form/form.d.ts.map +1 -1
  13. package/dist/components/form/form.js +157 -59
  14. package/dist/components/icon/icon.d.ts +2 -1
  15. package/dist/components/icon/icon.d.ts.map +1 -1
  16. package/dist/components/input/input.d.ts +5 -0
  17. package/dist/components/input/input.d.ts.map +1 -1
  18. package/dist/components/page-layout/page-layout.js +1 -1
  19. package/dist/components/select/select.d.ts +43 -7
  20. package/dist/components/select/select.d.ts.map +1 -1
  21. package/dist/components/select/select.js +458 -150
  22. package/dist/components/tabs/tab-panel.d.ts +5 -0
  23. package/dist/components/tabs/tab-panel.d.ts.map +1 -1
  24. package/dist/components/tabs/tab.d.ts +5 -0
  25. package/dist/components/tabs/tab.d.ts.map +1 -1
  26. package/dist/components/tabs/tabs.d.ts +5 -0
  27. package/dist/components/tabs/tabs.d.ts.map +1 -1
  28. package/dist/styles/index.css +2 -0
  29. package/package.json +1 -1
  30. package/src/components/button-group/button-group.ts +6 -0
  31. package/src/components/checkbox/checkbox.ts +54 -26
  32. package/src/components/code-block/code-block.ts +6 -0
  33. package/src/components/form/form-step.ts +5 -1
  34. package/src/components/form/form.ts +182 -66
  35. package/src/components/icon/icon.ts +3 -1
  36. package/src/components/input/input.ts +6 -0
  37. package/src/components/page-layout/page-layout.ts +1 -1
  38. package/src/components/select/select.ts +502 -130
  39. package/src/components/tabs/tab-panel.ts +5 -0
  40. package/src/components/tabs/tab.ts +6 -0
  41. package/src/components/tabs/tabs.ts +6 -0
  42. package/src/styles/theme.css +2 -0
@@ -69,6 +69,8 @@
69
69
  :root[data-theme=light] {
70
70
  --st-color-bg: var(--st-white);
71
71
  --st-color-surface: var(--st-gray-50);
72
+ --st-color-surface-muted: color-mix(in srgb, var(--st-color-surface), black 15%);
73
+ --st-color-surface-hover: color-mix(in srgb, var(--st-color-surface), white 10%);
72
74
  --st-color-border: var(--st-gray-200);
73
75
  --st-color-text: var(--st-gray-900);
74
76
  --st-color-text-muted: var(--st-gray-700);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sondor-technology/sondor-components",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,3 +65,9 @@ export class StButtonGroup extends LitElement {
65
65
  `;
66
66
  }
67
67
  }
68
+
69
+ declare global {
70
+ interface HTMLElementTagNameMap {
71
+ "st-button-group": StButtonGroup;
72
+ }
73
+ }
@@ -11,7 +11,6 @@ export class StCheckbox extends LitElement {
11
11
 
12
12
  @property({type: String, reflect: true}) size: "sm" | "md" | "lg" = "md";
13
13
  @property({type: String, reflect: true}) name = "";
14
- // Checkboxes default to 'on' natively if no value is provided
15
14
  @property({type: String}) value = "on";
16
15
 
17
16
  @property({type: Boolean, reflect: true}) checked = false;
@@ -20,21 +19,20 @@ export class StCheckbox extends LitElement {
20
19
  @property({type: Boolean, reflect: true}) disabled = false;
21
20
  @property({type: Boolean, reflect: true}) invalid = false;
22
21
  @property({type: Boolean, reflect: true}) required = false;
22
+ @property({type: String}) errorMessage = "";
23
23
 
24
24
  @property({attribute: "aria-label"}) ariaLabel = "";
25
25
 
26
- constructor() {
26
+ constructor() {
27
27
  super();
28
28
  this._internals = this.attachInternals();
29
29
 
30
- // 1. Trigger error state if the user clicks away and the field is invalid
31
30
  this.addEventListener('focusout', () => {
32
- this.invalid = !this._inputEl.validity.valid;
31
+ this.invalid = !this.checkValidity();
33
32
  });
34
33
 
35
- // 2. Trigger error state if the st-form tries to submit and fails
36
34
  this.addEventListener('invalid', () => {
37
- this.invalid = true;
35
+ this.invalid = !this.checkValidity();
38
36
  });
39
37
  }
40
38
 
@@ -67,7 +65,6 @@ constructor() {
67
65
  font-size: var(--st-font-size-base);
68
66
  }
69
67
 
70
- /* Visually hide the native input but keep it in the DOM for focus/screen readers */
71
68
  input {
72
69
  position: absolute;
73
70
  width: var(--checkbox-size);
@@ -80,7 +77,6 @@ constructor() {
80
77
  border-width: 0;
81
78
  }
82
79
 
83
- /* Custom Checkbox Box */
84
80
  .control {
85
81
  display: flex;
86
82
  align-items: center;
@@ -92,30 +88,25 @@ constructor() {
92
88
  background-color: var(--st-color-bg);
93
89
  transition: all 0.2s ease;
94
90
  flex-shrink: 0;
95
- /* Aligns the box nicely with standard text line-height */
96
91
  margin-top: 0.125rem;
97
92
  }
98
93
 
99
- /* Hover State */
100
94
  label:hover .control {
101
95
  border-color: var(--st-color-primary-hover);
102
96
  }
103
97
 
104
- /* Focus State */
105
98
  input:focus-visible + .control {
106
99
  box-shadow:
107
100
  0 0 0 2px var(--st-color-bg),
108
101
  0 0 0 4px var(--st-color-primary);
109
102
  }
110
103
 
111
- /* Checked & Indeterminate States */
112
104
  :host([checked]) .control,
113
105
  :host([indeterminate]) .control {
114
106
  border-color: var(--st-color-primary);
115
107
  background-color: var(--st-color-primary);
116
108
  }
117
109
 
118
- /* SVG Icons */
119
110
  svg {
120
111
  width: var(--checkbox-icon-size);
121
112
  height: var(--checkbox-icon-size);
@@ -131,14 +122,12 @@ constructor() {
131
122
  opacity 0.2s ease;
132
123
  }
133
124
 
134
- /* Reveal Icons */
135
125
  :host([checked]) svg.icon-check,
136
126
  :host([indeterminate]) svg.icon-minus {
137
127
  opacity: 1;
138
128
  transform: scale(1);
139
129
  }
140
130
 
141
- /* Disabled State */
142
131
  :host([disabled]) {
143
132
  pointer-events: none;
144
133
  }
@@ -147,7 +136,6 @@ constructor() {
147
136
  cursor: not-allowed;
148
137
  }
149
138
 
150
- /* Invalid State */
151
139
  :host([invalid]) .control {
152
140
  border-color: var(--st-color-error);
153
141
  }
@@ -162,8 +150,7 @@ constructor() {
162
150
  override updated(changedProperties: PropertyValues<this>) {
163
151
  super.updated(changedProperties);
164
152
 
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.
153
+ if (changedProperties.has("checked") || changedProperties.has("value") || changedProperties.has("errorMessage")) {
167
154
  this._internals.setFormValue(this.checked ? this.value : null);
168
155
  this._syncValidity();
169
156
  }
@@ -175,16 +162,54 @@ constructor() {
175
162
 
176
163
  private _syncValidity() {
177
164
  if (!this._inputEl) return;
178
- this._internals.setValidity(
179
- this._inputEl.validity,
180
- this._inputEl.validationMessage,
181
- this._inputEl,
182
- );
165
+
166
+ if (this.required && !this.checked) {
167
+ const message = this.errorMessage || this._inputEl.validationMessage || "Please check this box if you want to proceed.";
168
+ this._internals.setValidity(
169
+ {valueMissing: true},
170
+ message,
171
+ this._inputEl,
172
+ );
173
+ } else {
174
+ this._internals.setValidity(
175
+ this._inputEl.validity,
176
+ this._inputEl.validationMessage,
177
+ this._inputEl,
178
+ );
179
+ }
180
+ }
181
+
182
+ public checkValidity(): boolean {
183
+ if (!this._inputEl) return true;
184
+
185
+ if (this.required && !this.checked) {
186
+ const message = this.errorMessage || this._inputEl.validationMessage || "Please check this box if you want to proceed.";
187
+ this._internals.setValidity(
188
+ {valueMissing: true},
189
+ message,
190
+ this._inputEl,
191
+ );
192
+ this.invalid = true;
193
+ return false;
194
+ }
195
+
196
+ const isValid = this._inputEl.checkValidity();
197
+ this._syncValidity();
198
+ this.invalid = !isValid;
199
+ return isValid;
200
+ }
201
+
202
+ public reportValidity(): boolean {
203
+ this._syncValidity();
204
+ const isValid = this._internals.reportValidity();
205
+ this.invalid = !isValid;
206
+ return isValid;
183
207
  }
184
208
 
185
209
  formResetCallback() {
186
210
  this.checked = false;
187
211
  this.indeterminate = false;
212
+ this.invalid = false;
188
213
  this._internals.setFormValue(null);
189
214
  this.updateComplete.then(() => this._syncValidity());
190
215
  }
@@ -194,12 +219,16 @@ constructor() {
194
219
 
195
220
  if (this.disabled) return;
196
221
 
197
- // Standard native behavior: clicking an indeterminate checkbox unsets the indeterminate state
198
222
  if (this.indeterminate) {
199
223
  this.indeterminate = false;
200
224
  }
201
225
 
202
226
  this.checked = this._inputEl.checked;
227
+
228
+ if (this.checkValidity()) {
229
+ this.invalid = false;
230
+ }
231
+
203
232
  this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
204
233
  }
205
234
 
@@ -218,7 +247,6 @@ constructor() {
218
247
  @change=${this._handleChange}
219
248
  />
220
249
  <div class="control">
221
- <!-- Render both SVGs and use CSS to conditionally reveal them based on host attributes -->
222
250
  ${this.indeterminate
223
251
  ? html`<svg class="icon-minus" viewBox="0 0 24 24"><path d="M5 12h14"></path></svg>`
224
252
  : html`<svg class="icon-check" viewBox="0 0 24 24">
@@ -235,4 +263,4 @@ declare global {
235
263
  interface HTMLElementTagNameMap {
236
264
  "st-checkbox": StCheckbox;
237
265
  }
238
- }
266
+ }
@@ -254,4 +254,10 @@ export class StCodeBlock extends LitElement {
254
254
  </div>
255
255
  `;
256
256
  }
257
+ }
258
+
259
+ declare global {
260
+ interface HTMLElementTagNameMap {
261
+ "st-code-block": StCodeBlock;
262
+ }
257
263
  }
@@ -6,6 +6,10 @@ import {resetStyles} from "../../styles/reset.js";
6
6
  export class StFormStep extends LitElement {
7
7
  @property({type: String}) title = "";
8
8
  @property({type: Boolean, reflect: true}) active = false;
9
+
10
+ // Declare the validate function property (attribute: false prevents reflection of functions to DOM attributes)
11
+ @property({attribute: false})
12
+ validate?: () => Promise<boolean> | boolean;
9
13
 
10
14
  static styles = [
11
15
  resetStyles,
@@ -29,4 +33,4 @@ declare global {
29
33
  interface HTMLElementTagNameMap {
30
34
  "st-form-step": StFormStep;
31
35
  }
32
- }
36
+ }
@@ -1,5 +1,5 @@
1
- import {LitElement, html, css} from "lit";
2
- import {customElement, property, state, queryAssignedElements, query} from "lit/decorators.js";
1
+ import {LitElement, html, css, PropertyValues} from "lit";
2
+ import {customElement, property, state, query} from "lit/decorators.js";
3
3
  import {resetStyles} from "../../styles/reset.js";
4
4
  import type {StFormStep} from "./form-step.js";
5
5
 
@@ -13,11 +13,14 @@ export class StForm extends LitElement {
13
13
 
14
14
  @state() private _currentStepIndex = 0;
15
15
 
16
- @queryAssignedElements({selector: "st-form-step"})
17
- private _stepElements!: StFormStep[];
18
-
19
16
  @query("form") private _formEl!: HTMLFormElement;
20
17
 
18
+ private _modalFooterEl: HTMLElement | null = null;
19
+
20
+ private get _steps(): StFormStep[] {
21
+ return Array.from(this.querySelectorAll("st-form-step"));
22
+ }
23
+
21
24
  static styles = [
22
25
  resetStyles,
23
26
  css`
@@ -31,9 +34,6 @@ export class StForm extends LitElement {
31
34
  gap: var(--st-spacing-md);
32
35
  }
33
36
 
34
- /* ==========================================================
35
- Layouts
36
- ========================================================== */
37
37
  :host([layout="vertical"]) form {
38
38
  flex-direction: column;
39
39
  }
@@ -55,9 +55,6 @@ export class StForm extends LitElement {
55
55
  flex-direction: column;
56
56
  }
57
57
 
58
- /* ==========================================================
59
- Progress Navigation
60
- ========================================================== */
61
58
  .progress-nav {
62
59
  display: flex;
63
60
  align-items: center;
@@ -121,9 +118,6 @@ export class StForm extends LitElement {
121
118
  background-color: var(--st-color-success);
122
119
  }
123
120
 
124
- /* ==========================================================
125
- Actions & States
126
- ========================================================== */
127
121
  .form-actions {
128
122
  display: flex;
129
123
  gap: var(--st-spacing-md);
@@ -162,42 +156,165 @@ export class StForm extends LitElement {
162
156
  `,
163
157
  ];
164
158
 
165
- private _handleSlotChange() {
166
- if (this._stepElements.length > 0) {
159
+ override connectedCallback() {
160
+ super.connectedCallback();
161
+ this.updateComplete.then(() => {
167
162
  this._updateStepVisibility();
163
+ this._updateModalFooter();
164
+ });
165
+ }
166
+
167
+ override disconnectedCallback() {
168
+ super.disconnectedCallback();
169
+ if (this._modalFooterEl) {
170
+ this._modalFooterEl.remove();
171
+ this._modalFooterEl = null;
172
+ }
173
+ }
174
+
175
+ override updated(changedProperties: PropertyValues<this>) {
176
+ super.updated(changedProperties);
177
+ this._updateModalFooter();
178
+ }
179
+
180
+ private _updateModalFooter() {
181
+ const modal = this.closest("st-modal");
182
+ if (modal) {
183
+ if (!this._modalFooterEl) {
184
+ this._modalFooterEl = document.createElement("div");
185
+ this._modalFooterEl.setAttribute("slot", "footer");
186
+ this._modalFooterEl.style.display = "flex";
187
+ this._modalFooterEl.style.gap = "var(--st-spacing-md)";
188
+ this._modalFooterEl.style.width = "100%";
189
+ this._modalFooterEl.style.justifyContent = "flex-end";
190
+ this._modalFooterEl.style.alignItems = "center";
191
+ modal.appendChild(this._modalFooterEl);
192
+ }
193
+
194
+ const steps = this._steps;
195
+ const hasSteps = steps.length > 0;
196
+ const isLastStep = hasSteps ? this._currentStepIndex === steps.length - 1 : true;
197
+
198
+ // Imperative DOM management prevents event listener loss and re-rendering conflicts
199
+ this._modalFooterEl.innerHTML = "";
200
+
201
+ if (hasSteps && this._currentStepIndex > 0) {
202
+ const backBtn = document.createElement("st-button");
203
+ backBtn.setAttribute("variant", "secondary");
204
+ backBtn.setAttribute("type", "button");
205
+ if (this.loading) backBtn.setAttribute("disabled", "");
206
+ backBtn.textContent = "Back";
207
+ backBtn.addEventListener("click", (e) => {
208
+ e.preventDefault();
209
+ this._prevStep();
210
+ });
211
+ this._modalFooterEl.appendChild(backBtn);
212
+ }
213
+
214
+ const submitBtn = document.createElement("st-button");
215
+ submitBtn.setAttribute("color", "primary");
216
+ submitBtn.setAttribute("type", "button");
217
+ if (this.loading) submitBtn.setAttribute("disabled", "");
218
+ submitBtn.textContent = this.loading ? "Submitting..." : (isLastStep ? "Submit" : "Continue");
219
+ submitBtn.addEventListener("click", (e) => {
220
+ e.preventDefault();
221
+ this._triggerSubmission();
222
+ });
223
+ this._modalFooterEl.appendChild(submitBtn);
224
+
225
+ } else if (this._modalFooterEl) {
226
+ this._modalFooterEl.remove();
227
+ this._modalFooterEl = null;
168
228
  }
169
229
  }
170
230
 
231
+ private _handleSlotChange() {
232
+ this._updateStepVisibility();
233
+ this._updateModalFooter();
234
+ }
235
+
171
236
  private _updateStepVisibility() {
172
- this._stepElements.forEach((step, index) => {
237
+ const steps = this._steps;
238
+ steps.forEach((step, index) => {
173
239
  step.active = index === this._currentStepIndex;
174
240
  });
175
241
  this.requestUpdate();
176
242
  }
177
243
 
178
- private _validateCurrentContext(): boolean {
179
- if (this._stepElements.length === 0) {
180
- return this._formEl.checkValidity();
181
- }
244
+ private async _validateCurrentContext(): Promise<boolean> {
245
+ const steps = this._steps;
246
+ const scope: ParentNode = steps.length > 0
247
+ ? steps[this._currentStepIndex]
248
+ : this;
182
249
 
183
- const currentStep = this._stepElements[this._currentStepIndex];
184
250
  let isValid = true;
185
251
 
186
- // Find all custom and native form inputs assigned to the current step
187
252
  const inputs = Array.from(
188
- currentStep.querySelectorAll<
189
- HTMLElement & {checkValidity?: () => boolean; reportValidity?: () => void}
190
- >("*"),
253
+ scope.querySelectorAll<
254
+ HTMLElement & {
255
+ checkValidity?: () => boolean;
256
+ reportValidity?: () => boolean;
257
+ required?: boolean;
258
+ value?: any;
259
+ checked?: boolean;
260
+ invalid?: boolean;
261
+ }
262
+ >("st-input, st-select, st-textarea, st-checkbox, st-radio, input, select, textarea"),
191
263
  );
192
-
264
+
193
265
  for (const input of inputs) {
266
+ if (input.hasAttribute("disabled") || (input as any).disabled) continue;
267
+
268
+ let fieldValid = true;
269
+
194
270
  if (typeof input.checkValidity === "function") {
195
- if (!input.checkValidity()) {
196
- // Focus and report on the first invalid element encountered
197
- if (isValid && typeof input.reportValidity === "function") {
198
- input.reportValidity();
271
+ fieldValid = input.checkValidity();
272
+ } else {
273
+ const isRequired = input.hasAttribute("required") || input.required;
274
+ if (isRequired) {
275
+ const isToggle = input.tagName === "ST-CHECKBOX" || input.tagName === "ST-RADIO";
276
+ const val = isToggle ? input.checked : input.value;
277
+ if (val === undefined || val === null || val === "" || val === false) {
278
+ fieldValid = false;
199
279
  }
280
+ }
281
+ }
282
+
283
+ if (!fieldValid) {
284
+ if (typeof input.reportValidity === "function") {
285
+ input.reportValidity();
286
+ } else if ("invalid" in input) {
287
+ input.invalid = true;
288
+ }
289
+ if (isValid && typeof (input as any).focus === "function") {
290
+ (input as any).focus();
291
+ }
292
+ isValid = false;
293
+ }
294
+ }
295
+
296
+ if (steps.length === 0 && this._formEl) {
297
+ if (!this._formEl.checkValidity()) {
298
+ isValid = false;
299
+ }
300
+ }
301
+
302
+ if (isValid && steps.length > 0) {
303
+ const currentStep = steps[this._currentStepIndex] as StFormStep & {
304
+ validate?: () => Promise<boolean> | boolean;
305
+ };
306
+
307
+ if (typeof currentStep.validate === "function") {
308
+ try {
309
+ this.loading = true;
310
+ const stepPassed = await currentStep.validate();
311
+ if (!stepPassed) {
312
+ isValid = false;
313
+ }
314
+ } catch {
200
315
  isValid = false;
316
+ } finally {
317
+ this.loading = false;
201
318
  }
202
319
  }
203
320
  }
@@ -206,7 +323,6 @@ export class StForm extends LitElement {
206
323
  }
207
324
 
208
325
  private _triggerSubmission() {
209
- // HTML5 method that triggers form validation and fires the native submit event
210
326
  this._formEl.requestSubmit();
211
327
  }
212
328
 
@@ -220,13 +336,11 @@ export class StForm extends LitElement {
220
336
  private _extractFormData(): FormData {
221
337
  const formData = new FormData();
222
338
 
223
- // Query all light-DOM children that have a 'name' attribute
224
339
  const fields = this.querySelectorAll<
225
340
  HTMLElement & {name: string; value: any; checked?: boolean; files?: FileList}
226
341
  >("[name]");
227
342
 
228
343
  fields.forEach((field) => {
229
- // Standard HTML form behavior: ignore disabled fields
230
344
  if (field.hasAttribute("disabled")) return;
231
345
 
232
346
  const isToggle =
@@ -236,7 +350,6 @@ export class StForm extends LitElement {
236
350
  field.getAttribute("type") === "checkbox";
237
351
 
238
352
  if (isToggle) {
239
- // Only append radios/checkboxes if they are actively checked
240
353
  if (field.checked) {
241
354
  formData.append(field.name, field.value || "on");
242
355
  }
@@ -245,10 +358,8 @@ export class StForm extends LitElement {
245
358
  field.getAttribute("type") === "file" &&
246
359
  field.files
247
360
  ) {
248
- // Handle native file uploads
249
361
  Array.from(field.files).forEach((file) => formData.append(field.name, file));
250
362
  } else if (field.value !== undefined) {
251
- // Handle st-input, st-select, st-textarea, and standard inputs
252
363
  formData.append(field.name, field.value);
253
364
  }
254
365
  });
@@ -256,14 +367,16 @@ export class StForm extends LitElement {
256
367
  return formData;
257
368
  }
258
369
 
259
- private _handleSubmit(e: Event) {
370
+ private async _handleSubmit(e: Event) {
260
371
  e.preventDefault();
261
372
  e.stopPropagation();
262
373
 
263
- if (!this._validateCurrentContext()) return;
374
+ const isValid = await this._validateCurrentContext();
375
+ if (!isValid) return;
264
376
 
265
- const hasSteps = this._stepElements.length > 0;
266
- const isLastStep = hasSteps ? this._currentStepIndex === this._stepElements.length - 1 : true;
377
+ const steps = this._steps;
378
+ const hasSteps = steps.length > 0;
379
+ const isLastStep = hasSteps ? this._currentStepIndex === steps.length - 1 : true;
267
380
 
268
381
  if (hasSteps && !isLastStep) {
269
382
  this._currentStepIndex++;
@@ -271,7 +384,6 @@ export class StForm extends LitElement {
271
384
  return;
272
385
  }
273
386
 
274
- // Use our custom extractor to pierce the Shadow DOM boundary
275
387
  const formData = this._extractFormData();
276
388
 
277
389
  this.dispatchEvent(
@@ -285,8 +397,9 @@ export class StForm extends LitElement {
285
397
  }
286
398
 
287
399
  render() {
288
- const hasSteps = this._stepElements?.length > 0;
289
- const isLastStep = hasSteps ? this._currentStepIndex === this._stepElements.length - 1 : true;
400
+ const steps = this._steps;
401
+ const hasSteps = steps.length > 0;
402
+ const isInModal = !!this.closest("st-modal");
290
403
 
291
404
  return html`
292
405
  <div class="${hasSteps ? "has-steps" : ""}" aria-busy=${this.loading}>
@@ -300,7 +413,7 @@ export class StForm extends LitElement {
300
413
  ${hasSteps
301
414
  ? html`
302
415
  <div class="progress-nav" role="tablist">
303
- ${this._stepElements.map((step, index) => {
416
+ ${steps.map((step, index) => {
304
417
  const isActive = index === this._currentStepIndex;
305
418
  const isCompleted = index < this._currentStepIndex;
306
419
 
@@ -319,7 +432,7 @@ export class StForm extends LitElement {
319
432
  </div>
320
433
  <span>${step.title || `Step ${index + 1}`}</span>
321
434
  </div>
322
- ${index < this._stepElements.length - 1
435
+ ${index < steps.length - 1
323
436
  ? html` <div class="step-connector ${isCompleted ? "completed" : ""}"></div> `
324
437
  : ""}
325
438
  `;
@@ -339,30 +452,33 @@ export class StForm extends LitElement {
339
452
  <form @submit=${this._handleSubmit}>
340
453
  <slot @slotchange=${this._handleSlotChange}></slot>
341
454
 
342
- <div class="form-actions">
343
- ${hasSteps && this._currentStepIndex > 0
344
- ? html`
455
+ ${!isInModal
456
+ ? html`
457
+ <div class="form-actions">
458
+ ${hasSteps && this._currentStepIndex > 0
459
+ ? html`
460
+ <st-button
461
+ variant="secondary"
462
+ type="button"
463
+ @click=${this._prevStep}
464
+ ?disabled=${this.loading}
465
+ >
466
+ Back
467
+ </st-button>
468
+ `
469
+ : ""}
470
+
345
471
  <st-button
346
- variant="secondary"
472
+ color="primary"
347
473
  type="button"
348
- @click=${this._prevStep}
474
+ @click=${this._triggerSubmission}
349
475
  ?disabled=${this.loading}
350
476
  >
351
- Back
477
+ ${this.loading ? "Submitting..." : hasSteps && this._currentStepIndex === steps.length - 1 ? "Submit" : hasSteps ? "Continue" : "Submit"}
352
478
  </st-button>
353
- `
354
- : ""}
355
-
356
- <!-- Type is "button", but click triggers requestSubmit() on the internal form -->
357
- <st-button
358
- color="primary"
359
- type="button"
360
- @click=${this._triggerSubmission}
361
- ?disabled=${this.loading}
362
- >
363
- ${this.loading ? "Submitting..." : isLastStep ? "Submit" : "Continue"}
364
- </st-button>
365
- </div>
479
+ </div>
480
+ `
481
+ : ""}
366
482
  </form>
367
483
  </div>
368
484
  `;
@@ -373,4 +489,4 @@ declare global {
373
489
  interface HTMLElementTagNameMap {
374
490
  "st-form": StForm;
375
491
  }
376
- }
492
+ }
@@ -3,10 +3,12 @@ import { customElement, property } from 'lit/decorators.js';
3
3
  import { iconPaths, IconName } from './icon-paths.js';
4
4
  import { resetStyles } from '../../styles/reset.js';
5
5
 
6
+ export type IconVariant = 'outline' | 'solid';
7
+
6
8
  @customElement('st-icon')
7
9
  export class StIcon extends LitElement {
8
10
  @property({ type: String }) name: IconName | string = '';
9
- @property({ type: String }) variant: 'outline' | 'solid' = 'outline';
11
+ @property({ type: String }) variant: IconVariant = 'outline';
10
12
 
11
13
  // Semantic Colors
12
14
  @property({ type: String, reflect: true }) color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' = 'default';
@@ -246,3 +246,9 @@ export class StInput extends LitElement {
246
246
  `;
247
247
  }
248
248
  }
249
+
250
+ declare global {
251
+ interface HTMLElementTagNameMap {
252
+ "st-input": StInput;
253
+ }
254
+ }
@@ -153,7 +153,7 @@ export class StPageLayout extends LitElement {
153
153
  --layout-surface-color: var(--st-color-surface, #f8fafc);
154
154
  --layout-border-color: var(--st-color-border, #e2e8f0);
155
155
  --layout-sidebar-width: 16rem;
156
- --layout-right-sidebar-width: 14rem;
156
+ --layout-right-sidebar-width: 16rem;
157
157
  --layout-main-max-width: none;
158
158
  --layout-nav-height: 4rem;
159
159
  }