@mich8060/chg-design-system 0.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 (260) hide show
  1. package/.github/workflows/figma-sync.yml +30 -0
  2. package/ARCHITECTURE_FIX.md +241 -0
  3. package/LICENSE +21 -0
  4. package/README.lib.md +103 -0
  5. package/README.md +177 -0
  6. package/figma.config.json +9 -0
  7. package/package.json +67 -0
  8. package/package.lib.json +49 -0
  9. package/public/data/figma-variables.json +40026 -0
  10. package/public/favicon.ico +0 -0
  11. package/public/index.html +46 -0
  12. package/public/logo192.png +0 -0
  13. package/public/logo512.png +0 -0
  14. package/public/manifest.json +25 -0
  15. package/public/robots.txt +3 -0
  16. package/public/styles/tokens.css +1994 -0
  17. package/scripts/index.js +896 -0
  18. package/scripts/publish-lib.js +150 -0
  19. package/scripts/validate.js +94 -0
  20. package/src/App.css +457 -0
  21. package/src/App.css.map +1 -0
  22. package/src/App.js +161 -0
  23. package/src/App.scss +548 -0
  24. package/src/App.test.js +8 -0
  25. package/src/assets/images/.gitkeep +0 -0
  26. package/src/assets/images/doctors/Avatar-1.png +0 -0
  27. package/src/assets/images/doctors/Avatar-10.png +0 -0
  28. package/src/assets/images/doctors/Avatar-11.png +0 -0
  29. package/src/assets/images/doctors/Avatar-12.png +0 -0
  30. package/src/assets/images/doctors/Avatar-13.png +0 -0
  31. package/src/assets/images/doctors/Avatar-14.png +0 -0
  32. package/src/assets/images/doctors/Avatar-15.png +0 -0
  33. package/src/assets/images/doctors/Avatar-16.png +0 -0
  34. package/src/assets/images/doctors/Avatar-17.png +0 -0
  35. package/src/assets/images/doctors/Avatar-18.png +0 -0
  36. package/src/assets/images/doctors/Avatar-19.png +0 -0
  37. package/src/assets/images/doctors/Avatar-2.png +0 -0
  38. package/src/assets/images/doctors/Avatar-20.png +0 -0
  39. package/src/assets/images/doctors/Avatar-21.png +0 -0
  40. package/src/assets/images/doctors/Avatar-3.png +0 -0
  41. package/src/assets/images/doctors/Avatar-4.png +0 -0
  42. package/src/assets/images/doctors/Avatar-5.png +0 -0
  43. package/src/assets/images/doctors/Avatar-6.png +0 -0
  44. package/src/assets/images/doctors/Avatar-7.png +0 -0
  45. package/src/assets/images/doctors/Avatar-8.png +0 -0
  46. package/src/assets/images/doctors/Avatar-9.png +0 -0
  47. package/src/assets/images/doctors/Avatar.png +0 -0
  48. package/src/assets/images/doctors/index.js +141 -0
  49. package/src/data/figma-variables.json +90305 -0
  50. package/src/index.js +20 -0
  51. package/src/index.scss +10 -0
  52. package/src/pages/AccordionDemo.jsx +206 -0
  53. package/src/pages/AccordionDemo.scss +34 -0
  54. package/src/pages/ActionMenuDemo.jsx +957 -0
  55. package/src/pages/ActionMenuDemo.scss +34 -0
  56. package/src/pages/AvatarDemo.jsx +328 -0
  57. package/src/pages/AvatarDemo.scss +40 -0
  58. package/src/pages/BadgeDemo.jsx +254 -0
  59. package/src/pages/BadgeDemo.scss +40 -0
  60. package/src/pages/BorderRadiusDemo.jsx +112 -0
  61. package/src/pages/BorderRadiusDemo.scss +50 -0
  62. package/src/pages/BrandingDemo.jsx +117 -0
  63. package/src/pages/BreadcrumbDemo.jsx +172 -0
  64. package/src/pages/ButtonDemo.jsx +708 -0
  65. package/src/pages/ButtonDemo.scss +34 -0
  66. package/src/pages/CheckboxDemo.jsx +194 -0
  67. package/src/pages/ChipDemo.jsx +359 -0
  68. package/src/pages/ChipDemo.scss +40 -0
  69. package/src/pages/ColorsDemo.jsx +566 -0
  70. package/src/pages/ColorsDemo.scss +243 -0
  71. package/src/pages/ComponentsUsage.jsx +401 -0
  72. package/src/pages/DatepickerDemo.jsx +223 -0
  73. package/src/pages/DividerDemo.jsx +337 -0
  74. package/src/pages/DotStatusDemo.jsx +223 -0
  75. package/src/pages/DropdownDemo.jsx +229 -0
  76. package/src/pages/FieldDemo.jsx +253 -0
  77. package/src/pages/FigmaVariablesDemo.jsx +426 -0
  78. package/src/pages/FigmaVariablesDemo.scss +316 -0
  79. package/src/pages/FileUploadDemo.jsx +186 -0
  80. package/src/pages/FlexDemo.jsx +144 -0
  81. package/src/pages/FlexDemo.scss +119 -0
  82. package/src/pages/FontInstallation.jsx +252 -0
  83. package/src/pages/FontInstallation.scss +40 -0
  84. package/src/pages/Home.jsx +3156 -0
  85. package/src/pages/IconDemo.jsx +1680 -0
  86. package/src/pages/ImageAspectDemo.jsx +152 -0
  87. package/src/pages/InputDemo.jsx +245 -0
  88. package/src/pages/Installation.jsx +257 -0
  89. package/src/pages/Installation.scss +40 -0
  90. package/src/pages/KeyDemo.jsx +184 -0
  91. package/src/pages/MenuDemo.jsx +139 -0
  92. package/src/pages/MicroCalendarDemo.jsx +165 -0
  93. package/src/pages/PaginationDemo.jsx +176 -0
  94. package/src/pages/PillToggleDemo.jsx +212 -0
  95. package/src/pages/ProgressCircleDemo.jsx +206 -0
  96. package/src/pages/ProgressIndicatorDemo.jsx +227 -0
  97. package/src/pages/RadioDemo.jsx +282 -0
  98. package/src/pages/ShadowsDemo.jsx +118 -0
  99. package/src/pages/ShadowsDemo.scss +93 -0
  100. package/src/pages/SliderDemo.jsx +226 -0
  101. package/src/pages/SpacingDemo.jsx +160 -0
  102. package/src/pages/SpacingDemo.scss +107 -0
  103. package/src/pages/StatusDemo.jsx +196 -0
  104. package/src/pages/StepsDemo.jsx +308 -0
  105. package/src/pages/TableDemo.jsx +376 -0
  106. package/src/pages/TabsDemo.jsx +221 -0
  107. package/src/pages/ToastDemo.jsx +195 -0
  108. package/src/pages/ToggleDemo.jsx +187 -0
  109. package/src/pages/TokensDemo.jsx +637 -0
  110. package/src/pages/TokensDemo.scss +270 -0
  111. package/src/pages/TokensUsage.jsx +220 -0
  112. package/src/pages/TooltipDemo.jsx +170 -0
  113. package/src/pages/TypographyDemo.jsx +229 -0
  114. package/src/pages/TypographyDemo.scss +105 -0
  115. package/src/pages/UtilitiesDemo.jsx +381 -0
  116. package/src/pages/UtilitiesDemo.scss +214 -0
  117. package/src/reportWebVitals.js +13 -0
  118. package/src/setupTests.js +5 -0
  119. package/src/styles/_typography.scss +932 -0
  120. package/src/styles/_utilities.scss +3635 -0
  121. package/src/styles/_variables.scss +887 -0
  122. package/src/styles/prism-custom.css +206 -0
  123. package/src/styles/prism-custom.css.map +1 -0
  124. package/src/styles/prism-custom.scss +205 -0
  125. package/src/styles/tokens.css +4416 -0
  126. package/src/styles/tokens.css.map +1 -0
  127. package/src/styles/tokens.scss +1456 -0
  128. package/src/ui/Accordion/Accordion.jsx +70 -0
  129. package/src/ui/Accordion/Accordion.scss +82 -0
  130. package/src/ui/Accordion/index.js +1 -0
  131. package/src/ui/ActionMenu/ActionMenu.jsx +383 -0
  132. package/src/ui/ActionMenu/ActionMenu.scss +198 -0
  133. package/src/ui/ActionMenu/index.js +1 -0
  134. package/src/ui/Avatar/Avatar.jsx +49 -0
  135. package/src/ui/Avatar/Avatar.scss +82 -0
  136. package/src/ui/Avatar/index.js +1 -0
  137. package/src/ui/Badge/Badge.jsx +64 -0
  138. package/src/ui/Badge/Badge.scss +84 -0
  139. package/src/ui/Badge/index.js +1 -0
  140. package/src/ui/Branding/Branding.jsx +65 -0
  141. package/src/ui/Branding/Branding.scss +116 -0
  142. package/src/ui/Branding/index.js +1 -0
  143. package/src/ui/Breadcrumb/Breadcrumb.jsx +162 -0
  144. package/src/ui/Breadcrumb/Breadcrumb.scss +46 -0
  145. package/src/ui/Breadcrumb/index.js +2 -0
  146. package/src/ui/Button/Button.figma.tsx +49 -0
  147. package/src/ui/Button/Button.jsx +135 -0
  148. package/src/ui/Button/Button.scss +188 -0
  149. package/src/ui/Button/index.js +1 -0
  150. package/src/ui/Card/Card.jsx +25 -0
  151. package/src/ui/Card/Card.scss +47 -0
  152. package/src/ui/Card/index.js +1 -0
  153. package/src/ui/Checkbox/Checkbox.jsx +70 -0
  154. package/src/ui/Checkbox/Checkbox.scss +96 -0
  155. package/src/ui/Checkbox/index.js +1 -0
  156. package/src/ui/Chip/Chip.jsx +104 -0
  157. package/src/ui/Chip/Chip.scss +118 -0
  158. package/src/ui/Chip/index.js +1 -0
  159. package/src/ui/CopyButton/CopyButton.jsx +102 -0
  160. package/src/ui/CopyButton/CopyButton.scss +56 -0
  161. package/src/ui/CopyButton/index.js +1 -0
  162. package/src/ui/Datepicker/Datepicker.jsx +326 -0
  163. package/src/ui/Datepicker/Datepicker.scss +187 -0
  164. package/src/ui/Datepicker/index.js +2 -0
  165. package/src/ui/Divider/Divider.jsx +89 -0
  166. package/src/ui/Divider/Divider.scss +112 -0
  167. package/src/ui/Divider/index.js +1 -0
  168. package/src/ui/DotStatus/DotStatus.jsx +64 -0
  169. package/src/ui/DotStatus/DotStatus.scss +87 -0
  170. package/src/ui/DotStatus/index.js +1 -0
  171. package/src/ui/Dropdown/Dropdown.jsx +200 -0
  172. package/src/ui/Dropdown/Dropdown.scss +156 -0
  173. package/src/ui/Dropdown/index.js +1 -0
  174. package/src/ui/Field/Field.jsx +89 -0
  175. package/src/ui/Field/Field.scss +119 -0
  176. package/src/ui/Field/index.js +1 -0
  177. package/src/ui/FileUpload/FileUpload.figma.tsx +28 -0
  178. package/src/ui/FileUpload/FileUpload.jsx +153 -0
  179. package/src/ui/FileUpload/FileUpload.scss +78 -0
  180. package/src/ui/FileUpload/index.js +2 -0
  181. package/src/ui/Flex/Flex.jsx +42 -0
  182. package/src/ui/Flex/Flex.scss +119 -0
  183. package/src/ui/Flex/index.js +1 -0
  184. package/src/ui/Icon/Icon.figma.tsx +22 -0
  185. package/src/ui/Icon/Icon.jsx +47 -0
  186. package/src/ui/Icon/index.js +1 -0
  187. package/src/ui/ImageAspect/ImageAspect.jsx +56 -0
  188. package/src/ui/ImageAspect/ImageAspect.scss +62 -0
  189. package/src/ui/ImageAspect/index.js +1 -0
  190. package/src/ui/Input/Input.figma.tsx +35 -0
  191. package/src/ui/Input/Input.jsx +68 -0
  192. package/src/ui/Input/Input.scss +64 -0
  193. package/src/ui/Input/index.js +2 -0
  194. package/src/ui/Key/Key.jsx +37 -0
  195. package/src/ui/Key/Key.scss +34 -0
  196. package/src/ui/Key/index.js +1 -0
  197. package/src/ui/Menu/Menu.jsx +389 -0
  198. package/src/ui/Menu/Menu.scss +382 -0
  199. package/src/ui/Menu/index.js +1 -0
  200. package/src/ui/MicroCalendar/MicroCalendar.jsx +392 -0
  201. package/src/ui/MicroCalendar/MicroCalendar.scss +277 -0
  202. package/src/ui/MicroCalendar/index.js +1 -0
  203. package/src/ui/Pagination/Pagination.jsx +237 -0
  204. package/src/ui/Pagination/Pagination.scss +182 -0
  205. package/src/ui/Pagination/index.js +1 -0
  206. package/src/ui/PillToggle/PillToggle.jsx +56 -0
  207. package/src/ui/PillToggle/PillToggle.scss +84 -0
  208. package/src/ui/PillToggle/index.js +1 -0
  209. package/src/ui/Playground/Playground.jsx +524 -0
  210. package/src/ui/Playground/Playground.scss +310 -0
  211. package/src/ui/Playground/index.js +2 -0
  212. package/src/ui/ProgressCircle/ProgressCircle.jsx +147 -0
  213. package/src/ui/ProgressCircle/ProgressCircle.scss +143 -0
  214. package/src/ui/ProgressCircle/index.js +1 -0
  215. package/src/ui/ProgressIndicator/ProgressIndicator.jsx +92 -0
  216. package/src/ui/ProgressIndicator/ProgressIndicator.scss +133 -0
  217. package/src/ui/ProgressIndicator/index.js +1 -0
  218. package/src/ui/Radio/Radio.jsx +57 -0
  219. package/src/ui/Radio/Radio.scss +84 -0
  220. package/src/ui/Radio/index.js +1 -0
  221. package/src/ui/Slider/Slider.jsx +283 -0
  222. package/src/ui/Slider/Slider.scss +156 -0
  223. package/src/ui/Slider/index.js +1 -0
  224. package/src/ui/Status/Status.jsx +66 -0
  225. package/src/ui/Status/Status.scss +90 -0
  226. package/src/ui/Status/index.js +1 -0
  227. package/src/ui/Steps/Steps.jsx +201 -0
  228. package/src/ui/Steps/Steps.scss +240 -0
  229. package/src/ui/Steps/index.js +1 -0
  230. package/src/ui/Table/Table.jsx +143 -0
  231. package/src/ui/Table/Table.scss +90 -0
  232. package/src/ui/Table/index.js +1 -0
  233. package/src/ui/Tabs/TabItem.jsx +86 -0
  234. package/src/ui/Tabs/Tabs.figma.tsx +30 -0
  235. package/src/ui/Tabs/Tabs.jsx +318 -0
  236. package/src/ui/Tabs/Tabs.scss +164 -0
  237. package/src/ui/Tabs/Untitled +1 -0
  238. package/src/ui/Tabs/index.js +3 -0
  239. package/src/ui/Tag/Tag.figma.tsx +29 -0
  240. package/src/ui/Tag/Tag.jsx +93 -0
  241. package/src/ui/Tag/Tag.scss +229 -0
  242. package/src/ui/Tag/index.js +2 -0
  243. package/src/ui/Textarea/Textarea.figma.tsx +35 -0
  244. package/src/ui/Textarea/Textarea.jsx +68 -0
  245. package/src/ui/Textarea/Textarea.scss +69 -0
  246. package/src/ui/Textarea/index.js +2 -0
  247. package/src/ui/Toast/Toast.jsx +75 -0
  248. package/src/ui/Toast/Toast.scss +132 -0
  249. package/src/ui/Toast/index.js +2 -0
  250. package/src/ui/Toggle/Toggle.jsx +73 -0
  251. package/src/ui/Toggle/Toggle.scss +139 -0
  252. package/src/ui/Toggle/index.js +1 -0
  253. package/src/ui/Tooltip/Tooltip.figma.tsx +24 -0
  254. package/src/ui/Tooltip/Tooltip.jsx +123 -0
  255. package/src/ui/Tooltip/Tooltip.scss +80 -0
  256. package/src/ui/Tooltip/index.js +2 -0
  257. package/src/ui/index.js +63 -0
  258. package/src/utils/formatDate.js +27 -0
  259. package/src/utils/headerVariants.js +69 -0
  260. package/vite.config.lib.js +55 -0
@@ -0,0 +1,133 @@
1
+ @use "../../styles/typography" as *;
2
+
3
+ .uds-progress-indicator {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--uds-gap-8);
7
+ width: 100%;
8
+
9
+ &__header {
10
+ display: flex;
11
+ justify-content: space-between;
12
+ align-items: center;
13
+ gap: var(--uds-gap-8);
14
+ }
15
+
16
+ &__label {
17
+ @include uds-body-14-medium;
18
+ color: var(--uds-text-primary);
19
+ }
20
+
21
+ &__value {
22
+ @include uds-body-14-medium;
23
+ color: var(--uds-text-secondary);
24
+ white-space: nowrap;
25
+ }
26
+
27
+ &__track {
28
+ position: relative;
29
+ width: 100%;
30
+ background-color: var(--uds-surface-tertiary);
31
+ border-radius: var(--uds-radius-4);
32
+ overflow: hidden;
33
+ }
34
+
35
+ &__fill {
36
+ height: 100%;
37
+ background-color: var(--uds-surface-brand-primary);
38
+ border-radius: var(--uds-radius-4);
39
+ transition: width var(--uds-animation-duration-300) var(--uds-animation-ease-standard);
40
+ position: relative;
41
+ overflow: hidden;
42
+
43
+ // Animated shimmer effect (optional, can be enabled via class)
44
+ &::after {
45
+ content: "";
46
+ position: absolute;
47
+ top: 0;
48
+ left: 0;
49
+ right: 0;
50
+ bottom: 0;
51
+ background: linear-gradient(
52
+ 90deg,
53
+ transparent,
54
+ rgba(255, 255, 255, 0.2),
55
+ transparent
56
+ );
57
+ transform: translateX(-100%);
58
+ transition: transform 0.6s ease;
59
+ }
60
+ }
61
+
62
+ // Size variants
63
+ &--small {
64
+ .uds-progress-indicator__track {
65
+ height: 4px;
66
+ }
67
+ }
68
+
69
+ &--medium {
70
+ .uds-progress-indicator__track {
71
+ height: 8px;
72
+ }
73
+ }
74
+
75
+ &--large {
76
+ .uds-progress-indicator__track {
77
+ height: 12px;
78
+ }
79
+ }
80
+
81
+ // Variant colors
82
+ &--default,
83
+ &--blue {
84
+ .uds-progress-indicator__fill {
85
+ background-color: var(--uds-surface-brand-primary);
86
+ }
87
+ }
88
+
89
+ &--green,
90
+ &--success {
91
+ .uds-progress-indicator__fill {
92
+ background-color: #22c55e; // Green
93
+ }
94
+ }
95
+
96
+ &--orange,
97
+ &--warning {
98
+ .uds-progress-indicator__fill {
99
+ background-color: #f97316; // Orange
100
+ }
101
+ }
102
+
103
+ &--red,
104
+ &--error {
105
+ .uds-progress-indicator__fill {
106
+ background-color: #ef4444; // Red
107
+ }
108
+ }
109
+
110
+ &--purple {
111
+ .uds-progress-indicator__fill {
112
+ background-color: #a855f7; // Purple
113
+ }
114
+ }
115
+
116
+ // Animated state (for indeterminate/loading progress)
117
+ &--animated {
118
+ .uds-progress-indicator__fill {
119
+ &::after {
120
+ animation: progress-shimmer 1.5s infinite;
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ @keyframes progress-shimmer {
127
+ 0% {
128
+ transform: translateX(-100%);
129
+ }
130
+ 100% {
131
+ transform: translateX(100%);
132
+ }
133
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./ProgressIndicator";
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+ import "./Radio.scss";
3
+
4
+ /**
5
+ * Radio component for form inputs
6
+ * @param {boolean} checked - Whether the radio button is checked
7
+ * @param {function} onChange - Callback function when radio button state changes
8
+ * @param {string} name - Name attribute for grouping radio buttons (required for proper functionality)
9
+ * @param {string} id - Unique identifier for the radio input
10
+ * @param {string} value - Value of the radio button
11
+ * @param {string} label - Label text for the radio button
12
+ * @param {boolean} disabled - Whether the radio button is disabled
13
+ * @param {string} className - Additional CSS classes
14
+ * @param {object} props - Additional props to pass to the radio input
15
+ */
16
+ export default function Radio({
17
+ checked = false,
18
+ onChange,
19
+ name,
20
+ id,
21
+ value,
22
+ label,
23
+ disabled = false,
24
+ className = "",
25
+ ...props
26
+ }) {
27
+ const radioId = id || `radio-${Math.random().toString(36).substr(2, 9)}`;
28
+
29
+ const handleChange = (event) => {
30
+ if (!disabled && onChange) {
31
+ onChange(event);
32
+ }
33
+ };
34
+
35
+ return (
36
+ <label
37
+ className={`radio ${disabled ? "radio--disabled" : ""} ${className}`}
38
+ htmlFor={radioId}
39
+ >
40
+ <input
41
+ type="radio"
42
+ id={radioId}
43
+ name={name}
44
+ value={value}
45
+ className="radio__input"
46
+ checked={checked}
47
+ onChange={handleChange}
48
+ disabled={disabled}
49
+ {...props}
50
+ />
51
+ <span className="radio__circle">
52
+ {checked && <span className="radio__dot" />}
53
+ </span>
54
+ {label && <span className="radio__label">{label}</span>}
55
+ </label>
56
+ );
57
+ }
@@ -0,0 +1,84 @@
1
+ @use "../../styles/typography" as *;
2
+
3
+ .radio {
4
+ position: relative;
5
+ display: inline-flex;
6
+ align-items: center;
7
+ gap: var(--uds-gap-8);
8
+ cursor: pointer;
9
+ user-select: none;
10
+ }
11
+
12
+ .radio--disabled {
13
+ cursor: not-allowed;
14
+ opacity: 0.6;
15
+ }
16
+
17
+ .radio__input {
18
+ position: absolute;
19
+ opacity: 0;
20
+ width: 0;
21
+ height: 0;
22
+ margin: 0;
23
+
24
+ &:focus-visible + .radio__circle {
25
+ outline: solid var(--uds-focus-ring-width) var(--uds-focus-ring-border);
26
+ outline-offset: var(--uds-focus-ring-offset);
27
+ }
28
+ }
29
+
30
+ .radio__circle {
31
+ position: relative;
32
+ display: inline-flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ width: 20px;
36
+ height: 20px;
37
+ min-width: 20px;
38
+ min-height: 20px;
39
+ border: var(--uds-border-width-1) solid var(--uds-border-primary);
40
+ border-radius: 50%;
41
+ background-color: var(--uds-surface-primary);
42
+ transition:
43
+ border-color var(--uds-animation-duration-200) var(--uds-animation-ease-standard),
44
+ box-shadow var(--uds-animation-duration-200) var(--uds-animation-ease-standard);
45
+ flex-shrink: 0;
46
+ }
47
+
48
+ .radio__input:checked + .radio__circle {
49
+ border-color: var(--uds-border-brand-primary);
50
+ }
51
+
52
+ .radio__input:not(:checked):not(:disabled) + .radio__circle:hover {
53
+ border-color: var(--uds-border-brand-primary);
54
+ }
55
+
56
+ .radio__input:disabled + .radio__circle {
57
+ background-color: var(--uds-surface-tertiary);
58
+ border-color: var(--uds-border-primary);
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ .radio__dot {
63
+ width: 10px;
64
+ height: 10px;
65
+ border-radius: 50%;
66
+ background-color: var(--uds-surface-brand-primary);
67
+ display: block;
68
+ }
69
+
70
+ .radio__label {
71
+ @include uds-body-14-medium;
72
+ color: var(--uds-text-primary);
73
+ line-height: 20px;
74
+ cursor: pointer;
75
+ }
76
+
77
+ .radio--disabled .radio__label {
78
+ cursor: not-allowed;
79
+ color: var(--uds-text-secondary);
80
+ }
81
+
82
+ .radio__input:disabled + .radio__circle + .radio__label {
83
+ color: var(--uds-text-secondary);
84
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./Radio";
@@ -0,0 +1,283 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import "./Slider.scss";
3
+
4
+ const BASE_CLASS = "uds-slider";
5
+
6
+ /**
7
+ * Slider component for selecting values within a range
8
+ * @param {number|array} value - Current value(s). For single slider: number. For range slider: [min, max] array
9
+ * @param {function} onChange - Callback function when slider value changes
10
+ * @param {number} min - Minimum value
11
+ * @param {number} max - Maximum value
12
+ * @param {number} step - Step increment
13
+ * @param {boolean} range - Whether to use range mode (two handles)
14
+ * @param {boolean} showLabels - Whether to show value labels
15
+ * @param {string} label - Optional label text to display above the slider
16
+ * @param {boolean} disabled - Whether the slider is disabled
17
+ * @param {string} className - Additional CSS classes
18
+ * @param {string} 'aria-label' - Accessible label for screen readers
19
+ * @param {object} props - Additional props to pass to the slider element
20
+ */
21
+ export default function Slider({
22
+ value: controlledValue,
23
+ onChange,
24
+ min = 0,
25
+ max = 100,
26
+ step = 1,
27
+ range = false,
28
+ showLabels = false,
29
+ label,
30
+ disabled = false,
31
+ className = "",
32
+ "aria-label": ariaLabel,
33
+ ...props
34
+ }) {
35
+ const [internalValue, setInternalValue] = useState(range ? [min, max] : min);
36
+ const [isDragging, setIsDragging] = useState(null); // 'min', 'max', or null
37
+ const sliderRef = useRef(null);
38
+ const isControlled = controlledValue !== undefined;
39
+
40
+ const value = isControlled ? controlledValue : internalValue;
41
+ const currentMin = range ? value[0] : min;
42
+ const currentMax = range ? value[1] : value;
43
+
44
+ const sliderId = `slider-${Math.random().toString(36).substr(2, 9)}`;
45
+
46
+ const getPercentage = (val) => {
47
+ return ((val - min) / (max - min)) * 100;
48
+ };
49
+
50
+ const getValueFromPosition = (clientX) => {
51
+ if (!sliderRef.current) return min;
52
+ const rect = sliderRef.current.getBoundingClientRect();
53
+ const percentage = Math.max(
54
+ 0,
55
+ Math.min(1, (clientX - rect.left) / rect.width),
56
+ );
57
+ const rawValue = min + percentage * (max - min);
58
+ return Math.round(rawValue / step) * step;
59
+ };
60
+
61
+ const handleMouseDown = (e, handle) => {
62
+ if (disabled) return;
63
+ e.preventDefault();
64
+ setIsDragging(handle);
65
+ };
66
+
67
+ const handleMouseMove = (e) => {
68
+ if (!isDragging || disabled) return;
69
+
70
+ const newValue = getValueFromPosition(e.clientX);
71
+ let updatedValue;
72
+
73
+ if (range) {
74
+ if (isDragging === "min") {
75
+ updatedValue = [Math.min(newValue, currentMax - step), currentMax];
76
+ } else {
77
+ updatedValue = [currentMin, Math.max(newValue, currentMin + step)];
78
+ }
79
+ } else {
80
+ updatedValue = newValue;
81
+ }
82
+
83
+ if (!isControlled) {
84
+ setInternalValue(updatedValue);
85
+ }
86
+ if (onChange) {
87
+ onChange(updatedValue);
88
+ }
89
+ };
90
+
91
+ const handleMouseUp = () => {
92
+ setIsDragging(null);
93
+ };
94
+
95
+ useEffect(() => {
96
+ if (isDragging) {
97
+ const moveHandler = (e) => handleMouseMove(e);
98
+ const upHandler = () => handleMouseUp();
99
+ document.addEventListener("mousemove", moveHandler);
100
+ document.addEventListener("mouseup", upHandler);
101
+ return () => {
102
+ document.removeEventListener("mousemove", moveHandler);
103
+ document.removeEventListener("mouseup", upHandler);
104
+ };
105
+ }
106
+ }, [
107
+ isDragging,
108
+ currentMin,
109
+ currentMax,
110
+ range,
111
+ step,
112
+ disabled,
113
+ isControlled,
114
+ onChange,
115
+ ]);
116
+
117
+ const handleInputChange = (e, handle) => {
118
+ if (disabled) return;
119
+ const newValue = parseFloat(e.target.value);
120
+ let updatedValue;
121
+
122
+ if (range) {
123
+ if (handle === "min") {
124
+ updatedValue = [Math.min(newValue, currentMax - step), currentMax];
125
+ } else {
126
+ updatedValue = [currentMin, Math.max(newValue, currentMin + step)];
127
+ }
128
+ } else {
129
+ updatedValue = newValue;
130
+ }
131
+
132
+ if (!isControlled) {
133
+ setInternalValue(updatedValue);
134
+ }
135
+ if (onChange) {
136
+ onChange(updatedValue);
137
+ }
138
+ };
139
+
140
+ const minPercentage = getPercentage(currentMin);
141
+ const maxPercentage = getPercentage(currentMax);
142
+ const leftPosition = minPercentage;
143
+ const width = maxPercentage - minPercentage;
144
+
145
+ const classNames = [
146
+ BASE_CLASS,
147
+ range && `${BASE_CLASS}--range`,
148
+ disabled && `${BASE_CLASS}--disabled`,
149
+ className,
150
+ ]
151
+ .filter(Boolean)
152
+ .join(" ");
153
+
154
+ return (
155
+ <div className={classNames} {...props}>
156
+ {label && (
157
+ <div className={`${BASE_CLASS}__header`}>
158
+ <label htmlFor={sliderId} className={`${BASE_CLASS}__label`}>
159
+ {label}
160
+ </label>
161
+ </div>
162
+ )}
163
+ <div className={`${BASE_CLASS}__container`} ref={sliderRef}>
164
+ <div className={`${BASE_CLASS}__track`}>
165
+ {range ? (
166
+ <>
167
+ <div
168
+ className={`${BASE_CLASS}__fill`}
169
+ style={{
170
+ left: `${leftPosition}%`,
171
+ width: `${width}%`,
172
+ }}
173
+ />
174
+ <input
175
+ type="range"
176
+ id={`${sliderId}-min`}
177
+ min={min}
178
+ max={max}
179
+ step={step}
180
+ value={currentMin}
181
+ onChange={(e) => handleInputChange(e, "min")}
182
+ disabled={disabled}
183
+ className={`${BASE_CLASS}__input ${BASE_CLASS}__input--min`}
184
+ aria-label={ariaLabel || `${label || "Slider"} minimum value`}
185
+ />
186
+ <input
187
+ type="range"
188
+ id={`${sliderId}-max`}
189
+ min={min}
190
+ max={max}
191
+ step={step}
192
+ value={currentMax}
193
+ onChange={(e) => handleInputChange(e, "max")}
194
+ disabled={disabled}
195
+ className={`${BASE_CLASS}__input ${BASE_CLASS}__input--max`}
196
+ aria-label={ariaLabel || `${label || "Slider"} maximum value`}
197
+ />
198
+ <div
199
+ className={`${BASE_CLASS}__thumb ${BASE_CLASS}__thumb--min`}
200
+ style={{ left: `${minPercentage}%` }}
201
+ onMouseDown={(e) => handleMouseDown(e, "min")}
202
+ role="slider"
203
+ aria-valuemin={min}
204
+ aria-valuemax={max}
205
+ aria-valuenow={currentMin}
206
+ tabIndex={disabled ? -1 : 0}
207
+ />
208
+ <div
209
+ className={`${BASE_CLASS}__thumb ${BASE_CLASS}__thumb--max`}
210
+ style={{ left: `${maxPercentage}%` }}
211
+ onMouseDown={(e) => handleMouseDown(e, "max")}
212
+ role="slider"
213
+ aria-valuemin={min}
214
+ aria-valuemax={max}
215
+ aria-valuenow={currentMax}
216
+ tabIndex={disabled ? -1 : 0}
217
+ />
218
+ </>
219
+ ) : (
220
+ <>
221
+ <div
222
+ className={`${BASE_CLASS}__fill`}
223
+ style={{ width: `${maxPercentage}%` }}
224
+ />
225
+ <input
226
+ type="range"
227
+ id={sliderId}
228
+ min={min}
229
+ max={max}
230
+ step={step}
231
+ value={currentMax}
232
+ onChange={(e) => handleInputChange(e, "single")}
233
+ disabled={disabled}
234
+ className={`${BASE_CLASS}__input`}
235
+ aria-label={ariaLabel || label || "Slider"}
236
+ />
237
+ <div
238
+ className={`${BASE_CLASS}__thumb`}
239
+ style={{ left: `${maxPercentage}%` }}
240
+ onMouseDown={(e) => handleMouseDown(e, "single")}
241
+ role="slider"
242
+ aria-valuemin={min}
243
+ aria-valuemax={max}
244
+ aria-valuenow={currentMax}
245
+ tabIndex={disabled ? -1 : 0}
246
+ />
247
+ </>
248
+ )}
249
+ </div>
250
+ {showLabels && (
251
+ <div className={`${BASE_CLASS}__labels`}>
252
+ {range ? (
253
+ <>
254
+ <span
255
+ className={`${BASE_CLASS}__label-value`}
256
+ style={{ left: `${minPercentage}%` }}
257
+ >
258
+ {Math.round(((currentMin - min) / (max - min)) * 100)}%
259
+ </span>
260
+ <span
261
+ className={`${BASE_CLASS}__label-value`}
262
+ style={{ left: `${maxPercentage}%` }}
263
+ >
264
+ {Math.round(((currentMax - min) / (max - min)) * 100)}%
265
+ </span>
266
+ </>
267
+ ) : (
268
+ <>
269
+ <span className={`${BASE_CLASS}__label-value`}>0%</span>
270
+ <span
271
+ className={`${BASE_CLASS}__label-value`}
272
+ style={{ left: `${maxPercentage}%` }}
273
+ >
274
+ {Math.round(((currentMax - min) / (max - min)) * 100)}%
275
+ </span>
276
+ </>
277
+ )}
278
+ </div>
279
+ )}
280
+ </div>
281
+ </div>
282
+ );
283
+ }
@@ -0,0 +1,156 @@
1
+ @use "../../styles/typography" as *;
2
+
3
+ .uds-slider {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--uds-gap-8);
7
+ width: 100%;
8
+
9
+ &__header {
10
+ display: flex;
11
+ justify-content: space-between;
12
+ align-items: center;
13
+ }
14
+
15
+ &__label {
16
+ @include uds-body-14-medium;
17
+ color: var(--uds-text-primary);
18
+ }
19
+
20
+ &__container {
21
+ position: relative;
22
+ width: 100%;
23
+ padding: var(--uds-spacing-8) 0;
24
+ }
25
+
26
+ &__track {
27
+ position: relative;
28
+ width: 100%;
29
+ height: 4px;
30
+ background-color: var(--uds-surface-tertiary);
31
+ border-radius: var(--uds-radius-2);
32
+ cursor: pointer;
33
+ }
34
+
35
+ &__fill {
36
+ position: absolute;
37
+ top: 0;
38
+ height: 100%;
39
+ background-color: var(--uds-surface-brand-primary);
40
+ border-radius: var(--uds-radius-2);
41
+ pointer-events: none;
42
+ }
43
+
44
+ &__input {
45
+ position: absolute;
46
+ width: 100%;
47
+ height: 4px;
48
+ top: 0;
49
+ left: 0;
50
+ margin: 0;
51
+ padding: 0;
52
+ opacity: 0;
53
+ cursor: pointer;
54
+ z-index: 2;
55
+
56
+ &::-webkit-slider-thumb {
57
+ appearance: none;
58
+ width: 0;
59
+ height: 0;
60
+ }
61
+
62
+ &::-moz-range-thumb {
63
+ width: 0;
64
+ height: 0;
65
+ border: none;
66
+ }
67
+
68
+ &:focus-visible {
69
+ outline: none;
70
+ }
71
+
72
+ &:focus-visible + .uds-slider__thumb {
73
+ outline: solid var(--uds-focus-ring-width) var(--uds-focus-ring-border);
74
+ outline-offset: var(--uds-focus-ring-offset);
75
+ }
76
+
77
+ &--min {
78
+ z-index: 3;
79
+ }
80
+
81
+ &--max {
82
+ z-index: 4;
83
+ }
84
+ }
85
+
86
+ &__thumb {
87
+ position: absolute;
88
+ width: 20px;
89
+ height: 20px;
90
+ background-color: var(--uds-surface-primary);
91
+ border: var(--uds-border-width-1) solid var(--uds-border-primary);
92
+ border-radius: 50%;
93
+ cursor: grab;
94
+ transform: translate(-50%, -50%);
95
+ top: 50%;
96
+ z-index: 5;
97
+ transition:
98
+ border-color var(--uds-animation-duration-200) var(--uds-animation-ease-standard),
99
+ box-shadow var(--uds-animation-duration-200) var(--uds-animation-ease-standard);
100
+
101
+ &:hover:not(.uds-slider--disabled &) {
102
+ border-color: var(--uds-border-brand-primary);
103
+ }
104
+
105
+ &:active {
106
+ cursor: grabbing;
107
+ }
108
+
109
+ &--min {
110
+ z-index: 6;
111
+ }
112
+
113
+ &--max {
114
+ z-index: 7;
115
+ }
116
+ }
117
+
118
+ &__labels {
119
+ position: relative;
120
+ width: 100%;
121
+ margin-top: var(--uds-gap-8);
122
+ height: 20px;
123
+ }
124
+
125
+ &__label-value {
126
+ @include uds-body-12-medium;
127
+ position: absolute;
128
+ color: var(--uds-text-secondary);
129
+ transform: translateX(-50%);
130
+ white-space: nowrap;
131
+ }
132
+
133
+ &--disabled {
134
+ opacity: 0.6;
135
+ pointer-events: none;
136
+ cursor: not-allowed;
137
+
138
+ .uds-slider__track {
139
+ cursor: not-allowed;
140
+ }
141
+
142
+ .uds-slider__thumb {
143
+ cursor: not-allowed;
144
+ }
145
+ }
146
+
147
+ &--range {
148
+ .uds-slider__input--min {
149
+ z-index: 3;
150
+ }
151
+
152
+ .uds-slider__input--max {
153
+ z-index: 4;
154
+ }
155
+ }
156
+ }