@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,89 @@
1
+ import React from "react";
2
+ import Icon from "../Icon/Icon";
3
+ import "./Field.scss";
4
+
5
+ const BASE_CLASS = "uds-field";
6
+
7
+ /**
8
+ * Field component for form input fields with label, helper text, and optional features
9
+ * @param {string} label - Label text for the field
10
+ * @param {boolean} required - Whether the field is required (adds asterisk to label)
11
+ * @param {string} helperMessage - Helper text displayed below the input
12
+ * @param {string} infoIcon - Icon name for info icon (e.g., "Info")
13
+ * @param {function} onInfoClick - Callback when info icon is clicked
14
+ * @param {number} maxLength - Maximum character length (enables character count)
15
+ * @param {number|string} value - Current value (for character count calculation)
16
+ * @param {string} id - Unique identifier for the field
17
+ * @param {string} className - Additional CSS classes
18
+ * @param {React.ReactNode} children - The input element to wrap
19
+ * @param {object} props - Additional props to pass to the field wrapper
20
+ */
21
+ export default function Field({
22
+ label,
23
+ required = false,
24
+ helperMessage,
25
+ infoIcon,
26
+ onInfoClick,
27
+ maxLength,
28
+ value,
29
+ id,
30
+ className = "",
31
+ children,
32
+ ...props
33
+ }) {
34
+ const fieldId = id || `field-${Math.random().toString(36).substr(2, 9)}`;
35
+
36
+ // Calculate character count if maxLength is provided
37
+ const currentLength = value ? String(value).length : 0;
38
+ const showCharacterCount = maxLength !== undefined && maxLength !== null;
39
+
40
+ const classNames = [BASE_CLASS, className].filter(Boolean).join(" ");
41
+
42
+ return (
43
+ <div className={classNames} {...props}>
44
+ {/* Label Row */}
45
+ {(label || infoIcon) && (
46
+ <div className={`${BASE_CLASS}__label-row`}>
47
+ {label && (
48
+ <label htmlFor={fieldId} className={`${BASE_CLASS}__label`}>
49
+ {label}
50
+ {required && <span className={`${BASE_CLASS}__required`}>*</span>}
51
+ </label>
52
+ )}
53
+ {infoIcon && (
54
+ <button
55
+ type="button"
56
+ className={`${BASE_CLASS}__info-icon`}
57
+ onClick={onInfoClick}
58
+ aria-label="More information"
59
+ tabIndex={0}
60
+ >
61
+ <Icon name={infoIcon} size={16} appearance="regular" />
62
+ </button>
63
+ )}
64
+ </div>
65
+ )}
66
+
67
+ {/* Input Slot */}
68
+ <div className={`${BASE_CLASS}__input-wrapper`}>
69
+ {React.isValidElement(children) && fieldId
70
+ ? React.cloneElement(children, {
71
+ id: fieldId,
72
+ maxLength: maxLength || children.props.maxLength,
73
+ ...children.props,
74
+ })
75
+ : children}
76
+ {showCharacterCount && (
77
+ <span className={`${BASE_CLASS}__character-count`}>
78
+ {currentLength}/{maxLength}
79
+ </span>
80
+ )}
81
+ </div>
82
+
83
+ {/* Helper Message */}
84
+ {helperMessage && (
85
+ <div className={`${BASE_CLASS}__helper`}>{helperMessage}</div>
86
+ )}
87
+ </div>
88
+ );
89
+ }
@@ -0,0 +1,119 @@
1
+ @use "../../styles/typography" as *;
2
+
3
+ .uds-field {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--uds-gap-8);
7
+ width: 100%;
8
+
9
+ &__label-row {
10
+ display: flex;
11
+ align-items: center;
12
+ gap: var(--uds-gap-8);
13
+ justify-content: space-between;
14
+ }
15
+
16
+ &__label {
17
+ @include uds-body-14-medium;
18
+ color: var(--uds-text-primary);
19
+ display: inline-block;
20
+ line-height: 20px;
21
+ }
22
+
23
+ &__required {
24
+ color: var(--uds-text-error, #ef4444);
25
+ margin-left: 2px;
26
+ }
27
+
28
+ &__info-icon {
29
+ all: unset;
30
+ display: inline-flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ width: 20px;
34
+ height: 20px;
35
+ border-radius: 50%;
36
+ background-color: var(--uds-surface-tertiary);
37
+ color: var(--uds-text-secondary);
38
+ cursor: pointer;
39
+ flex-shrink: 0;
40
+ transition: all var(--uds-animation-duration-200) var(--uds-animation-ease-standard);
41
+
42
+ &:hover {
43
+ background-color: var(--uds-surface-quaternary);
44
+ color: var(--uds-text-primary);
45
+ }
46
+
47
+ &:focus-visible {
48
+ outline: solid var(--uds-focus-ring-width) var(--uds-focus-ring-border);
49
+ outline-offset: var(--uds-focus-ring-offset);
50
+ }
51
+ }
52
+
53
+ &__input-wrapper {
54
+ position: relative;
55
+ display: flex;
56
+ align-items: flex-start;
57
+ width: 100%;
58
+
59
+ // Style the input element
60
+ input,
61
+ textarea,
62
+ select {
63
+ width: 100%;
64
+ padding: var(--uds-spacing-10) var(--uds-spacing-12);
65
+ border: var(--uds-border-width-1) solid var(--uds-border-primary);
66
+ border-radius: var(--uds-radius-4);
67
+ background-color: var(--uds-surface-primary);
68
+ color: var(--uds-text-primary);
69
+ @include uds-body-16;
70
+ transition: all var(--uds-animation-duration-200) var(--uds-animation-ease-standard);
71
+ box-sizing: border-box;
72
+
73
+ &::placeholder {
74
+ color: var(--uds-text-secondary);
75
+ }
76
+
77
+ &:focus {
78
+ outline: none;
79
+ border-color: var(--uds-border-brand-primary);
80
+ }
81
+
82
+ &:disabled {
83
+ background-color: var(--uds-surface-tertiary);
84
+ color: var(--uds-text-secondary);
85
+ cursor: not-allowed;
86
+ opacity: 0.6;
87
+ }
88
+ }
89
+
90
+ input,
91
+ select {
92
+ // For single-line inputs, center align vertically
93
+ align-self: center;
94
+ }
95
+
96
+ textarea {
97
+ resize: vertical;
98
+ min-height: 80px;
99
+ align-self: stretch;
100
+ }
101
+ }
102
+
103
+ &__character-count {
104
+ @include uds-body-12;
105
+ position: absolute;
106
+ bottom: var(--uds-spacing-8);
107
+ right: var(--uds-spacing-12);
108
+ color: var(--uds-text-secondary);
109
+ pointer-events: none;
110
+ line-height: 1;
111
+ }
112
+
113
+ &__helper {
114
+ @include uds-body-14;
115
+ color: var(--uds-text-secondary);
116
+ line-height: 20px;
117
+ margin-top: calc(var(--uds-gap-8) * -1);
118
+ }
119
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./Field";
@@ -0,0 +1,28 @@
1
+ import FileUpload from './FileUpload';
2
+ import { code } from '@figma/code-connect';
3
+
4
+ export default code.connect(
5
+ FileUpload,
6
+ 'https://www.figma.com/design/LkIyThUA0oVNsDEAyOF7ER/Design-System--Components',
7
+ {
8
+ props: {
9
+ instructionText: code.string('instructionText'),
10
+ acceptText: code.string('acceptText'),
11
+ maxSize: code.number('maxSize'),
12
+ accept: code.array('accept'),
13
+ multiple: code.boolean('multiple'),
14
+ disabled: code.boolean('disabled'),
15
+ },
16
+ example: ({ instructionText, acceptText, maxSize, accept, multiple, disabled }) => (
17
+ <FileUpload
18
+ instructionText={instructionText}
19
+ acceptText={acceptText}
20
+ maxSize={maxSize}
21
+ accept={accept}
22
+ multiple={multiple}
23
+ disabled={disabled}
24
+ onFileSelect={(files) => console.log('Files selected:', files)}
25
+ />
26
+ ),
27
+ }
28
+ );
@@ -0,0 +1,153 @@
1
+ import React, { useState, useRef } from "react";
2
+ import Icon from "../Icon/Icon";
3
+ import "./FileUpload.scss";
4
+
5
+ const BASE_CLASS = "uds-file-upload";
6
+
7
+ /**
8
+ * FileUpload component for drag-and-drop and click-to-upload file selection
9
+ * @param {function} onFileSelect - Callback function when files are selected (receives FileList)
10
+ * @param {array} accept - Array of accepted file types (e.g., ['image/png', 'image/jpg'])
11
+ * @param {number} maxSize - Maximum file size in MB (default: 10)
12
+ * @param {string} acceptText - Text to display accepted file types (e.g., "PNG, JPG")
13
+ * @param {string} instructionText - Custom instruction text (default: "Drop files here or click to upload")
14
+ * @param {boolean} multiple - Whether to allow multiple file selection (default: false)
15
+ * @param {boolean} disabled - Whether the upload is disabled
16
+ * @param {string} className - Additional CSS classes
17
+ * @param {object} props - Additional props to pass to the file input
18
+ */
19
+ export default function FileUpload({
20
+ onFileSelect,
21
+ accept = [],
22
+ maxSize = 10,
23
+ acceptText,
24
+ instructionText = "Drop files here or click to upload",
25
+ multiple = false,
26
+ disabled = false,
27
+ className = "",
28
+ ...props
29
+ }) {
30
+ const [isDragging, setIsDragging] = useState(false);
31
+ const fileInputRef = useRef(null);
32
+
33
+ const classNames = [
34
+ BASE_CLASS,
35
+ isDragging && `${BASE_CLASS}--dragging`,
36
+ disabled && `${BASE_CLASS}--disabled`,
37
+ className,
38
+ ]
39
+ .filter(Boolean)
40
+ .join(" ");
41
+
42
+ const handleDragEnter = (e) => {
43
+ e.preventDefault();
44
+ e.stopPropagation();
45
+ if (!disabled) {
46
+ setIsDragging(true);
47
+ }
48
+ };
49
+
50
+ const handleDragLeave = (e) => {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ setIsDragging(false);
54
+ };
55
+
56
+ const handleDragOver = (e) => {
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ };
60
+
61
+ const handleDrop = (e) => {
62
+ e.preventDefault();
63
+ e.stopPropagation();
64
+ setIsDragging(false);
65
+
66
+ if (disabled) return;
67
+
68
+ const files = e.dataTransfer.files;
69
+ if (files && files.length > 0) {
70
+ handleFiles(files);
71
+ }
72
+ };
73
+
74
+ const handleFileInputChange = (e) => {
75
+ const files = e.target.files;
76
+ if (files && files.length > 0) {
77
+ handleFiles(files);
78
+ }
79
+ // Reset input so same file can be selected again
80
+ e.target.value = "";
81
+ };
82
+
83
+ const handleFiles = (files) => {
84
+ const fileArray = Array.from(files);
85
+ const validFiles = fileArray.filter((file) => {
86
+ // Check file type
87
+ if (accept.length > 0 && !accept.some((type) => file.type.match(type))) {
88
+ return false;
89
+ }
90
+ // Check file size (convert MB to bytes)
91
+ if (file.size > maxSize * 1024 * 1024) {
92
+ return false;
93
+ }
94
+ return true;
95
+ });
96
+
97
+ if (validFiles.length > 0 && onFileSelect) {
98
+ // Create a FileList-like object
99
+ const dataTransfer = new DataTransfer();
100
+ validFiles.forEach((file) => dataTransfer.items.add(file));
101
+ onFileSelect(dataTransfer.files);
102
+ }
103
+ };
104
+
105
+ const handleClick = () => {
106
+ if (!disabled && fileInputRef.current) {
107
+ fileInputRef.current.click();
108
+ }
109
+ };
110
+
111
+ // Generate accept text from accept prop if not provided
112
+ const displayAcceptText =
113
+ acceptText ||
114
+ (accept.length > 0
115
+ ? accept
116
+ .map((type) => {
117
+ const ext = type.split("/")[1]?.toUpperCase();
118
+ return ext || type;
119
+ })
120
+ .join(", ")
121
+ : "All files");
122
+
123
+ return (
124
+ <div
125
+ className={classNames}
126
+ onDragEnter={handleDragEnter}
127
+ onDragLeave={handleDragLeave}
128
+ onDragOver={handleDragOver}
129
+ onDrop={handleDrop}
130
+ onClick={handleClick}
131
+ >
132
+ <input
133
+ ref={fileInputRef}
134
+ type="file"
135
+ className={`${BASE_CLASS}__input`}
136
+ accept={accept.length > 0 ? accept.join(",") : undefined}
137
+ multiple={multiple}
138
+ disabled={disabled}
139
+ onChange={handleFileInputChange}
140
+ {...props}
141
+ />
142
+ <div className={`${BASE_CLASS}__content`}>
143
+ <div className={`${BASE_CLASS}__icon`}>
144
+ <Icon name="Upload" size={32} appearance="regular" />
145
+ </div>
146
+ <p className={`${BASE_CLASS}__instruction`}>{instructionText}</p>
147
+ <p className={`${BASE_CLASS}__accept`}>
148
+ {displayAcceptText} up to {maxSize}MB
149
+ </p>
150
+ </div>
151
+ </div>
152
+ );
153
+ }
@@ -0,0 +1,78 @@
1
+ @use "../../styles/typography" as *;
2
+
3
+ .uds-file-upload {
4
+ position: relative;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ min-height: 200px;
9
+ padding: var(--uds-spacing-48);
10
+ background: var(--uds-surface-primary, #ffffff);
11
+ border: var(--uds-border-width-2) dashed var(--uds-border-primary, #d1d5db);
12
+ border-radius: var(--uds-radius-8);
13
+ cursor: pointer;
14
+ transition: all var(--uds-animation-duration-200) var(--uds-animation-ease-standard);
15
+
16
+ &:hover:not(&--disabled) {
17
+ border-color: var(--uds-border-brand-primary, #3b82f6);
18
+ background: var(--uds-surface-secondary, #f9fafb);
19
+ }
20
+
21
+ &--dragging {
22
+ border-color: var(--uds-border-brand-primary, #3b82f6);
23
+ background: var(--uds-surface-brand-tertiary, #eff6ff);
24
+ }
25
+
26
+ &--disabled {
27
+ cursor: not-allowed;
28
+ opacity: 0.6;
29
+ pointer-events: none;
30
+ }
31
+
32
+ &__input {
33
+ position: absolute;
34
+ opacity: 0;
35
+ width: 0;
36
+ height: 0;
37
+ pointer-events: none;
38
+ }
39
+
40
+ &__content {
41
+ display: flex;
42
+ flex-direction: column;
43
+ align-items: center;
44
+ justify-content: center;
45
+ gap: var(--uds-gap-12);
46
+ text-align: center;
47
+ pointer-events: none;
48
+ }
49
+
50
+ &__icon {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ color: var(--uds-text-secondary, #6b7280);
55
+ transition: color var(--uds-animation-duration-200) var(--uds-animation-ease-standard);
56
+
57
+ .uds-file-upload:hover:not(.uds-file-upload--disabled) & {
58
+ color: var(--uds-text-brand-primary, #3b82f6);
59
+ }
60
+
61
+ .uds-file-upload--dragging & {
62
+ color: var(--uds-text-brand-primary, #3b82f6);
63
+ }
64
+ }
65
+
66
+ &__instruction {
67
+ @include uds-body-16;
68
+ margin: 0;
69
+ color: var(--uds-text-primary, #111827);
70
+ font-weight: var(--uds-font-weight-medium);
71
+ }
72
+
73
+ &__accept {
74
+ @include uds-body-14;
75
+ margin: 0;
76
+ color: var(--uds-text-secondary, #6b7280);
77
+ }
78
+ }
@@ -0,0 +1,2 @@
1
+ export { default as FileUpload } from "./FileUpload";
2
+ export { default } from "./FileUpload";
@@ -0,0 +1,42 @@
1
+ import React from "react";
2
+ import "./Flex.scss";
3
+
4
+ /**
5
+ * Flex layout component
6
+ * @param {string} direction - Flex direction: 'row', 'column', 'row-reverse', 'column-reverse'
7
+ * @param {string} gap - Gap between items (uses UDS gap tokens)
8
+ * @param {string} alignItems - Align items: 'flex-start', 'flex-end', 'center', 'stretch', 'baseline'
9
+ * @param {string} justifyContent - Justify content: 'flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly'
10
+ * @param {boolean} wrap - Whether to wrap items
11
+ * @param {string} className - Additional CSS classes
12
+ * @param {React.ReactNode} children - Child elements
13
+ * @param {object} props - Additional props to pass to the flex container
14
+ */
15
+ export default function Flex({
16
+ direction = "row",
17
+ gap,
18
+ alignItems,
19
+ justifyContent,
20
+ wrap = false,
21
+ className = "",
22
+ children,
23
+ ...props
24
+ }) {
25
+ const flexClasses = [
26
+ "uds-flex",
27
+ direction && `uds-flex--${direction}`,
28
+ wrap && "uds-flex--wrap",
29
+ gap && `uds-flex--gap-${gap}`,
30
+ alignItems && `uds-flex--align-${alignItems.replace("-", "_")}`,
31
+ justifyContent && `uds-flex--justify-${justifyContent.replace(/-/g, "_")}`,
32
+ className,
33
+ ]
34
+ .filter(Boolean)
35
+ .join(" ");
36
+
37
+ return (
38
+ <div className={flexClasses} {...props}>
39
+ {children}
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,119 @@
1
+ @use "../../styles/typography" as *;
2
+
3
+ .uds-flex {
4
+ display: flex;
5
+
6
+ // Direction variants
7
+ &--row {
8
+ flex-direction: row;
9
+ }
10
+
11
+ &--column {
12
+ flex-direction: column;
13
+ }
14
+
15
+ &--row-reverse {
16
+ flex-direction: row-reverse;
17
+ }
18
+
19
+ &--column-reverse {
20
+ flex-direction: column-reverse;
21
+ }
22
+
23
+ // Wrap
24
+ &--wrap {
25
+ flex-wrap: wrap;
26
+ }
27
+
28
+ // Gap variants (using UDS gap tokens)
29
+ &--gap-4 {
30
+ gap: var(--uds-gap-4);
31
+ }
32
+
33
+ &--gap-8 {
34
+ gap: var(--uds-gap-8);
35
+ }
36
+
37
+ &--gap-12 {
38
+ gap: var(--uds-gap-12);
39
+ }
40
+
41
+ &--gap-16 {
42
+ gap: var(--uds-gap-16);
43
+ }
44
+
45
+ &--gap-24 {
46
+ gap: var(--uds-gap-24);
47
+ }
48
+
49
+ &--gap-32 {
50
+ gap: var(--uds-gap-32);
51
+ }
52
+
53
+ // Align items variants
54
+ &--align-flex_start {
55
+ align-items: flex-start;
56
+ }
57
+
58
+ &--align-flex_end {
59
+ align-items: flex-end;
60
+ }
61
+
62
+ &--align-center {
63
+ align-items: center;
64
+ }
65
+
66
+ &--align-stretch {
67
+ align-items: stretch;
68
+ }
69
+
70
+ &--align-baseline {
71
+ align-items: baseline;
72
+ }
73
+
74
+ // Justify content variants
75
+ &--justify-flex_start {
76
+ justify-content: flex-start;
77
+ }
78
+
79
+ &--justify-flex_end {
80
+ justify-content: flex-end;
81
+ }
82
+
83
+ &--justify-center {
84
+ justify-content: center;
85
+ }
86
+
87
+ &--justify-space_between {
88
+ justify-content: space-between;
89
+ }
90
+
91
+ &--justify-space_around {
92
+ justify-content: space-around;
93
+ }
94
+
95
+ &--justify-space_evenly {
96
+ justify-content: space-evenly;
97
+ }
98
+ }
99
+
100
+ // Flex item utilities
101
+ .uds-flex-item {
102
+ flex: 0 1 auto;
103
+
104
+ &--grow {
105
+ flex: 1 1 auto;
106
+ }
107
+
108
+ &--shrink {
109
+ flex: 0 1 auto;
110
+ }
111
+
112
+ &--no-grow {
113
+ flex: 0 1 auto;
114
+ }
115
+
116
+ &--no-shrink {
117
+ flex: 1 0 auto;
118
+ }
119
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./Flex";
@@ -0,0 +1,22 @@
1
+ import React from "react"
2
+ import Icon from "./Icon"
3
+ import figma from "@figma/code-connect"
4
+
5
+ /**
6
+ * -- This file was auto-generated by Code Connect --
7
+ * `props` includes a mapping from Figma properties and variants to
8
+ * suggested values. You should update this to match the props of your
9
+ * code component, and update the `example` function to return the
10
+ * code example you'd like to see in Figma
11
+ */
12
+
13
+ figma.connect(
14
+ Icon,
15
+ "https://www.figma.com/file/LkIyThUA0oVNsDEAyOF7ER/Design-System--Components?node-id=1393%3A7018",
16
+ {
17
+ props: {
18
+ icon: figma.instance("Icon"),
19
+ },
20
+ example: (props) => <Icon />,
21
+ },
22
+ )