@inglorious/ui 8.6.0 → 9.1.0

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 (76) hide show
  1. package/package.json +2 -2
  2. package/src/controls/button/button.stories.js +7 -1
  3. package/src/controls/button/style.css +11 -0
  4. package/src/controls/button/template.js +4 -4
  5. package/src/controls/checkbox/checkbox.stories.js +12 -0
  6. package/src/controls/checkbox/style.css +11 -0
  7. package/src/controls/checkbox/template.js +9 -1
  8. package/src/controls/combobox/style.css +14 -7
  9. package/src/controls/combobox/template.js +12 -2
  10. package/src/controls/radio-group/radio-group.stories.js +21 -1
  11. package/src/controls/radio-group/style.css +11 -0
  12. package/src/controls/radio-group/template.js +11 -1
  13. package/src/controls/rating/rating.stories.js +8 -0
  14. package/src/controls/rating/style.css +26 -4
  15. package/src/controls/rating/template.js +29 -12
  16. package/src/controls/slider/slider.stories.js +28 -1
  17. package/src/controls/slider/style.css +102 -0
  18. package/src/controls/slider/template.js +114 -15
  19. package/src/controls/switch/style.css +11 -0
  20. package/src/controls/switch/switch.stories.js +10 -0
  21. package/src/controls/switch/template.js +14 -2
  22. package/src/data-display/chip/chip.stories.js +17 -0
  23. package/src/data-display/chip/style.css +5 -0
  24. package/src/data-display/chip/template.js +3 -1
  25. package/src/data-display/divider/divider.stories.js +9 -0
  26. package/src/data-display/divider/template.js +12 -4
  27. package/src/data-display/tooltip/style.css +22 -14
  28. package/src/examples/dashboard/primitive-content.js +6 -6
  29. package/src/examples/dashboard/style.css +11 -5
  30. package/src/feedback/alert/alert.stories.js +9 -1
  31. package/src/feedback/alert/style.css +5 -0
  32. package/src/feedback/alert/template.js +2 -1
  33. package/src/feedback/dialog/dialog.stories.js +31 -0
  34. package/src/feedback/dialog/style.css +27 -13
  35. package/src/feedback/dialog/template.js +68 -18
  36. package/src/feedback/progress/progress.stories.js +5 -0
  37. package/src/feedback/progress/style.css +47 -23
  38. package/src/feedback/progress/template.js +18 -11
  39. package/src/feedback/snackbar/style.css +13 -0
  40. package/src/feedback/snackbar/template.js +7 -0
  41. package/src/layout/flex/flex.stories.js +1 -1
  42. package/src/layout/flex/style.css +8 -0
  43. package/src/navigation/drawer/drawer.stories.js +3 -7
  44. package/src/navigation/drawer/style.css +59 -18
  45. package/src/navigation/drawer/template.js +137 -80
  46. package/src/navigation/menu/menu.stories.js +15 -0
  47. package/src/navigation/menu/style.css +19 -0
  48. package/src/navigation/menu/template.js +59 -6
  49. package/src/navigation/speed-dial/style.css +32 -5
  50. package/src/navigation/speed-dial/template.js +41 -12
  51. package/src/navigation/tabs/template.js +52 -4
  52. package/src/shared/anchor.js +11 -0
  53. package/src/shared/top-layer.js +83 -0
  54. package/src/surfaces/accordion/accordion.stories.js +9 -0
  55. package/src/surfaces/accordion/style.css +14 -7
  56. package/src/surfaces/accordion/template.js +31 -20
  57. package/src/surfaces/card/card.stories.js +24 -0
  58. package/src/surfaces/card/style.css +15 -0
  59. package/src/surfaces/card/template.js +2 -0
  60. package/types/controls/button.d.ts +1 -1
  61. package/types/controls/checkbox.d.ts +2 -0
  62. package/types/controls/radio-group.d.ts +2 -0
  63. package/types/controls/rating.d.ts +4 -0
  64. package/types/controls/slider.d.ts +6 -2
  65. package/types/controls/switch.d.ts +2 -0
  66. package/types/data-display/chip.d.ts +2 -0
  67. package/types/data-display/divider.d.ts +2 -0
  68. package/types/feedback/alert.d.ts +1 -1
  69. package/types/feedback/dialog.d.ts +5 -1
  70. package/types/feedback/progress.d.ts +2 -0
  71. package/types/layout/flex.d.ts +1 -1
  72. package/types/navigation/drawer.d.ts +8 -3
  73. package/types/navigation/menu.d.ts +17 -0
  74. package/types/navigation/speed-dial.d.ts +5 -0
  75. package/types/surfaces/accordion.d.ts +2 -0
  76. package/types/surfaces/card.d.ts +5 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/ui",
3
- "version": "8.6.0",
3
+ "version": "9.1.0",
4
4
  "description": "A design system for Inglorious Web with render-function components and customizable themes",
5
5
  "author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -315,7 +315,7 @@
315
315
  "@iconify-icons/fa7-regular": "^2.0.0",
316
316
  "@iconify-icons/fa7-solid": "^2.0.0",
317
317
  "@iconify-icons/material-symbols": "^2.0.6",
318
- "@inglorious/charts": "11.1.7",
318
+ "@inglorious/charts": "12.0.0",
319
319
  "@inglorious/eslint-config": "1.2.0",
320
320
  "@inglorious/store": "12.0.0",
321
321
  "@inglorious/vite-plugin-jsx": "2.0.5",
@@ -32,7 +32,7 @@ export default {
32
32
  },
33
33
  size: {
34
34
  control: "select",
35
- options: ["sm", "md", "lg"],
35
+ options: ["xs", "sm", "md", "lg"],
36
36
  description: "Size scale for paddings and font-size.",
37
37
  },
38
38
  shape: {
@@ -106,6 +106,12 @@ Ghost.args = {
106
106
  variant: "ghost",
107
107
  }
108
108
 
109
+ export const ExtraSmall = {}
110
+ ExtraSmall.args = {
111
+ ...Default.args,
112
+ size: "xs",
113
+ }
114
+
109
115
  export const Small = {}
110
116
  Small.args = {
111
117
  ...Default.args,
@@ -153,6 +153,11 @@
153
153
  cursor: not-allowed;
154
154
  }
155
155
 
156
+ &.iw-button-xs {
157
+ padding: var(--iw-space-0) var(--iw-space-2);
158
+ font-size: var(--iw-font-size-xs);
159
+ }
160
+
156
161
  &.iw-button-sm {
157
162
  padding: var(--iw-space-1) var(--iw-space-3);
158
163
  font-size: var(--iw-font-size-sm);
@@ -179,6 +184,12 @@
179
184
  border-radius: var(--iw-radius-sm);
180
185
  }
181
186
 
187
+ &.iw-button-xs.iw-button-shape-round,
188
+ &.iw-button-xs.iw-button-shape-square {
189
+ width: 1.5rem;
190
+ height: 1.5rem;
191
+ }
192
+
182
193
  &.iw-button-sm.iw-button-shape-round,
183
194
  &.iw-button-sm.iw-button-shape-square {
184
195
  width: 2rem;
@@ -35,8 +35,8 @@ export function render(props) {
35
35
  isPressed = false,
36
36
  isFullWidth = false,
37
37
  buttonType = "button",
38
- ariaLabel = "",
39
- isAriaPressed = false,
38
+ ariaLabel,
39
+ isAriaPressed,
40
40
  className = "",
41
41
  accept,
42
42
  isMultiple = false,
@@ -74,8 +74,8 @@ export function render(props) {
74
74
  return html`
75
75
  <button
76
76
  type=${buttonType}
77
- aria-label=${ariaLabel}
78
- aria-pressed=${isAriaPressed}
77
+ aria-label=${ariaLabel || nothing}
78
+ aria-pressed=${isAriaPressed ?? nothing}
79
79
  class=${classMap(classes)}
80
80
  ?disabled=${isDisabled}
81
81
  @click=${handleClick}
@@ -7,6 +7,10 @@ export default {
7
7
  render: createRender(Checkbox),
8
8
  argTypes: {
9
9
  label: { control: "text", description: "Label text." },
10
+ description: {
11
+ control: "text",
12
+ description: "A line of detail under the label.",
13
+ },
10
14
  name: { control: "text", description: "Native HTML name attribute." },
11
15
  isChecked: { control: "boolean", description: "Current checked state." },
12
16
  isDisabled: { control: "boolean", description: "Disables interaction." },
@@ -48,3 +52,11 @@ Checked.args = {
48
52
  ...Default.args,
49
53
  isChecked: true,
50
54
  }
55
+
56
+ export const WithDescription = {}
57
+ WithDescription.args = {
58
+ ...Default.args,
59
+ label: "Weekly summary",
60
+ name: "summary",
61
+ description: "An email every Monday with last week's totals.",
62
+ }
@@ -14,6 +14,17 @@
14
14
  accent-color: var(--iw-checkbox-accent);
15
15
  }
16
16
 
17
+ /* With a description, the label and it stack beside the box. */
18
+ .iw-checkbox-text {
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+
23
+ .iw-checkbox-description {
24
+ font-size: var(--iw-font-size-sm);
25
+ color: var(--iw-color-text-secondary);
26
+ }
27
+
17
28
  .iw-checkbox-label {
18
29
  font-size: var(--iw-font-size-base);
19
30
  line-height: var(--iw-line-height-normal);
@@ -21,6 +21,7 @@ export function render(props) {
21
21
  type, // eslint-disable-line no-unused-vars
22
22
  name = "",
23
23
  label = "",
24
+ description,
24
25
  isChecked = false,
25
26
  isDisabled = false,
26
27
  isRequired = false,
@@ -54,7 +55,14 @@ export function render(props) {
54
55
  @change=${(event) => onChange?.(event.target.checked)}
55
56
  ${ref((element) => applyElementProps(element, rest))}
56
57
  />
57
- <span class="iw-checkbox-label">${label}</span>
58
+ ${
59
+ description
60
+ ? html`<span class="iw-checkbox-text">
61
+ <span class="iw-checkbox-label">${label}</span>
62
+ <span class="iw-checkbox-description">${description}</span>
63
+ </span>`
64
+ : html`<span class="iw-checkbox-label">${label}</span>`
65
+ }
58
66
  </label>
59
67
  `
60
68
  }
@@ -5,7 +5,8 @@
5
5
  flex-direction: column;
6
6
  gap: var(--iw-space-1);
7
7
  font-family: var(--iw-font-family-sans);
8
- position: relative; /* required for dropdown positioning */
8
+ /* Each combobox anchors its own dropdown to its own frame. */
9
+ anchor-scope: --iw-combobox;
9
10
 
10
11
  &.iw-combobox-full-width {
11
12
  width: 100%;
@@ -38,6 +39,7 @@
38
39
  }
39
40
 
40
41
  .iw-combobox-frame {
42
+ anchor-name: --iw-combobox;
41
43
  display: flex;
42
44
  align-items: center;
43
45
  /* The gap spaces the text from the arrow, as it does for Input's icons. */
@@ -182,12 +184,17 @@
182
184
  }
183
185
  }
184
186
 
187
+ /* A popover in the top layer, placed under the frame and as wide as it,
188
+ flipping above when there is no room below. The browser's popover box
189
+ is centred in the viewport, so its insets, margin and colours are reset. */
185
190
  .iw-combobox-dropdown {
186
- position: absolute;
187
- top: 100%;
188
- left: 0;
189
- right: 0;
190
- z-index: 1000;
191
+ position-anchor: --iw-combobox;
192
+ position-area: block-end span-inline-end;
193
+ position-try-fallbacks: flip-block;
194
+ inset: auto;
195
+ width: anchor-size(width);
196
+ padding: 0;
197
+ color: var(--iw-color-text);
191
198
  display: flex;
192
199
  flex-direction: column;
193
200
  max-height: 16rem;
@@ -198,7 +205,7 @@
198
205
  border-start-end-radius: 0;
199
206
  background: var(--iw-color-bg);
200
207
  box-shadow: var(--iw-shadow-lg);
201
- margin-top: -1px;
208
+ margin: -1px 0 0;
202
209
 
203
210
  .iw-combobox-dropdown-search {
204
211
  width: 100%;
@@ -12,6 +12,7 @@ import { when } from "@inglorious/web/directives/when"
12
12
 
13
13
  import { Chip } from "../../data-display/chip/index.js"
14
14
  import { fallbackLabel } from "../../shared/fallback-label.js"
15
+ import { syncPopover } from "../../shared/top-layer.js"
15
16
  import {
16
17
  filterOptions,
17
18
  formatOption,
@@ -263,7 +264,12 @@ export const Combobox = {
263
264
 
264
265
  /**
265
266
  * Renders the dropdown panel that contains search input, options, and empty/loading states.
266
- * Shown only when the combobox is open.
267
+ * Shown only when the combobox is open, as a popover in the top layer so no
268
+ * container can clip it, anchored under the control by CSS.
269
+ *
270
+ * A manual popover: the control that opens it is not a button, so it cannot
271
+ * be the popover's invoker, and a light dismiss would close the dropdown on
272
+ * the very press that toggles it. Outside presses and Esc are handled here.
267
273
  * @param {ComboboxProps} props
268
274
  * @returns {TemplateResult}
269
275
  */
@@ -276,7 +282,11 @@ export const Combobox = {
276
282
  id=${id ? `${id}-control-listbox` : nothing}
277
283
  class="iw-combobox-dropdown"
278
284
  role="listbox"
279
- ${ref((el) => trackOutsideClick(props, el))}
285
+ popover="manual"
286
+ ${ref((el) => {
287
+ syncPopover(el, { isOpen: true })
288
+ trackOutsideClick(props, el)
289
+ })}
280
290
  >
281
291
  ${when(isSearchable, () => this.renderSearchInput?.(props))}
282
292
  ${when(isLoading, () => this.renderLoading?.(props))}
@@ -20,7 +20,11 @@ export default {
20
20
  description: "Semantic accent color.",
21
21
  },
22
22
  isDisabled: { control: "boolean", description: "Disables all options." },
23
- options: { control: "object", description: "List of radio options." },
23
+ options: {
24
+ control: "object",
25
+ description:
26
+ "List of radio options: `label`, `value`, and an optional `description` under the label.",
27
+ },
24
28
  onChange: { action: "onChange" },
25
29
  },
26
30
  parameters: {
@@ -52,3 +56,19 @@ Row.args = {
52
56
  ...Default.args,
53
57
  direction: "row",
54
58
  }
59
+
60
+ export const WithDescriptions = {}
61
+ WithDescriptions.args = {
62
+ ...Default.args,
63
+ label: "Shipping",
64
+ name: "shipping",
65
+ value: "standard",
66
+ options: [
67
+ {
68
+ label: "Standard",
69
+ value: "standard",
70
+ description: "3 to 5 working days, free",
71
+ },
72
+ { label: "Express", value: "express", description: "Next day, €9" },
73
+ ],
74
+ }
@@ -19,6 +19,17 @@
19
19
  cursor: pointer;
20
20
  }
21
21
 
22
+ /* With a description, the label and it stack beside the circle. */
23
+ .iw-radio-text {
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+
28
+ .iw-radio-description {
29
+ font-size: var(--iw-font-size-sm);
30
+ color: var(--iw-color-text-secondary);
31
+ }
32
+
22
33
  .iw-radio {
23
34
  margin: 0;
24
35
  accent-color: var(--iw-radio-accent);
@@ -55,6 +55,7 @@ export function render(props) {
55
55
  ${options.map((option, index) => {
56
56
  const {
57
57
  label: optionLabel,
58
+ description: optionDescription,
58
59
  value: optionValue,
59
60
  isDisabled: optionDisabled = false,
60
61
  ...optionRest
@@ -73,7 +74,16 @@ export function render(props) {
73
74
  @change=${(event) => onChange?.(event.target.value)}
74
75
  ${ref((element) => applyElementProps(element, optionRest))}
75
76
  />
76
- <span>${optionLabel}</span>
77
+ ${
78
+ optionDescription
79
+ ? html`<span class="iw-radio-text">
80
+ <span>${optionLabel}</span>
81
+ <span class="iw-radio-description"
82
+ >${optionDescription}</span
83
+ >
84
+ </span>`
85
+ : html`<span>${optionLabel}</span>`
86
+ }
77
87
  </label>
78
88
  `
79
89
  })}
@@ -8,6 +8,14 @@ export default {
8
8
  argTypes: {
9
9
  value: { control: "number", description: "Current rating value." },
10
10
  max: { control: "number", description: "Maximum rating value." },
11
+ name: {
12
+ control: "text",
13
+ description: "Groups the radios and names the value in a form.",
14
+ },
15
+ ariaLabel: {
16
+ control: "text",
17
+ description: 'Names the group for assistive tech. Defaults to "Rating".',
18
+ },
11
19
  isDisabled: { control: "boolean", description: "Disables interaction." },
12
20
  isReadOnly: {
13
21
  control: "boolean",
@@ -1,17 +1,39 @@
1
+ /* A fieldset of native radios: the inputs stay in the page for the keyboard
2
+ and assistive tech but out of sight, and each star is the label that
3
+ checks its radio. */
1
4
  .iw-rating {
2
5
  display: inline-flex;
3
6
  align-items: center;
4
7
  gap: var(--iw-space-1);
8
+ margin: 0;
9
+ padding: 0;
10
+ border: 0;
11
+ min-inline-size: 0;
5
12
 
6
13
  .iw-rating-item {
7
- border: 0;
8
- background: transparent;
9
- padding: 0;
10
- margin: 0;
14
+ position: relative;
11
15
  cursor: pointer;
12
16
  color: var(--iw-color-warning);
13
17
  font-size: 1.25rem;
14
18
  line-height: 1;
19
+ border-radius: var(--iw-radius-sm);
20
+ }
21
+
22
+ .iw-rating-input {
23
+ position: absolute;
24
+ inset: 0;
25
+ margin: 0;
26
+ opacity: 0;
27
+ cursor: inherit;
28
+ }
29
+
30
+ .iw-rating-item:has(.iw-rating-input:focus-visible) {
31
+ outline: 2px solid var(--iw-color-primary);
32
+ outline-offset: 2px;
33
+ }
34
+
35
+ &:disabled .iw-rating-item {
36
+ cursor: default;
15
37
  }
16
38
 
17
39
  &.iw-rating-sm .iw-rating-item {
@@ -3,7 +3,7 @@
3
3
  * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
4
4
  */
5
5
 
6
- import { html } from "@inglorious/web"
6
+ import { html, nothing } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
 
9
9
  const DEFAULT_VALUE = 0
@@ -12,12 +12,16 @@ const INDEX_TO_RATING = 1
12
12
 
13
13
  /**
14
14
  * Renders a star (or custom symbol) rating control.
15
+ * Each star is a native radio, so the arrow keys move between them, the group
16
+ * is announced as one choice, and the value submits with a form under `name`.
15
17
  * Supports max value, read-only/disabled states, and emits selection changes.
16
18
  * @param {RatingProps} props
17
19
  * @returns {TemplateResult}
18
20
  */
19
21
  export function render(props) {
20
22
  const {
23
+ id,
24
+ name,
21
25
  value = DEFAULT_VALUE,
22
26
  max = DEFAULT_MAX,
23
27
  isDisabled = false,
@@ -25,9 +29,14 @@ export function render(props) {
25
29
  symbol = "★",
26
30
  emptySymbol = "☆",
27
31
  size = "md",
32
+ ariaLabel = "Rating",
28
33
  onChange,
29
34
  } = props
30
35
 
36
+ // Radios are a group by sharing a name: without one, the arrow keys do not
37
+ // move between the stars.
38
+ const groupName = name || id
39
+
31
40
  const classes = {
32
41
  "iw-rating": true,
33
42
  [`iw-rating-${size}`]: size !== "md",
@@ -36,22 +45,30 @@ export function render(props) {
36
45
  }
37
46
 
38
47
  return html`
39
- <div class=${classMap(classes)} role="radiogroup" aria-label="rating">
48
+ <fieldset
49
+ id=${id || nothing}
50
+ class=${classMap(classes)}
51
+ aria-label=${ariaLabel}
52
+ ?disabled=${isDisabled || isReadOnly}
53
+ >
40
54
  ${Array.from({ length: max }, (_, index) => {
41
55
  const ratingValue = index + INDEX_TO_RATING
42
56
  const isActive = ratingValue <= value
43
57
  return html`
44
- <button
45
- type="button"
46
- class="iw-rating-item"
47
- aria-label=${`rate-${ratingValue}`}
48
- ?disabled=${isDisabled || isReadOnly}
49
- @click=${() => onChange?.(ratingValue)}
50
- >
51
- ${isActive ? symbol : emptySymbol}
52
- </button>
58
+ <label class="iw-rating-item">
59
+ <input
60
+ class="iw-rating-input"
61
+ type="radio"
62
+ name=${groupName || nothing}
63
+ value=${ratingValue}
64
+ aria-label=${`${ratingValue} of ${max}`}
65
+ .checked=${ratingValue === value}
66
+ @change=${() => onChange?.(ratingValue)}
67
+ />
68
+ <span aria-hidden="true">${isActive ? symbol : emptySymbol}</span>
69
+ </label>
53
70
  `
54
71
  })}
55
- </div>
72
+ </fieldset>
56
73
  `
57
74
  }
@@ -8,7 +8,11 @@ export default {
8
8
  argTypes: {
9
9
  label: { control: "text", description: "Field label." },
10
10
  name: { control: "text", description: "Native input name." },
11
- value: { control: "number", description: "Current slider value." },
11
+ value: {
12
+ control: "object",
13
+ description:
14
+ "One number, or `[low, high]` for a range with a thumb at each end.",
15
+ },
12
16
  min: { control: "number", description: "Minimum value." },
13
17
  max: { control: "number", description: "Maximum value." },
14
18
  step: { control: "number", description: "Step value." },
@@ -20,6 +24,10 @@ export default {
20
24
  isDisabled: { control: "boolean", description: "Disables interaction." },
21
25
  isValueVisible: { control: "boolean", description: "Shows current value." },
22
26
  isFullWidth: { control: "boolean", description: "Expands width to 100%." },
27
+ hasMarks: {
28
+ control: "boolean",
29
+ description: "Draws a tick at every step.",
30
+ },
23
31
  onChange: { action: "onChange" },
24
32
  },
25
33
  parameters: {
@@ -44,3 +52,22 @@ Default.args = {
44
52
  isValueVisible: true,
45
53
  isFullWidth: false,
46
54
  }
55
+
56
+ export const Range = {}
57
+ Range.args = {
58
+ ...Default.args,
59
+ label: "Price",
60
+ name: "price",
61
+ value: [20, 80],
62
+ }
63
+
64
+ export const Marks = {}
65
+ Marks.args = {
66
+ ...Default.args,
67
+ label: "Rating",
68
+ name: "rating",
69
+ value: 3,
70
+ min: 1,
71
+ max: 5,
72
+ hasMarks: true,
73
+ }
@@ -23,6 +23,108 @@
23
23
  accent-color: var(--iw-slider-accent);
24
24
  }
25
25
 
26
+ /* A tick per step, under the track: their ends sit under the thumb's ends
27
+ of travel, half a thumb in from the track's. */
28
+ .iw-slider-marks {
29
+ display: flex;
30
+ justify-content: space-between;
31
+ padding-inline: calc(var(--iw-slider-thumb, 1rem) / 2);
32
+
33
+ span {
34
+ inline-size: 1px;
35
+ block-size: var(--iw-space-2);
36
+ background: var(--iw-color-border);
37
+ }
38
+ }
39
+
40
+ /* Two thumbs on one track. The inputs lie on top of each other with their
41
+ own tracks hidden, and only their thumbs take presses, so either can be
42
+ dragged; the track below is drawn here, filled between the two. */
43
+ &.iw-slider-range {
44
+ --iw-slider-thumb: 1rem;
45
+
46
+ .iw-slider-track {
47
+ position: relative;
48
+ block-size: var(--iw-slider-thumb);
49
+
50
+ &::before,
51
+ &::after {
52
+ content: "";
53
+ position: absolute;
54
+ inset-block-start: 50%;
55
+ block-size: 0.25rem;
56
+ translate: 0 -50%;
57
+ border-radius: var(--iw-radius-full, 999px);
58
+ }
59
+
60
+ &::before {
61
+ inset-inline: 0;
62
+ background: color-mix(
63
+ in srgb,
64
+ var(--iw-slider-accent) 25%,
65
+ transparent
66
+ );
67
+ }
68
+
69
+ &::after {
70
+ inset-inline-start: var(--iw-slider-low);
71
+ inset-inline-end: calc(100% - var(--iw-slider-high));
72
+ background: var(--iw-slider-accent);
73
+ }
74
+ }
75
+
76
+ .iw-slider-input {
77
+ position: absolute;
78
+ inset: 0;
79
+ z-index: 1;
80
+ margin: 0;
81
+ appearance: none;
82
+ background: transparent;
83
+ pointer-events: none;
84
+
85
+ &::-webkit-slider-runnable-track {
86
+ background: transparent;
87
+ }
88
+
89
+ &::-moz-range-track {
90
+ background: transparent;
91
+ }
92
+
93
+ &::-webkit-slider-thumb {
94
+ appearance: none;
95
+ inline-size: var(--iw-slider-thumb);
96
+ block-size: var(--iw-slider-thumb);
97
+ border: 2px solid var(--iw-color-surface);
98
+ border-radius: 50%;
99
+ background: var(--iw-slider-accent);
100
+ box-shadow: var(--iw-shadow-sm);
101
+ cursor: pointer;
102
+ pointer-events: auto;
103
+ }
104
+
105
+ &::-moz-range-thumb {
106
+ inline-size: var(--iw-slider-thumb);
107
+ block-size: var(--iw-slider-thumb);
108
+ border: 2px solid var(--iw-color-surface);
109
+ border-radius: 50%;
110
+ background: var(--iw-slider-accent);
111
+ box-shadow: var(--iw-shadow-sm);
112
+ cursor: pointer;
113
+ pointer-events: auto;
114
+ }
115
+
116
+ &:focus-visible::-webkit-slider-thumb {
117
+ outline: 2px solid var(--iw-slider-accent);
118
+ outline-offset: 2px;
119
+ }
120
+
121
+ &:focus-visible::-moz-range-thumb {
122
+ outline: 2px solid var(--iw-slider-accent);
123
+ outline-offset: 2px;
124
+ }
125
+ }
126
+ }
127
+
26
128
  &.iw-slider-full-width {
27
129
  width: 100%;
28
130
  }