@inglorious/ui 0.1.0 → 1.0.1

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 (283) hide show
  1. package/README.md +45 -0
  2. package/package.json +190 -15
  3. package/src/all.css +7 -0
  4. package/src/controls/button/button.stories.js +35 -15
  5. package/src/controls/button/index.js +1 -3
  6. package/src/controls/button/style.css +80 -37
  7. package/src/controls/button/template.js +23 -26
  8. package/src/controls/button-group/button-group.stories.js +19 -15
  9. package/src/controls/button-group/index.js +1 -2
  10. package/src/controls/button-group/style.css +0 -7
  11. package/src/controls/button-group/template.js +46 -56
  12. package/src/controls/checkbox/checkbox.stories.js +16 -13
  13. package/src/controls/checkbox/index.js +1 -2
  14. package/src/controls/checkbox/style.css +23 -2
  15. package/src/controls/checkbox/template.js +20 -18
  16. package/src/controls/combobox/combobox.stories.js +92 -0
  17. package/src/controls/{select → combobox}/handlers.js +8 -5
  18. package/src/controls/combobox/helpers.js +91 -0
  19. package/src/controls/combobox/index.js +43 -0
  20. package/src/controls/combobox/style.css +203 -0
  21. package/src/controls/combobox/template.js +340 -0
  22. package/src/controls/controls.css +12 -0
  23. package/src/controls/fab/fab.stories.js +11 -11
  24. package/src/controls/fab/index.js +1 -2
  25. package/src/controls/fab/template.js +21 -19
  26. package/src/controls/icon-button/icon-button.stories.js +8 -10
  27. package/src/controls/icon-button/index.js +1 -2
  28. package/src/controls/icon-button/template.js +13 -19
  29. package/src/controls/input/index.js +1 -2
  30. package/src/controls/input/input.stories.js +24 -23
  31. package/src/controls/input/style.css +9 -7
  32. package/src/controls/input/template.js +28 -31
  33. package/src/controls/radio-group/index.js +1 -2
  34. package/src/controls/radio-group/radio-group.stories.js +11 -8
  35. package/src/controls/radio-group/style.css +22 -1
  36. package/src/controls/radio-group/template.js +20 -15
  37. package/src/controls/rating/index.js +1 -2
  38. package/src/controls/rating/rating.stories.js +7 -10
  39. package/src/controls/rating/style.css +1 -2
  40. package/src/controls/rating/template.js +20 -17
  41. package/src/controls/select/helpers.js +2 -21
  42. package/src/controls/select/index.js +1 -13
  43. package/src/controls/select/select.stories.js +68 -64
  44. package/src/controls/select/style.css +66 -189
  45. package/src/controls/select/template.js +77 -406
  46. package/src/controls/slider/index.js +1 -2
  47. package/src/controls/slider/slider.stories.js +15 -12
  48. package/src/controls/slider/style.css +24 -3
  49. package/src/controls/slider/template.js +30 -23
  50. package/src/controls/switch/index.js +1 -2
  51. package/src/controls/switch/style.css +23 -2
  52. package/src/controls/switch/switch.stories.js +14 -11
  53. package/src/controls/switch/template.js +18 -16
  54. package/src/data-display/avatar/avatar.stories.js +106 -0
  55. package/src/data-display/avatar/index.js +3 -0
  56. package/src/data-display/avatar/style.css +64 -0
  57. package/src/data-display/avatar/template.js +81 -0
  58. package/src/data-display/badge/badge.stories.js +64 -0
  59. package/src/data-display/badge/index.js +3 -0
  60. package/src/data-display/badge/style.css +84 -0
  61. package/src/data-display/badge/template.js +46 -0
  62. package/src/data-display/chip/chip.stories.js +57 -0
  63. package/src/data-display/chip/index.js +3 -0
  64. package/src/data-display/chip/style.css +84 -0
  65. package/src/data-display/chip/template.js +32 -0
  66. package/src/data-display/data-display.css +11 -0
  67. package/src/data-display/data-grid/base.css +58 -0
  68. package/src/data-display/data-grid/data-grid.stories.js +158 -0
  69. package/src/data-display/data-grid/filters/helpers.js +8 -0
  70. package/src/data-display/data-grid/filters/index.js +21 -0
  71. package/src/data-display/data-grid/filters/input.js +24 -0
  72. package/src/data-display/data-grid/filters/range.js +48 -0
  73. package/src/data-display/data-grid/filters/select.js +17 -0
  74. package/src/data-display/data-grid/handlers.js +523 -0
  75. package/src/data-display/data-grid/helpers.js +248 -0
  76. package/src/data-display/data-grid/index.js +64 -0
  77. package/src/data-display/data-grid/style.css +14 -0
  78. package/src/data-display/data-grid/template.js +458 -0
  79. package/src/data-display/data-grid/theme.css +148 -0
  80. package/src/data-display/divider/divider.stories.js +48 -0
  81. package/src/data-display/divider/index.js +3 -0
  82. package/src/data-display/divider/style.css +18 -0
  83. package/src/data-display/divider/template.js +17 -0
  84. package/src/data-display/icon/icon-implementations.mdx +160 -0
  85. package/src/data-display/icon/icon-utf8.stories.js +156 -0
  86. package/src/data-display/icon/icon.stories.js +38 -0
  87. package/src/data-display/icon/index.js +3 -0
  88. package/src/data-display/icon/style.css +17 -0
  89. package/src/data-display/icon/template.js +19 -0
  90. package/src/data-display/list/handlers.js +25 -0
  91. package/src/data-display/list/index.js +4 -0
  92. package/src/data-display/list/list.stories.js +187 -0
  93. package/src/data-display/list/style.css +119 -0
  94. package/src/data-display/list/template.js +302 -0
  95. package/src/data-display/material-icon/index.js +3 -0
  96. package/src/data-display/material-icon/material-icon.stories.js +99 -0
  97. package/src/data-display/material-icon/style.css +27 -0
  98. package/src/data-display/material-icon/template.js +21 -0
  99. package/src/data-display/table/index.js +1 -0
  100. package/src/data-display/table/style.css +42 -0
  101. package/src/data-display/table/table.stories.js +40 -0
  102. package/src/data-display/table/template.js +114 -0
  103. package/src/data-display/tooltip/index.js +3 -0
  104. package/src/data-display/tooltip/style.css +57 -0
  105. package/src/data-display/tooltip/template.js +58 -0
  106. package/src/data-display/tooltip/tooltip.stories.js +44 -0
  107. package/src/data-display/typography/index.js +3 -0
  108. package/src/data-display/typography/style.css +49 -0
  109. package/src/data-display/typography/template.js +110 -0
  110. package/src/data-display/typography/typography.stories.js +48 -0
  111. package/src/data-display/virtual-list/handlers.js +68 -0
  112. package/src/data-display/virtual-list/index.js +4 -0
  113. package/src/data-display/virtual-list/template.js +101 -0
  114. package/src/examples/dashboard/app-drawer.js +255 -0
  115. package/src/examples/dashboard/app-header.js +149 -0
  116. package/src/examples/dashboard/dashboard.js +85 -0
  117. package/src/examples/dashboard/dashboard.stories.js +47 -0
  118. package/src/examples/dashboard/data.js +205 -0
  119. package/src/examples/dashboard/mode.js +27 -0
  120. package/src/examples/dashboard/primitive-content.js +1325 -0
  121. package/src/examples/dashboard/primitive-section.js +158 -0
  122. package/src/examples/dashboard/router.js +13 -0
  123. package/src/examples/dashboard/social-card.js +73 -0
  124. package/src/examples/dashboard/stat-card.js +84 -0
  125. package/src/examples/dashboard/style.css +339 -0
  126. package/src/examples/dashboard/theme-utils.js +95 -0
  127. package/src/examples/dashboard/theme.js +27 -0
  128. package/src/examples/dashboard/traffic-card.js +108 -0
  129. package/src/examples/dashboard/traffic-sales-card.js +267 -0
  130. package/src/feedback/alert/alert.stories.js +70 -0
  131. package/src/feedback/alert/index.js +3 -0
  132. package/src/feedback/alert/style.css +98 -0
  133. package/src/feedback/alert/template.js +74 -0
  134. package/src/feedback/backdrop/backdrop.stories.js +21 -0
  135. package/src/feedback/backdrop/index.js +3 -0
  136. package/src/feedback/backdrop/style.css +25 -0
  137. package/src/feedback/backdrop/template.js +31 -0
  138. package/src/feedback/dialog/dialog.stories.js +27 -0
  139. package/src/feedback/dialog/index.js +3 -0
  140. package/src/feedback/dialog/style.css +68 -0
  141. package/src/feedback/dialog/template.js +64 -0
  142. package/src/feedback/feedback.css +6 -0
  143. package/src/feedback/progress/index.js +3 -0
  144. package/src/feedback/progress/progress.stories.js +54 -0
  145. package/src/feedback/progress/style.css +95 -0
  146. package/src/feedback/progress/template.js +88 -0
  147. package/src/feedback/skeleton/index.js +3 -0
  148. package/src/feedback/skeleton/skeleton.stories.js +44 -0
  149. package/src/feedback/skeleton/style.css +41 -0
  150. package/src/feedback/skeleton/template.js +48 -0
  151. package/src/feedback/snackbar/index.js +3 -0
  152. package/src/feedback/snackbar/snackbar.stories.js +25 -0
  153. package/src/feedback/snackbar/style.css +64 -0
  154. package/src/feedback/snackbar/template.js +55 -0
  155. package/src/index.js +32 -1
  156. package/src/layout/container/container.stories.js +105 -0
  157. package/src/layout/container/index.js +3 -0
  158. package/src/layout/container/style.css +34 -0
  159. package/src/layout/container/template.js +119 -0
  160. package/src/layout/flex/flex.stories.js +28 -10
  161. package/src/layout/flex/index.js +1 -2
  162. package/src/layout/flex/style.css +20 -0
  163. package/src/layout/flex/template.js +40 -13
  164. package/src/layout/grid/grid.stories.js +46 -30
  165. package/src/layout/grid/index.js +1 -2
  166. package/src/layout/grid/style.css +20 -0
  167. package/src/layout/grid/template.js +100 -20
  168. package/src/layout/layout.css +3 -0
  169. package/src/navigation/bottom-navigation/bottom-navigation.stories.js +33 -0
  170. package/src/navigation/bottom-navigation/index.js +1 -0
  171. package/src/navigation/bottom-navigation/style.css +32 -0
  172. package/src/navigation/bottom-navigation/template.js +74 -0
  173. package/src/navigation/breadcrumbs/breadcrumbs.stories.js +33 -0
  174. package/src/navigation/breadcrumbs/index.js +1 -0
  175. package/src/navigation/breadcrumbs/style.css +27 -0
  176. package/src/navigation/breadcrumbs/template.js +79 -0
  177. package/src/navigation/drawer/drawer.stories.js +60 -0
  178. package/src/navigation/drawer/index.js +3 -0
  179. package/src/navigation/drawer/style.css +125 -0
  180. package/src/navigation/drawer/template.js +131 -0
  181. package/src/navigation/link/index.js +3 -0
  182. package/src/navigation/link/link.stories.js +53 -0
  183. package/src/navigation/link/style.css +40 -0
  184. package/src/navigation/link/template.js +53 -0
  185. package/src/navigation/menu/index.js +1 -0
  186. package/src/navigation/menu/menu.stories.js +36 -0
  187. package/src/navigation/menu/style.css +44 -0
  188. package/src/navigation/menu/template.js +81 -0
  189. package/src/navigation/navigation.css +9 -0
  190. package/src/navigation/pagination/index.js +1 -0
  191. package/src/navigation/pagination/pagination.stories.js +61 -0
  192. package/src/navigation/pagination/style.css +29 -0
  193. package/src/navigation/pagination/template.js +140 -0
  194. package/src/navigation/speed-dial/index.js +1 -0
  195. package/src/navigation/speed-dial/speed-dial.stories.js +40 -0
  196. package/src/navigation/speed-dial/style.css +38 -0
  197. package/src/navigation/speed-dial/template.js +68 -0
  198. package/src/navigation/stepper/index.js +1 -0
  199. package/src/navigation/stepper/stepper.stories.js +35 -0
  200. package/src/navigation/stepper/style.css +49 -0
  201. package/src/navigation/stepper/template.js +61 -0
  202. package/src/navigation/tabs/index.js +1 -0
  203. package/src/navigation/tabs/style.css +35 -0
  204. package/src/navigation/tabs/tabs.stories.js +40 -0
  205. package/src/navigation/tabs/template.js +79 -0
  206. package/src/overview.mdx +39 -0
  207. package/src/stories/notifyStory.js +22 -25
  208. package/src/styles/base.css +2 -0
  209. package/src/styles/reset.css +15 -0
  210. package/src/{themes → styles/themes}/bootstrap.css +2 -0
  211. package/src/{themes → styles/themes}/inglorious.css +9 -1
  212. package/src/{themes → styles/themes}/material.css +2 -0
  213. package/src/{tokens/index.css → styles/tokens.css} +9 -0
  214. package/src/surfaces/accordion/accordion.stories.js +60 -0
  215. package/src/surfaces/accordion/index.js +1 -0
  216. package/src/surfaces/accordion/style.css +64 -0
  217. package/src/surfaces/accordion/template.js +85 -0
  218. package/src/surfaces/app-bar/app-bar.stories.js +109 -0
  219. package/src/surfaces/app-bar/index.js +3 -0
  220. package/src/surfaces/app-bar/style.css +109 -0
  221. package/src/surfaces/app-bar/template.js +78 -0
  222. package/src/surfaces/card/card.stories.js +108 -0
  223. package/src/surfaces/card/index.js +1 -0
  224. package/src/surfaces/card/style.css +107 -0
  225. package/src/surfaces/card/template.js +163 -0
  226. package/src/surfaces/paper/index.js +3 -0
  227. package/src/surfaces/paper/paper.stories.js +97 -0
  228. package/src/surfaces/paper/style.css +73 -0
  229. package/src/surfaces/paper/template.js +76 -0
  230. package/src/surfaces/surfaces.css +4 -0
  231. package/types/controls/button-group.d.ts +8 -7
  232. package/types/controls/button.d.ts +10 -7
  233. package/types/controls/checkbox.d.ts +8 -6
  234. package/types/controls/combobox.d.ts +66 -0
  235. package/types/controls/fab.d.ts +6 -5
  236. package/types/controls/icon-button.d.ts +8 -7
  237. package/types/controls/input.d.ts +18 -11
  238. package/types/controls/radio-group.d.ts +7 -5
  239. package/types/controls/rating.d.ts +6 -5
  240. package/types/controls/select.d.ts +11 -34
  241. package/types/controls/slider.d.ts +8 -6
  242. package/types/controls/switch.d.ts +7 -5
  243. package/types/data-display/avatar.d.ts +30 -0
  244. package/types/data-display/badge.d.ts +20 -0
  245. package/types/data-display/chip.d.ts +25 -0
  246. package/types/data-display/data-grid.d.ts +151 -0
  247. package/types/data-display/divider.d.ts +14 -0
  248. package/types/data-display/icon.d.ts +16 -0
  249. package/types/data-display/list.d.ts +66 -0
  250. package/types/data-display/material-icon.d.ts +15 -0
  251. package/types/data-display/table.d.ts +45 -0
  252. package/types/data-display/tooltip.d.ts +20 -0
  253. package/types/data-display/typography.d.ts +28 -0
  254. package/types/data-display/virtual-list.d.ts +94 -0
  255. package/types/feedback/alert.d.ts +22 -0
  256. package/types/feedback/backdrop.d.ts +17 -0
  257. package/types/feedback/dialog.d.ts +21 -0
  258. package/types/feedback/progress.d.ts +18 -0
  259. package/types/feedback/skeleton.d.ts +18 -0
  260. package/types/feedback/snackbar.d.ts +19 -0
  261. package/types/index.d.ts +92 -14
  262. package/types/layout/container.d.ts +28 -0
  263. package/types/layout/flex.d.ts +16 -5
  264. package/types/layout/grid.d.ts +15 -4
  265. package/types/navigation/bottom-navigation.d.ts +33 -0
  266. package/types/navigation/breadcrumbs.d.ts +29 -0
  267. package/types/navigation/drawer.d.ts +23 -0
  268. package/types/navigation/link.d.ts +22 -0
  269. package/types/navigation/menu.d.ts +32 -0
  270. package/types/navigation/pagination.d.ts +50 -0
  271. package/types/navigation/speed-dial.d.ts +35 -0
  272. package/types/navigation/stepper.d.ts +26 -0
  273. package/types/navigation/tabs.d.ts +31 -0
  274. package/types/surfaces/accordion.d.ts +32 -0
  275. package/types/surfaces/app-bar.d.ts +25 -0
  276. package/types/surfaces/card.d.ts +39 -0
  277. package/types/surfaces/paper.d.ts +28 -0
  278. package/src/shared/withRenderValidation.js +0 -19
  279. package/src/widgets/card/card.stories.js +0 -135
  280. package/src/widgets/card/index.js +0 -4
  281. package/src/widgets/card/style.css +0 -53
  282. package/src/widgets/card/template.js +0 -97
  283. package/types/widgets/card.d.ts +0 -21
@@ -1,423 +1,94 @@
1
1
  /**
2
- * @typedef {import('../../../types/controls/select').SelectEntity} SelectEntity
3
- * @typedef {import('../../../types/controls/select').SelectOption} SelectOption
4
- * @typedef {import('@inglorious/web').Api} Api
2
+ * @typedef {import('../../../types/controls/select').SelectProps} SelectProps
5
3
  * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
6
4
  */
7
5
 
8
- import { classMap, html, ref, repeat, when } from "@inglorious/web"
9
- import { applyElementProps } from "@/shared/applyElementProps.js"
6
+ import { classMap, html, ref, repeat } from "@inglorious/web"
10
7
 
11
- import {
12
- filterOptions,
13
- formatOption,
14
- getOptionLabel,
15
- getOptionValue,
16
- isOptionSelected,
17
- } from "./helpers.js"
18
-
19
- const NO_FOCUSED_INDEX = -1
20
-
21
- /**
22
- * Select control render function.
23
- * @param {SelectEntity} entity
24
- * @param {Api} api
25
- * @returns {TemplateResult}
26
- */
27
- export function render(entity, api) {
28
- const type = api.getType?.(entity.type)
29
- const normalized = ensureDefaults(entity)
30
-
31
- return html`<div
32
- class=${classMap({
33
- "iw-select": true,
34
- "iw-select-full-width": !!normalized.fullWidth,
35
- [`iw-select-${normalized.size}`]: normalized.size !== "md",
36
- })}
37
- >
38
- ${normalized.label
39
- ? html`<label class="iw-select-label">${normalized.label}</label>`
40
- : null}
41
- ${type?.renderControl?.(normalized, api) ?? renderControl(normalized, api)}
42
- ${when(
43
- normalized.isOpen,
44
- () =>
45
- type?.renderDropdown?.(normalized, api) ??
46
- renderDropdown(normalized, api),
47
- )}
48
- </div>`
49
- }
8
+ import { applyElementProps } from "../../shared/applyElementProps.js"
9
+ import { formatOption, getOptionValue, isOptionSelected } from "./helpers.js"
50
10
 
51
11
  /**
52
- * Render trigger control.
53
- * @param {SelectEntity} entity
54
- * @param {Api} api
12
+ * Select component for Inglorious Web.
13
+ *
14
+ * @example
15
+ * // Stateless usage
16
+ * select.render({ options: ['true', 'false'], value: 'true' })
17
+ *
18
+ * @param {SelectProps} props
55
19
  * @returns {TemplateResult}
56
20
  */
57
- export function renderControl(entity, api) {
58
- const type = api.getType?.(entity.type)
59
-
60
- return html`<div
61
- class="iw-select-control ${classMap({
62
- "iw-select-control-open": entity.isOpen,
63
- "iw-select-control-disabled": entity.isDisabled,
64
- "iw-select-control-selection":
65
- entity.isMulti &&
66
- Array.isArray(entity.selectedValue) &&
67
- entity.selectedValue.length,
68
- })}"
69
- @click=${() => !entity.isDisabled && api.notify(`#${entity.id}:toggle`)}
70
- ${ref((element) => applyElementProps(element, entity.controlProps ?? {}))}
71
- >
72
- ${when(
73
- entity.isMulti,
74
- () =>
75
- type?.renderMultiValue?.(entity, api) ?? renderMultiValue(entity, api),
76
- () => type?.renderSingleValue?.(entity) ?? renderSingleValue(entity),
77
- )}
78
- ${when(
79
- entity.isClearable &&
80
- ((entity.isMulti &&
81
- Array.isArray(entity.selectedValue) &&
82
- entity.selectedValue.length) ||
83
- (!entity.isMulti && entity.selectedValue !== null)),
84
- () =>
85
- html`<button
86
- class="iw-select-clear"
87
- type="button"
88
- @click=${(event) => {
89
- event.stopPropagation()
90
- api.notify(`#${entity.id}:clear`)
91
- }}
92
- >
93
- ×
94
- </button>`,
95
- )}
96
-
97
- <div class="iw-select-arrow"><span>▼</span></div>
98
- </div>`
99
- }
100
-
101
- /**
102
- * Render single selected value.
103
- * @param {SelectEntity} entity
104
- * @returns {TemplateResult}
105
- */
106
- export function renderSingleValue(entity) {
107
- if (entity.selectedValue === null) {
108
- return html`<span class="iw-select-placeholder"
109
- >${entity.placeholder}</span
110
- >`
21
+ export function render(props) {
22
+ const {
23
+ id,
24
+ type, // eslint-disable-line no-unused-vars
25
+ name = "",
26
+ value = "",
27
+ options = [],
28
+ size = "md",
29
+ isDisabled = false,
30
+ isFullWidth = false,
31
+ isMulti = false,
32
+ onChange,
33
+ onBlur,
34
+ onFocus,
35
+ ...rest
36
+ } = props
37
+
38
+ const inputId = id || name
39
+
40
+ const selectClasses = {
41
+ "iw-select": true,
42
+ "iw-select-full-width": !!isFullWidth,
43
+ [`iw-select-${size}`]: size !== "md",
111
44
  }
112
45
 
113
- const selectedOption = entity.options.find(
114
- (option) => getOptionValue(option) === entity.selectedValue,
46
+ const selectedOption = options.find(
47
+ (option) => getOptionValue(option) === value,
115
48
  )
116
49
 
117
- return html`<span class="iw-select-value"
118
- >${selectedOption
119
- ? getOptionLabel(selectedOption)
120
- : String(entity.selectedValue)}</span
121
- >`
122
- }
123
-
124
- /**
125
- * Render multi selected value tags.
126
- * @param {SelectEntity} entity
127
- * @param {Api} api
128
- * @returns {TemplateResult}
129
- */
130
- export function renderMultiValue(entity, api) {
131
- const type = api.getType?.(entity.type)
132
- if (!Array.isArray(entity.selectedValue) || !entity.selectedValue.length) {
133
- return html`<span class="iw-select-placeholder"
134
- >${entity.placeholder}</span
135
- >`
136
- }
137
-
138
- return html`<div class="iw-select-multi-value">
139
- ${repeat(
140
- entity.selectedValue,
141
- (value) => value,
142
- (value) =>
143
- type?.renderMultiValueTag?.(entity, value, api) ??
144
- renderMultiValueTag(entity, value, api),
145
- )}
146
- </div>`
147
- }
148
-
149
- /**
150
- * Render a selected multi-value tag.
151
- * @param {SelectEntity} entity
152
- * @param {string|number} value
153
- * @param {Api} api
154
- * @returns {TemplateResult}
155
- */
156
- export function renderMultiValueTag(entity, value, api) {
157
- const option = entity.options.find((opt) => getOptionValue(opt) === value)
158
-
159
- return html`<div
160
- class="iw-select-multi-value-tag"
161
- @click=${(event) => {
162
- event.stopPropagation()
163
- api.notify(`#${entity.id}:optionSelect`, option || { value })
164
- }}
165
- >
166
- <span class="iw-select-multi-value-tag-label"
167
- >${option ? getOptionLabel(option) : String(value)}</span
50
+ return html`
51
+ <select
52
+ id=${inputId}
53
+ name=${name}
54
+ ?multiple=${isMulti}
55
+ ?disabled=${isDisabled}
56
+ class=${classMap(selectClasses)}
57
+ @change=${(event) => onChange?.(event.target.value)}
58
+ @blur=${onBlur}
59
+ @focus=${onFocus}
60
+ ${ref((element) => applyElementProps(element, rest))}
168
61
  >
169
- <span class="iw-select-multi-value-remove">×</span>
170
- </div>`
171
- }
172
-
173
- /**
174
- * Render dropdown.
175
- * @param {SelectEntity} entity
176
- * @param {Api} api
177
- * @returns {TemplateResult}
178
- */
179
- export function renderDropdown(entity, api) {
180
- const type = api.getType?.(entity.type)
181
- const filteredOptions = filterOptions(entity.options, entity.searchTerm)
182
-
183
- return html`<div
184
- class="iw-select-dropdown"
185
- ${ref((el) => {
186
- if (el) {
187
- setTimeout(() => {
188
- document.addEventListener(
189
- "click",
190
- (event) => {
191
- if (!el.contains(event.target)) {
192
- api.notify(`#${entity.id}:close`)
193
- }
194
- },
195
- { once: true },
62
+ ${repeat(
63
+ options,
64
+ (option) => getOptionValue(option),
65
+ (option) => {
66
+ const {
67
+ value,
68
+ label,
69
+ isDisabled: isOptionDisabled,
70
+ } = formatOption(option)
71
+ const isSelected = isOptionSelected(
72
+ option,
73
+ selectedOption?.value,
74
+ isMulti,
196
75
  )
197
- })
198
- }
199
- })}
200
- >
201
- ${when(
202
- entity.isSearchable,
203
- () =>
204
- type?.renderSearchInput?.(entity, api) ??
205
- renderSearchInput(entity, api),
206
- )}
207
- ${when(
208
- entity.isLoading,
209
- () => type?.renderLoading?.(entity) ?? renderLoading(entity),
210
- )}
211
- ${when(
212
- !entity.isLoading && !filteredOptions.length,
213
- () => type?.renderNoOptions?.(entity) ?? renderNoOptions(entity),
214
- )}
215
- ${when(
216
- !entity.isLoading && filteredOptions.length,
217
- () => type?.renderOptions?.(entity, api) ?? renderOptions(entity, api),
218
- )}
219
- </div>`
220
- }
221
-
222
- /**
223
- * Render search input.
224
- * @param {SelectEntity} entity
225
- * @param {Api} api
226
- * @returns {TemplateResult}
227
- */
228
- export function renderSearchInput(entity, api) {
229
- return html`<input
230
- class="iw-select-dropdown-search"
231
- type="text"
232
- placeholder="Search..."
233
- .value=${entity.searchTerm ?? ""}
234
- @input=${(event) =>
235
- api.notify(`#${entity.id}:searchChange`, event.target.value)}
236
- @keydown=${(event) => handleKeyDown(entity, event, api)}
237
- ${ref((el) => {
238
- if (el && entity.isOpen) queueMicrotask(() => el.focus())
239
- })}
240
- />`
241
- }
242
-
243
- /**
244
- * Render options list.
245
- * @param {SelectEntity} entity
246
- * @param {Api} api
247
- * @returns {TemplateResult}
248
- */
249
- export function renderOptions(entity, api) {
250
- const filteredOptions = filterOptions(entity.options, entity.searchTerm)
251
- const type = api.getType?.(entity.type)
252
76
 
253
- return html`<div class="iw-select-dropdown-options">
254
- ${repeat(
255
- filteredOptions,
256
- (option) => getOptionValue(option),
257
- (option, index) =>
258
- type?.renderOption?.(entity, { option, index }, api) ??
259
- renderOption(entity, { option, index }, api),
260
- )}
261
- </div>`
77
+ const optionClasses = {
78
+ "iw-select-option": true,
79
+ "iw-select-option-selected": isSelected,
80
+ }
81
+
82
+ return html`<option
83
+ value=${value}
84
+ .selected=${value === selectedOption?.value}
85
+ ?disabled=${isOptionDisabled}
86
+ class=${classMap(optionClasses)}
87
+ >
88
+ ${label}
89
+ </option>`
90
+ },
91
+ )}
92
+ </select>
93
+ `
262
94
  }
263
-
264
- /**
265
- * Render an option row.
266
- * @param {SelectEntity} entity
267
- * @param {{ option: SelectOption, index: number }} payload
268
- * @param {Api} api
269
- * @returns {TemplateResult}
270
- */
271
- export function renderOption(entity, { option, index }, api) {
272
- const normalized = formatOption(option)
273
- const isSelected = isOptionSelected(
274
- option,
275
- entity.selectedValue,
276
- entity.isMulti,
277
- )
278
- const isFocused = index === entity.focusedIndex
279
-
280
- return html`<div
281
- class="iw-select-dropdown-options-option ${classMap({
282
- "iw-select-dropdown-options-option-selected": isSelected,
283
- "iw-select-dropdown-options-option-focused": isFocused,
284
- "iw-select-dropdown-options-option-disabled": !!normalized.disabled,
285
- })}"
286
- @click=${() =>
287
- !normalized.disabled &&
288
- api.notify(`#${entity.id}:optionSelect`, normalized)}
289
- @mouseenter=${() => api.notify(`#${entity.id}:focusSet`, index)}
290
- >
291
- ${when(
292
- entity.isMulti,
293
- () =>
294
- html`<input
295
- type="checkbox"
296
- .checked=${isSelected}
297
- ?disabled=${!!normalized.disabled}
298
- />`,
299
- )}
300
- <span>${getOptionLabel(normalized)}</span>
301
- </div>`
302
- }
303
-
304
- /**
305
- * Render loading state.
306
- * @param {SelectEntity} entity
307
- * @returns {TemplateResult}
308
- */
309
- export function renderLoading(entity) {
310
- return html`<div class="iw-select-loading">${entity.loadingMessage}</div>`
311
- }
312
-
313
- /**
314
- * Render no options state.
315
- * @param {SelectEntity} entity
316
- * @returns {TemplateResult}
317
- */
318
- export function renderNoOptions(entity) {
319
- return html`<div class="iw-select-no-options">
320
- ${entity.noOptionsMessage}
321
- </div>`
322
- }
323
-
324
- /**
325
- * Handle key navigation.
326
- * @param {SelectEntity} entity
327
- * @param {KeyboardEvent} event
328
- * @param {Api} api
329
- */
330
- function handleKeyDown(entity, event, api) {
331
- const filteredOptions = filterOptions(entity.options, entity.searchTerm)
332
-
333
- switch (event.key) {
334
- case "ArrowDown":
335
- event.preventDefault()
336
- api.notify(`#${entity.id}:focusNext`)
337
- break
338
- case "ArrowUp":
339
- event.preventDefault()
340
- api.notify(`#${entity.id}:focusPrev`)
341
- break
342
- case "Enter":
343
- event.preventDefault()
344
- if (
345
- entity.focusedIndex > NO_FOCUSED_INDEX &&
346
- filteredOptions[entity.focusedIndex]
347
- ) {
348
- const option = formatOption(filteredOptions[entity.focusedIndex])
349
- if (!option.disabled) {
350
- api.notify(`#${entity.id}:optionSelect`, option)
351
- }
352
- }
353
- break
354
- case "Escape":
355
- event.preventDefault()
356
- api.notify(`#${entity.id}:close`)
357
- break
358
- case "Home":
359
- event.preventDefault()
360
- api.notify(`#${entity.id}:focusFirst`)
361
- break
362
- case "End":
363
- event.preventDefault()
364
- api.notify(`#${entity.id}:focusLast`)
365
- break
366
- default:
367
- break
368
- }
369
- }
370
-
371
- /**
372
- * @param {SelectEntity} entity
373
- * @returns {SelectEntity}
374
- */
375
- function ensureDefaults(entity) {
376
- const isMulti = entity.isMulti ?? false
377
- const controlProps = Object.fromEntries(
378
- Object.entries(entity).filter(([key]) => !KNOWN_SELECT_KEYS.has(key)),
379
- )
380
-
381
- return {
382
- ...entity,
383
- options: (entity.options ?? []).map(formatOption),
384
- isOpen: entity.isOpen ?? false,
385
- searchTerm: entity.searchTerm ?? "",
386
- focusedIndex: entity.focusedIndex ?? NO_FOCUSED_INDEX,
387
- isMulti,
388
- selectedValue: entity.selectedValue ?? (isMulti ? [] : null),
389
- isLoading: entity.isLoading ?? false,
390
- isDisabled: entity.isDisabled ?? false,
391
- isSearchable: entity.isSearchable ?? true,
392
- isClearable: entity.isClearable ?? true,
393
- placeholder: entity.placeholder ?? "Select...",
394
- noOptionsMessage: entity.noOptionsMessage ?? "No options",
395
- loadingMessage: entity.loadingMessage ?? "Loading...",
396
- size: entity.size ?? "md",
397
- fullWidth: entity.fullWidth ?? false,
398
- label: entity.label ?? "",
399
- controlProps,
400
- }
401
- }
402
-
403
- const KNOWN_SELECT_KEYS = new Set([
404
- "id",
405
- "type",
406
- "label",
407
- "options",
408
- "isOpen",
409
- "searchTerm",
410
- "focusedIndex",
411
- "isMulti",
412
- "selectedValue",
413
- "isLoading",
414
- "isDisabled",
415
- "isSearchable",
416
- "isClearable",
417
- "placeholder",
418
- "noOptionsMessage",
419
- "loadingMessage",
420
- "fullWidth",
421
- "size",
422
- "controlProps",
423
- ])
@@ -1,4 +1,3 @@
1
- import { withRenderValidation } from "../../shared/withRenderValidation.js"
2
1
  import * as renderers from "./template.js"
3
2
 
4
- export const slider = [{ ...renderers }, withRenderValidation]
3
+ export const slider = { ...renderers }
@@ -1,24 +1,26 @@
1
- import {
2
- makeStoryRender,
3
- notifyActionArgType,
4
- } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notifyStory.js"
5
2
  import { slider } from "."
6
3
 
7
4
  export default {
8
5
  title: "Controls/Slider",
9
6
  tags: ["autodocs"],
10
- render: makeStoryRender(slider, "story-slider"),
7
+ render: createRender(slider),
11
8
  argTypes: {
12
- ...notifyActionArgType,
13
9
  label: { control: "text", description: "Field label." },
14
10
  name: { control: "text", description: "Native input name." },
15
11
  value: { control: "number", description: "Current slider value." },
16
12
  min: { control: "number", description: "Minimum value." },
17
13
  max: { control: "number", description: "Maximum value." },
18
14
  step: { control: "number", description: "Step value." },
19
- disabled: { control: "boolean", description: "Disables interaction." },
20
- showValue: { control: "boolean", description: "Shows current value." },
21
- fullWidth: { control: "boolean", description: "Expands width to 100%." },
15
+ color: {
16
+ control: "select",
17
+ options: ["primary", "secondary", "success", "warning", "error", "info"],
18
+ description: "Semantic accent color.",
19
+ },
20
+ isDisabled: { control: "boolean", description: "Disables interaction." },
21
+ isValueVisible: { control: "boolean", description: "Shows current value." },
22
+ isFullWidth: { control: "boolean", description: "Expands width to 100%." },
23
+ onChange: { action: "onChange" },
22
24
  },
23
25
  parameters: {
24
26
  docs: {
@@ -37,7 +39,8 @@ Default.args = {
37
39
  min: 0,
38
40
  max: 100,
39
41
  step: 1,
40
- disabled: false,
41
- showValue: true,
42
- fullWidth: false,
42
+ color: "primary",
43
+ isDisabled: false,
44
+ isValueVisible: true,
45
+ isFullWidth: false,
43
46
  }
@@ -1,4 +1,5 @@
1
- .iw-slider-field {
1
+ .iw-slider {
2
+ --iw-slider-accent: var(--iw-color-primary);
2
3
  display: inline-flex;
3
4
  flex-direction: column;
4
5
  gap: var(--iw-space-2);
@@ -17,12 +18,32 @@
17
18
  color: var(--iw-color-text-secondary);
18
19
  }
19
20
 
20
- .iw-slider {
21
+ .iw-slider-input {
21
22
  width: 100%;
22
- accent-color: var(--iw-color-primary);
23
+ accent-color: var(--iw-slider-accent);
23
24
  }
24
25
 
25
26
  &.iw-slider-full-width {
26
27
  width: 100%;
27
28
  }
29
+
30
+ &.iw-slider-secondary {
31
+ --iw-slider-accent: var(--iw-color-gray-600);
32
+ }
33
+
34
+ &.iw-slider-success {
35
+ --iw-slider-accent: var(--iw-color-success);
36
+ }
37
+
38
+ &.iw-slider-warning {
39
+ --iw-slider-accent: var(--iw-color-warning);
40
+ }
41
+
42
+ &.iw-slider-error {
43
+ --iw-slider-accent: var(--iw-color-error);
44
+ }
45
+
46
+ &.iw-slider-info {
47
+ --iw-slider-accent: var(--iw-color-info);
48
+ }
28
49
  }
@@ -1,64 +1,71 @@
1
1
  /**
2
- * @typedef {import('../../../types/controls/slider').SliderEntity} SliderEntity
3
- * @typedef {import('@inglorious/web').Api} Api
2
+ * @typedef {import('../../../types/controls/slider').SliderProps} SliderProps
4
3
  * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
4
  */
6
5
 
7
6
  import { classMap, html, ref } from "@inglorious/web"
8
7
 
9
- import { applyElementProps } from "@/shared/applyElementProps.js"
8
+ import { applyElementProps } from "../../shared/applyElementProps.js"
9
+
10
+ const DEFAULT_VALUE = 0
11
+ const DEFAULT_MIN = 0
12
+ const DEFAULT_MAX = 100
13
+ const DEFAULT_STEP = 1
10
14
 
11
15
  /**
12
16
  * Slider control based on native range input.
13
17
  *
14
- * @param {SliderEntity} entity
15
- * @param {Api} api
18
+ * @param {SliderProps} props
16
19
  * @returns {TemplateResult}
17
20
  */
18
- export function render(entity, api) {
21
+ export function render(props) {
19
22
  const {
23
+ id,
24
+ type, // eslint-disable-line no-unused-vars
20
25
  name = "",
21
26
  label,
22
- value = 0,
23
- min = 0,
24
- max = 100,
25
- step = 1,
26
- disabled = false,
27
- showValue = true,
28
- fullWidth = false,
27
+ value = DEFAULT_VALUE,
28
+ min = DEFAULT_MIN,
29
+ max = DEFAULT_MAX,
30
+ step = DEFAULT_STEP,
31
+ isDisabled = false,
32
+ color = "primary",
33
+ isValueVisible = true,
34
+ isFullWidth = false,
35
+ onChange,
29
36
  ...rest
30
- } = entity
37
+ } = props
31
38
 
32
- const inputId = entity.id || name
39
+ const inputId = id || name
33
40
 
34
41
  const classes = {
35
- "iw-slider-field": true,
36
- "iw-slider-full-width": fullWidth,
42
+ "iw-slider": true,
43
+ [`iw-slider-${color}`]: color !== "primary",
44
+ "iw-slider-full-width": isFullWidth,
37
45
  }
38
46
 
39
47
  return html`
40
48
  <div class=${classMap(classes)}>
41
- ${(label || showValue) &&
49
+ ${(label || isValueVisible) &&
42
50
  html`<div class="iw-slider-header">
43
51
  ${label
44
52
  ? html`<label for=${inputId}>${label}</label>`
45
53
  : html`<span></span>`}
46
- ${showValue
54
+ ${isValueVisible
47
55
  ? html`<output class="iw-slider-value">${value}</output>`
48
56
  : null}
49
57
  </div>`}
50
58
  <input
51
59
  id=${inputId}
52
- class="iw-slider"
60
+ class="iw-slider-input"
53
61
  type="range"
54
62
  name=${name}
55
63
  min=${min}
56
64
  max=${max}
57
65
  step=${step}
58
66
  .value=${String(value)}
59
- ?disabled=${disabled}
60
- @input=${(event) =>
61
- api.notify(`#${entity.id}:change`, Number(event.target.value))}
67
+ ?disabled=${isDisabled}
68
+ @input=${(event) => onChange?.(Number(event.target.value))}
62
69
  ${ref((element) => applyElementProps(element, rest))}
63
70
  />
64
71
  </div>