@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.
- package/.github/workflows/figma-sync.yml +30 -0
- package/ARCHITECTURE_FIX.md +241 -0
- package/LICENSE +21 -0
- package/README.lib.md +103 -0
- package/README.md +177 -0
- package/figma.config.json +9 -0
- package/package.json +67 -0
- package/package.lib.json +49 -0
- package/public/data/figma-variables.json +40026 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +46 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/public/styles/tokens.css +1994 -0
- package/scripts/index.js +896 -0
- package/scripts/publish-lib.js +150 -0
- package/scripts/validate.js +94 -0
- package/src/App.css +457 -0
- package/src/App.css.map +1 -0
- package/src/App.js +161 -0
- package/src/App.scss +548 -0
- package/src/App.test.js +8 -0
- package/src/assets/images/.gitkeep +0 -0
- package/src/assets/images/doctors/Avatar-1.png +0 -0
- package/src/assets/images/doctors/Avatar-10.png +0 -0
- package/src/assets/images/doctors/Avatar-11.png +0 -0
- package/src/assets/images/doctors/Avatar-12.png +0 -0
- package/src/assets/images/doctors/Avatar-13.png +0 -0
- package/src/assets/images/doctors/Avatar-14.png +0 -0
- package/src/assets/images/doctors/Avatar-15.png +0 -0
- package/src/assets/images/doctors/Avatar-16.png +0 -0
- package/src/assets/images/doctors/Avatar-17.png +0 -0
- package/src/assets/images/doctors/Avatar-18.png +0 -0
- package/src/assets/images/doctors/Avatar-19.png +0 -0
- package/src/assets/images/doctors/Avatar-2.png +0 -0
- package/src/assets/images/doctors/Avatar-20.png +0 -0
- package/src/assets/images/doctors/Avatar-21.png +0 -0
- package/src/assets/images/doctors/Avatar-3.png +0 -0
- package/src/assets/images/doctors/Avatar-4.png +0 -0
- package/src/assets/images/doctors/Avatar-5.png +0 -0
- package/src/assets/images/doctors/Avatar-6.png +0 -0
- package/src/assets/images/doctors/Avatar-7.png +0 -0
- package/src/assets/images/doctors/Avatar-8.png +0 -0
- package/src/assets/images/doctors/Avatar-9.png +0 -0
- package/src/assets/images/doctors/Avatar.png +0 -0
- package/src/assets/images/doctors/index.js +141 -0
- package/src/data/figma-variables.json +90305 -0
- package/src/index.js +20 -0
- package/src/index.scss +10 -0
- package/src/pages/AccordionDemo.jsx +206 -0
- package/src/pages/AccordionDemo.scss +34 -0
- package/src/pages/ActionMenuDemo.jsx +957 -0
- package/src/pages/ActionMenuDemo.scss +34 -0
- package/src/pages/AvatarDemo.jsx +328 -0
- package/src/pages/AvatarDemo.scss +40 -0
- package/src/pages/BadgeDemo.jsx +254 -0
- package/src/pages/BadgeDemo.scss +40 -0
- package/src/pages/BorderRadiusDemo.jsx +112 -0
- package/src/pages/BorderRadiusDemo.scss +50 -0
- package/src/pages/BrandingDemo.jsx +117 -0
- package/src/pages/BreadcrumbDemo.jsx +172 -0
- package/src/pages/ButtonDemo.jsx +708 -0
- package/src/pages/ButtonDemo.scss +34 -0
- package/src/pages/CheckboxDemo.jsx +194 -0
- package/src/pages/ChipDemo.jsx +359 -0
- package/src/pages/ChipDemo.scss +40 -0
- package/src/pages/ColorsDemo.jsx +566 -0
- package/src/pages/ColorsDemo.scss +243 -0
- package/src/pages/ComponentsUsage.jsx +401 -0
- package/src/pages/DatepickerDemo.jsx +223 -0
- package/src/pages/DividerDemo.jsx +337 -0
- package/src/pages/DotStatusDemo.jsx +223 -0
- package/src/pages/DropdownDemo.jsx +229 -0
- package/src/pages/FieldDemo.jsx +253 -0
- package/src/pages/FigmaVariablesDemo.jsx +426 -0
- package/src/pages/FigmaVariablesDemo.scss +316 -0
- package/src/pages/FileUploadDemo.jsx +186 -0
- package/src/pages/FlexDemo.jsx +144 -0
- package/src/pages/FlexDemo.scss +119 -0
- package/src/pages/FontInstallation.jsx +252 -0
- package/src/pages/FontInstallation.scss +40 -0
- package/src/pages/Home.jsx +3156 -0
- package/src/pages/IconDemo.jsx +1680 -0
- package/src/pages/ImageAspectDemo.jsx +152 -0
- package/src/pages/InputDemo.jsx +245 -0
- package/src/pages/Installation.jsx +257 -0
- package/src/pages/Installation.scss +40 -0
- package/src/pages/KeyDemo.jsx +184 -0
- package/src/pages/MenuDemo.jsx +139 -0
- package/src/pages/MicroCalendarDemo.jsx +165 -0
- package/src/pages/PaginationDemo.jsx +176 -0
- package/src/pages/PillToggleDemo.jsx +212 -0
- package/src/pages/ProgressCircleDemo.jsx +206 -0
- package/src/pages/ProgressIndicatorDemo.jsx +227 -0
- package/src/pages/RadioDemo.jsx +282 -0
- package/src/pages/ShadowsDemo.jsx +118 -0
- package/src/pages/ShadowsDemo.scss +93 -0
- package/src/pages/SliderDemo.jsx +226 -0
- package/src/pages/SpacingDemo.jsx +160 -0
- package/src/pages/SpacingDemo.scss +107 -0
- package/src/pages/StatusDemo.jsx +196 -0
- package/src/pages/StepsDemo.jsx +308 -0
- package/src/pages/TableDemo.jsx +376 -0
- package/src/pages/TabsDemo.jsx +221 -0
- package/src/pages/ToastDemo.jsx +195 -0
- package/src/pages/ToggleDemo.jsx +187 -0
- package/src/pages/TokensDemo.jsx +637 -0
- package/src/pages/TokensDemo.scss +270 -0
- package/src/pages/TokensUsage.jsx +220 -0
- package/src/pages/TooltipDemo.jsx +170 -0
- package/src/pages/TypographyDemo.jsx +229 -0
- package/src/pages/TypographyDemo.scss +105 -0
- package/src/pages/UtilitiesDemo.jsx +381 -0
- package/src/pages/UtilitiesDemo.scss +214 -0
- package/src/reportWebVitals.js +13 -0
- package/src/setupTests.js +5 -0
- package/src/styles/_typography.scss +932 -0
- package/src/styles/_utilities.scss +3635 -0
- package/src/styles/_variables.scss +887 -0
- package/src/styles/prism-custom.css +206 -0
- package/src/styles/prism-custom.css.map +1 -0
- package/src/styles/prism-custom.scss +205 -0
- package/src/styles/tokens.css +4416 -0
- package/src/styles/tokens.css.map +1 -0
- package/src/styles/tokens.scss +1456 -0
- package/src/ui/Accordion/Accordion.jsx +70 -0
- package/src/ui/Accordion/Accordion.scss +82 -0
- package/src/ui/Accordion/index.js +1 -0
- package/src/ui/ActionMenu/ActionMenu.jsx +383 -0
- package/src/ui/ActionMenu/ActionMenu.scss +198 -0
- package/src/ui/ActionMenu/index.js +1 -0
- package/src/ui/Avatar/Avatar.jsx +49 -0
- package/src/ui/Avatar/Avatar.scss +82 -0
- package/src/ui/Avatar/index.js +1 -0
- package/src/ui/Badge/Badge.jsx +64 -0
- package/src/ui/Badge/Badge.scss +84 -0
- package/src/ui/Badge/index.js +1 -0
- package/src/ui/Branding/Branding.jsx +65 -0
- package/src/ui/Branding/Branding.scss +116 -0
- package/src/ui/Branding/index.js +1 -0
- package/src/ui/Breadcrumb/Breadcrumb.jsx +162 -0
- package/src/ui/Breadcrumb/Breadcrumb.scss +46 -0
- package/src/ui/Breadcrumb/index.js +2 -0
- package/src/ui/Button/Button.figma.tsx +49 -0
- package/src/ui/Button/Button.jsx +135 -0
- package/src/ui/Button/Button.scss +188 -0
- package/src/ui/Button/index.js +1 -0
- package/src/ui/Card/Card.jsx +25 -0
- package/src/ui/Card/Card.scss +47 -0
- package/src/ui/Card/index.js +1 -0
- package/src/ui/Checkbox/Checkbox.jsx +70 -0
- package/src/ui/Checkbox/Checkbox.scss +96 -0
- package/src/ui/Checkbox/index.js +1 -0
- package/src/ui/Chip/Chip.jsx +104 -0
- package/src/ui/Chip/Chip.scss +118 -0
- package/src/ui/Chip/index.js +1 -0
- package/src/ui/CopyButton/CopyButton.jsx +102 -0
- package/src/ui/CopyButton/CopyButton.scss +56 -0
- package/src/ui/CopyButton/index.js +1 -0
- package/src/ui/Datepicker/Datepicker.jsx +326 -0
- package/src/ui/Datepicker/Datepicker.scss +187 -0
- package/src/ui/Datepicker/index.js +2 -0
- package/src/ui/Divider/Divider.jsx +89 -0
- package/src/ui/Divider/Divider.scss +112 -0
- package/src/ui/Divider/index.js +1 -0
- package/src/ui/DotStatus/DotStatus.jsx +64 -0
- package/src/ui/DotStatus/DotStatus.scss +87 -0
- package/src/ui/DotStatus/index.js +1 -0
- package/src/ui/Dropdown/Dropdown.jsx +200 -0
- package/src/ui/Dropdown/Dropdown.scss +156 -0
- package/src/ui/Dropdown/index.js +1 -0
- package/src/ui/Field/Field.jsx +89 -0
- package/src/ui/Field/Field.scss +119 -0
- package/src/ui/Field/index.js +1 -0
- package/src/ui/FileUpload/FileUpload.figma.tsx +28 -0
- package/src/ui/FileUpload/FileUpload.jsx +153 -0
- package/src/ui/FileUpload/FileUpload.scss +78 -0
- package/src/ui/FileUpload/index.js +2 -0
- package/src/ui/Flex/Flex.jsx +42 -0
- package/src/ui/Flex/Flex.scss +119 -0
- package/src/ui/Flex/index.js +1 -0
- package/src/ui/Icon/Icon.figma.tsx +22 -0
- package/src/ui/Icon/Icon.jsx +47 -0
- package/src/ui/Icon/index.js +1 -0
- package/src/ui/ImageAspect/ImageAspect.jsx +56 -0
- package/src/ui/ImageAspect/ImageAspect.scss +62 -0
- package/src/ui/ImageAspect/index.js +1 -0
- package/src/ui/Input/Input.figma.tsx +35 -0
- package/src/ui/Input/Input.jsx +68 -0
- package/src/ui/Input/Input.scss +64 -0
- package/src/ui/Input/index.js +2 -0
- package/src/ui/Key/Key.jsx +37 -0
- package/src/ui/Key/Key.scss +34 -0
- package/src/ui/Key/index.js +1 -0
- package/src/ui/Menu/Menu.jsx +389 -0
- package/src/ui/Menu/Menu.scss +382 -0
- package/src/ui/Menu/index.js +1 -0
- package/src/ui/MicroCalendar/MicroCalendar.jsx +392 -0
- package/src/ui/MicroCalendar/MicroCalendar.scss +277 -0
- package/src/ui/MicroCalendar/index.js +1 -0
- package/src/ui/Pagination/Pagination.jsx +237 -0
- package/src/ui/Pagination/Pagination.scss +182 -0
- package/src/ui/Pagination/index.js +1 -0
- package/src/ui/PillToggle/PillToggle.jsx +56 -0
- package/src/ui/PillToggle/PillToggle.scss +84 -0
- package/src/ui/PillToggle/index.js +1 -0
- package/src/ui/Playground/Playground.jsx +524 -0
- package/src/ui/Playground/Playground.scss +310 -0
- package/src/ui/Playground/index.js +2 -0
- package/src/ui/ProgressCircle/ProgressCircle.jsx +147 -0
- package/src/ui/ProgressCircle/ProgressCircle.scss +143 -0
- package/src/ui/ProgressCircle/index.js +1 -0
- package/src/ui/ProgressIndicator/ProgressIndicator.jsx +92 -0
- package/src/ui/ProgressIndicator/ProgressIndicator.scss +133 -0
- package/src/ui/ProgressIndicator/index.js +1 -0
- package/src/ui/Radio/Radio.jsx +57 -0
- package/src/ui/Radio/Radio.scss +84 -0
- package/src/ui/Radio/index.js +1 -0
- package/src/ui/Slider/Slider.jsx +283 -0
- package/src/ui/Slider/Slider.scss +156 -0
- package/src/ui/Slider/index.js +1 -0
- package/src/ui/Status/Status.jsx +66 -0
- package/src/ui/Status/Status.scss +90 -0
- package/src/ui/Status/index.js +1 -0
- package/src/ui/Steps/Steps.jsx +201 -0
- package/src/ui/Steps/Steps.scss +240 -0
- package/src/ui/Steps/index.js +1 -0
- package/src/ui/Table/Table.jsx +143 -0
- package/src/ui/Table/Table.scss +90 -0
- package/src/ui/Table/index.js +1 -0
- package/src/ui/Tabs/TabItem.jsx +86 -0
- package/src/ui/Tabs/Tabs.figma.tsx +30 -0
- package/src/ui/Tabs/Tabs.jsx +318 -0
- package/src/ui/Tabs/Tabs.scss +164 -0
- package/src/ui/Tabs/Untitled +1 -0
- package/src/ui/Tabs/index.js +3 -0
- package/src/ui/Tag/Tag.figma.tsx +29 -0
- package/src/ui/Tag/Tag.jsx +93 -0
- package/src/ui/Tag/Tag.scss +229 -0
- package/src/ui/Tag/index.js +2 -0
- package/src/ui/Textarea/Textarea.figma.tsx +35 -0
- package/src/ui/Textarea/Textarea.jsx +68 -0
- package/src/ui/Textarea/Textarea.scss +69 -0
- package/src/ui/Textarea/index.js +2 -0
- package/src/ui/Toast/Toast.jsx +75 -0
- package/src/ui/Toast/Toast.scss +132 -0
- package/src/ui/Toast/index.js +2 -0
- package/src/ui/Toggle/Toggle.jsx +73 -0
- package/src/ui/Toggle/Toggle.scss +139 -0
- package/src/ui/Toggle/index.js +1 -0
- package/src/ui/Tooltip/Tooltip.figma.tsx +24 -0
- package/src/ui/Tooltip/Tooltip.jsx +123 -0
- package/src/ui/Tooltip/Tooltip.scss +80 -0
- package/src/ui/Tooltip/index.js +2 -0
- package/src/ui/index.js +63 -0
- package/src/utils/formatDate.js +27 -0
- package/src/utils/headerVariants.js +69 -0
- package/vite.config.lib.js +55 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
|
+
import Field from "../ui/Field/Field";
|
|
4
|
+
import Flex from "../ui/Flex/Flex";
|
|
5
|
+
import Breadcrumb from "../ui/Breadcrumb/Breadcrumb";
|
|
6
|
+
import Divider from "../ui/Divider/Divider";
|
|
7
|
+
import { formatLastUpdated } from "../utils/formatDate";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Field Component Demo & Documentation
|
|
11
|
+
*
|
|
12
|
+
* This page demonstrates the Field component and its various configurations.
|
|
13
|
+
*
|
|
14
|
+
* ## Field Component Props:
|
|
15
|
+
*
|
|
16
|
+
* ### Optional Props:
|
|
17
|
+
* - `label` (string): Label text for the field
|
|
18
|
+
* - `required` (boolean): Whether the field is required (adds asterisk to label)
|
|
19
|
+
* - `helperMessage` (string): Helper text displayed below the input
|
|
20
|
+
* - `infoIcon` (string): Icon name for info icon (e.g., "Info")
|
|
21
|
+
* - `onInfoClick` (function): Callback when info icon is clicked
|
|
22
|
+
* - `maxLength` (number): Maximum character length (enables character count)
|
|
23
|
+
* - `value` (number|string): Current value (for character count calculation)
|
|
24
|
+
* - `id` (string): Unique identifier for the field
|
|
25
|
+
* - `children` (React.ReactNode): The input element to wrap
|
|
26
|
+
*
|
|
27
|
+
* ## Usage Examples:
|
|
28
|
+
*
|
|
29
|
+
* Basic field:
|
|
30
|
+
* ```jsx
|
|
31
|
+
* <Field label="Name">
|
|
32
|
+
* <input type="text" />
|
|
33
|
+
* </Field>
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Field with helper message:
|
|
37
|
+
* ```jsx
|
|
38
|
+
* <Field label="Email" helperMessage="We'll never share your email">
|
|
39
|
+
* <input type="email" />
|
|
40
|
+
* </Field>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Field with character count:
|
|
44
|
+
* ```jsx
|
|
45
|
+
* <Field label="Description" maxLength={100} value={description}>
|
|
46
|
+
* <textarea />
|
|
47
|
+
* </Field>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
export default function FieldDemo() {
|
|
52
|
+
const [basicValue, setBasicValue] = useState("");
|
|
53
|
+
const [requiredValue, setRequiredValue] = useState("");
|
|
54
|
+
const [helperValue, setHelperValue] = useState("");
|
|
55
|
+
const [countValue, setCountValue] = useState("");
|
|
56
|
+
const [allFeaturesValue, setAllFeaturesValue] = useState("");
|
|
57
|
+
const [requiredAllValue, setRequiredAllValue] = useState("");
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<section className="page">
|
|
61
|
+
<header className="page__header">
|
|
62
|
+
<div className="page__header-container">
|
|
63
|
+
<Breadcrumb />
|
|
64
|
+
<div className="page__header-info">
|
|
65
|
+
<div className="page__header-content">
|
|
66
|
+
<h1 className="page__header-title">Field</h1>
|
|
67
|
+
<p className="page__header-description">
|
|
68
|
+
The Field component provides a consistent wrapper for form inputs with
|
|
69
|
+
labels, helper messages, character counts, and optional info icons. It
|
|
70
|
+
can wrap any input type (text, email, textarea, etc.) and provides a
|
|
71
|
+
unified styling and behavior pattern.
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="page__header-metadata">
|
|
75
|
+
<div className="page__metadata-row">
|
|
76
|
+
<p className="page__metadata-label">Author</p>
|
|
77
|
+
<a
|
|
78
|
+
href="https://chgit.slack.com/team/U06V9C0K06S"
|
|
79
|
+
className="page__metadata-link"
|
|
80
|
+
target="_blank"
|
|
81
|
+
rel="noopener noreferrer"
|
|
82
|
+
>
|
|
83
|
+
@Michael-Stevens
|
|
84
|
+
</a>
|
|
85
|
+
</div>
|
|
86
|
+
<div className="page__metadata-row">
|
|
87
|
+
<p className="page__metadata-label">Last updated</p>
|
|
88
|
+
<p className="page__metadata-value">{formatLastUpdated()}</p>
|
|
89
|
+
</div>
|
|
90
|
+
<div className="page__metadata-row">
|
|
91
|
+
<p className="page__metadata-label">Version</p>
|
|
92
|
+
<Flex direction="row" gap="8" alignItems="center">
|
|
93
|
+
<p className="page__metadata-value">1.0.0</p>
|
|
94
|
+
<span className="page__version-badge">BETA</span>
|
|
95
|
+
</Flex>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</header>
|
|
101
|
+
<main className="page__content">
|
|
102
|
+
<div className="page__examples-section">
|
|
103
|
+
<div className="demo-group">
|
|
104
|
+
<h2 className="demo-group__heading">Basic Usage</h2>
|
|
105
|
+
<p className="demo-group__description">
|
|
106
|
+
A simple field wrapper with a label. The Field component wraps any input element and provides consistent styling and structure.
|
|
107
|
+
</p>
|
|
108
|
+
<div className="demo-content">
|
|
109
|
+
<Field label="Name">
|
|
110
|
+
<input
|
|
111
|
+
type="text"
|
|
112
|
+
value={basicValue}
|
|
113
|
+
onChange={(e) => setBasicValue(e.target.value)}
|
|
114
|
+
placeholder="Enter your name"
|
|
115
|
+
/>
|
|
116
|
+
</Field>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div className="demo-group">
|
|
121
|
+
<h2 className="demo-group__heading">Required Field</h2>
|
|
122
|
+
<p className="demo-group__description">
|
|
123
|
+
Required fields display an asterisk (*) next to the label to indicate that the field must be filled out.
|
|
124
|
+
</p>
|
|
125
|
+
<div className="demo-content">
|
|
126
|
+
<Field label="Email" required>
|
|
127
|
+
<input
|
|
128
|
+
type="email"
|
|
129
|
+
value={requiredValue}
|
|
130
|
+
onChange={(e) => setRequiredValue(e.target.value)}
|
|
131
|
+
placeholder="Enter your email"
|
|
132
|
+
/>
|
|
133
|
+
</Field>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div className="demo-group">
|
|
138
|
+
<h2 className="demo-group__heading">With Helper Message</h2>
|
|
139
|
+
<p className="demo-group__description">
|
|
140
|
+
Helper messages provide additional context or instructions to help users understand what to enter in the field.
|
|
141
|
+
</p>
|
|
142
|
+
<div className="demo-content">
|
|
143
|
+
<Field
|
|
144
|
+
label="Password"
|
|
145
|
+
helperMessage="Must be at least 8 characters long"
|
|
146
|
+
required
|
|
147
|
+
>
|
|
148
|
+
<input
|
|
149
|
+
type="password"
|
|
150
|
+
value={helperValue}
|
|
151
|
+
onChange={(e) => setHelperValue(e.target.value)}
|
|
152
|
+
placeholder="Enter your password"
|
|
153
|
+
/>
|
|
154
|
+
</Field>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div className="demo-group">
|
|
159
|
+
<h2 className="demo-group__heading">With Character Count</h2>
|
|
160
|
+
<p className="demo-group__description">
|
|
161
|
+
Fields can display a character count when a maxLength is specified. This is useful for textareas or inputs with character limits.
|
|
162
|
+
</p>
|
|
163
|
+
<div className="demo-content">
|
|
164
|
+
<Field
|
|
165
|
+
label="Description"
|
|
166
|
+
maxLength={100}
|
|
167
|
+
value={countValue}
|
|
168
|
+
helperMessage="Brief description of your item"
|
|
169
|
+
>
|
|
170
|
+
<textarea
|
|
171
|
+
value={countValue}
|
|
172
|
+
onChange={(e) => setCountValue(e.target.value)}
|
|
173
|
+
placeholder="Enter a description"
|
|
174
|
+
rows={4}
|
|
175
|
+
/>
|
|
176
|
+
</Field>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div className="demo-group">
|
|
181
|
+
<h2 className="demo-group__heading">With Info Icon</h2>
|
|
182
|
+
<p className="demo-group__description">
|
|
183
|
+
Fields can include an info icon that can trigger additional information or tooltips when clicked.
|
|
184
|
+
</p>
|
|
185
|
+
<div className="demo-content">
|
|
186
|
+
<Field
|
|
187
|
+
label="Account Number"
|
|
188
|
+
infoIcon="Info"
|
|
189
|
+
onInfoClick={() => alert("Account numbers are 8-12 digits long")}
|
|
190
|
+
helperMessage="Your unique account identifier"
|
|
191
|
+
>
|
|
192
|
+
<input
|
|
193
|
+
type="text"
|
|
194
|
+
value={allFeaturesValue}
|
|
195
|
+
onChange={(e) => setAllFeaturesValue(e.target.value)}
|
|
196
|
+
placeholder="Enter account number"
|
|
197
|
+
/>
|
|
198
|
+
</Field>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div className="demo-group">
|
|
203
|
+
<h2 className="demo-group__heading">All Features Combined</h2>
|
|
204
|
+
<p className="demo-group__description">
|
|
205
|
+
A field with all available features: label, required indicator, helper message, info icon, and character count.
|
|
206
|
+
</p>
|
|
207
|
+
<div className="demo-content">
|
|
208
|
+
<Field
|
|
209
|
+
label="Product Description"
|
|
210
|
+
required
|
|
211
|
+
helperMessage="Provide a detailed description of your product"
|
|
212
|
+
infoIcon="Info"
|
|
213
|
+
onInfoClick={() => alert("Include key features and benefits")}
|
|
214
|
+
maxLength={200}
|
|
215
|
+
value={requiredAllValue}
|
|
216
|
+
>
|
|
217
|
+
<textarea
|
|
218
|
+
value={requiredAllValue}
|
|
219
|
+
onChange={(e) => setRequiredAllValue(e.target.value)}
|
|
220
|
+
placeholder="Enter product description"
|
|
221
|
+
rows={5}
|
|
222
|
+
/>
|
|
223
|
+
</Field>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<Divider variant="solid" />
|
|
229
|
+
|
|
230
|
+
<div className="page__tabs-content-container">
|
|
231
|
+
<div className="demo-group">
|
|
232
|
+
<div className="page__navigation">
|
|
233
|
+
<Link
|
|
234
|
+
to="/dropdown"
|
|
235
|
+
className="page__nav-link page__nav-link--prev"
|
|
236
|
+
>
|
|
237
|
+
<span className="page__nav-label">Previous</span>
|
|
238
|
+
<span className="page__nav-title">Dropdown</span>
|
|
239
|
+
</Link>
|
|
240
|
+
<Link
|
|
241
|
+
to="/file-upload"
|
|
242
|
+
className="page__nav-link page__nav-link--next"
|
|
243
|
+
>
|
|
244
|
+
<span className="page__nav-label">Next</span>
|
|
245
|
+
<span className="page__nav-title">File Upload</span>
|
|
246
|
+
</Link>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</main>
|
|
251
|
+
</section>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from "react";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
|
+
import Divider from "../ui/Divider/Divider";
|
|
4
|
+
import Breadcrumb from "../ui/Breadcrumb/Breadcrumb";
|
|
5
|
+
import { formatLastUpdated } from "../utils/formatDate";
|
|
6
|
+
import Flex from "../ui/Flex/Flex";
|
|
7
|
+
import "./FigmaVariablesDemo.scss";
|
|
8
|
+
|
|
9
|
+
function FigmaVariablesDemo() {
|
|
10
|
+
const [data, setData] = useState(null);
|
|
11
|
+
const [loading, setLoading] = useState(true);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [selectedCollection, setSelectedCollection] = useState("all");
|
|
14
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const loadData = async () => {
|
|
19
|
+
try {
|
|
20
|
+
// Try to load from the data file
|
|
21
|
+
const response = await fetch("/data/figma-variables.json");
|
|
22
|
+
|
|
23
|
+
// Check if response is actually JSON
|
|
24
|
+
const contentType = response.headers.get("content-type");
|
|
25
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"Data file not found. Please run: npm run fetch:variables"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`Failed to load data: ${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const jsonData = await response.json();
|
|
36
|
+
setData(jsonData);
|
|
37
|
+
setLoading(false);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error("Error loading Figma variables:", err);
|
|
40
|
+
// Check if it's a JSON parse error (likely HTML 404 page)
|
|
41
|
+
if (err.message.includes("JSON") || err.message.includes("<!DOCTYPE")) {
|
|
42
|
+
setError("Data file not found. Please run the fetch script first.");
|
|
43
|
+
} else {
|
|
44
|
+
setError(err.message);
|
|
45
|
+
}
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
loadData();
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const filteredVariables = useMemo(() => {
|
|
54
|
+
if (!data || !data.variables) return [];
|
|
55
|
+
|
|
56
|
+
// Only include variables that start with "uds/"
|
|
57
|
+
let filtered = data.variables.filter((v) => {
|
|
58
|
+
const name = v.name || "";
|
|
59
|
+
return name.toLowerCase().startsWith("uds/");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Filter by collection
|
|
63
|
+
if (selectedCollection !== "all") {
|
|
64
|
+
// Try matching by collectionId first
|
|
65
|
+
let collectionMatch = filtered.filter(
|
|
66
|
+
(v) => v.collectionId === selectedCollection,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// If no matches by ID, try matching by collection name (for inferred collections)
|
|
70
|
+
if (collectionMatch.length === 0) {
|
|
71
|
+
const selectedCollectionObj = data.collections.find(
|
|
72
|
+
(c) => c.id === selectedCollection
|
|
73
|
+
);
|
|
74
|
+
if (selectedCollectionObj) {
|
|
75
|
+
const collectionName = selectedCollectionObj.name;
|
|
76
|
+
// Match variables by prefix (e.g., "uds/icon/primary" matches collection "uds")
|
|
77
|
+
collectionMatch = filtered.filter((v) => {
|
|
78
|
+
const name = v.name || "";
|
|
79
|
+
const parts = name.split("/");
|
|
80
|
+
return parts.length > 0 && parts[0] === collectionName;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
filtered = collectionMatch;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Filter by search term
|
|
89
|
+
if (searchTerm) {
|
|
90
|
+
const term = searchTerm.toLowerCase();
|
|
91
|
+
filtered = filtered.filter(
|
|
92
|
+
(v) =>
|
|
93
|
+
v.name.toLowerCase().includes(term) ||
|
|
94
|
+
v.type.toLowerCase().includes(term) ||
|
|
95
|
+
(v.description && v.description.toLowerCase().includes(term)),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return filtered;
|
|
100
|
+
}, [data, selectedCollection, searchTerm]);
|
|
101
|
+
|
|
102
|
+
// Group variables by their path prefix (e.g., "uds/icon", "uds/color/primary")
|
|
103
|
+
const groupedVariables = useMemo(() => {
|
|
104
|
+
if (!filteredVariables || filteredVariables.length === 0) return {};
|
|
105
|
+
|
|
106
|
+
const groups = {};
|
|
107
|
+
|
|
108
|
+
filteredVariables.forEach((variable) => {
|
|
109
|
+
const name = variable.name || "";
|
|
110
|
+
const parts = name.split("/").filter(Boolean);
|
|
111
|
+
|
|
112
|
+
if (parts.length === 0) {
|
|
113
|
+
// Variables without a path go to "root"
|
|
114
|
+
if (!groups["root"]) {
|
|
115
|
+
groups["root"] = [];
|
|
116
|
+
}
|
|
117
|
+
groups["root"].push(variable);
|
|
118
|
+
} else if (parts.length === 1) {
|
|
119
|
+
// Single segment - group by that segment
|
|
120
|
+
const groupKey = parts[0];
|
|
121
|
+
if (!groups[groupKey]) {
|
|
122
|
+
groups[groupKey] = [];
|
|
123
|
+
}
|
|
124
|
+
groups[groupKey].push(variable);
|
|
125
|
+
} else {
|
|
126
|
+
// Multiple segments - group by all but the last segment
|
|
127
|
+
// e.g., "uds/icon/primary" -> group "uds/icon"
|
|
128
|
+
// e.g., "uds/color/primary/500" -> group "uds/color/primary"
|
|
129
|
+
const groupKey = parts.slice(0, -1).join("/");
|
|
130
|
+
if (!groups[groupKey]) {
|
|
131
|
+
groups[groupKey] = [];
|
|
132
|
+
}
|
|
133
|
+
groups[groupKey].push(variable);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Sort groups by name for consistent display
|
|
138
|
+
const sortedGroups = {};
|
|
139
|
+
Object.keys(groups)
|
|
140
|
+
.sort((a, b) => {
|
|
141
|
+
// Sort "root" to the end
|
|
142
|
+
if (a === "root") return 1;
|
|
143
|
+
if (b === "root") return -1;
|
|
144
|
+
return a.localeCompare(b);
|
|
145
|
+
})
|
|
146
|
+
.forEach((key) => {
|
|
147
|
+
sortedGroups[key] = groups[key];
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return sortedGroups;
|
|
151
|
+
}, [filteredVariables]);
|
|
152
|
+
|
|
153
|
+
const isColorValue = (value) => {
|
|
154
|
+
if (typeof value !== "string") return false;
|
|
155
|
+
return (
|
|
156
|
+
/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/i.test(value.trim()) ||
|
|
157
|
+
/^rgba?\(/.test(value.trim())
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const getColorValue = (value) => {
|
|
162
|
+
if (typeof value !== "string") return null;
|
|
163
|
+
if (isColorValue(value)) {
|
|
164
|
+
return value.trim();
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const VariableValue = ({ value, type, variableName }) => {
|
|
170
|
+
const colorValue = getColorValue(value);
|
|
171
|
+
|
|
172
|
+
if (colorValue) {
|
|
173
|
+
return (
|
|
174
|
+
<div className="variable-value variable-value--color">
|
|
175
|
+
<span
|
|
176
|
+
className="variable-value__swatch"
|
|
177
|
+
style={{ backgroundColor: colorValue }}
|
|
178
|
+
/>
|
|
179
|
+
<span className="variable-value__text">{value}</span>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (type === "BOOLEAN") {
|
|
185
|
+
return (
|
|
186
|
+
<span className="variable-value variable-value--boolean">
|
|
187
|
+
{value ? "true" : "false"}
|
|
188
|
+
</span>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (type === "NUMBER" || type === "FLOAT") {
|
|
193
|
+
// Add px units for spacing, sizing, blur, border-width, gap, and radius variables
|
|
194
|
+
let displayValue = value;
|
|
195
|
+
if (variableName && typeof value === "number") {
|
|
196
|
+
const varLower = variableName.toLowerCase();
|
|
197
|
+
// Exclude typography-related variables
|
|
198
|
+
const isTypography = ['font', 'line', 'letter', 'typography', 'lhu', 'paragraph', 'heading', 'body', 'display', 'label'].some(term => varLower.includes(term));
|
|
199
|
+
// Check for border-width (can be border/width or border-width)
|
|
200
|
+
const hasBorderWidth = (varLower.includes('border') && varLower.includes('width')) || varLower.includes('border-width');
|
|
201
|
+
// Include variables that need px units
|
|
202
|
+
const needsPx = ['spacing', 'sizing', 'blur', 'gap', 'radius'].some(term => varLower.includes(term)) || hasBorderWidth;
|
|
203
|
+
|
|
204
|
+
if (!isTypography && needsPx) {
|
|
205
|
+
displayValue = `${value}px`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return (
|
|
209
|
+
<span className="variable-value variable-value--number">{displayValue}</span>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return <span className="variable-value variable-value--text">{value}</span>;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (loading) {
|
|
217
|
+
return (
|
|
218
|
+
<div className="figma-variables-demo">
|
|
219
|
+
<div className="figma-variables-demo__loading">Loading variables...</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (error || !data) {
|
|
225
|
+
return (
|
|
226
|
+
<div>
|
|
227
|
+
<header className="page__header">
|
|
228
|
+
<div className="page__header-container">
|
|
229
|
+
<Breadcrumb />
|
|
230
|
+
<div className="page__header-info">
|
|
231
|
+
<div className="page__header-content">
|
|
232
|
+
<h1 className="page__header-title">Figma Variables</h1>
|
|
233
|
+
<p className="page__header-description">
|
|
234
|
+
Published design variables from Figma, fetched via API
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</header>
|
|
240
|
+
<main className="page__content">
|
|
241
|
+
<div className="figma-variables-demo">
|
|
242
|
+
<div className="figma-variables-demo__error">
|
|
243
|
+
<h2>No Data Available</h2>
|
|
244
|
+
<p>{error || "Failed to load variables."}</p>
|
|
245
|
+
<br />
|
|
246
|
+
<div className="figma-variables-demo__instructions">
|
|
247
|
+
<h3>To fetch variables from Figma:</h3>
|
|
248
|
+
<ol>
|
|
249
|
+
<li>
|
|
250
|
+
Make sure you have a <code>FIGMA_ACCESS_TOKEN</code> in your <code>.env</code> file
|
|
251
|
+
</li>
|
|
252
|
+
<li>
|
|
253
|
+
Run the fetch script:
|
|
254
|
+
<pre>
|
|
255
|
+
<code>npm run fetch:variables</code>
|
|
256
|
+
</pre>
|
|
257
|
+
Or:
|
|
258
|
+
<pre>
|
|
259
|
+
<code>node scripts/fetch-figma-variables.js</code>
|
|
260
|
+
</pre>
|
|
261
|
+
</li>
|
|
262
|
+
<li>Refresh this page to see the variables</li>
|
|
263
|
+
</ol>
|
|
264
|
+
<p>
|
|
265
|
+
<strong>Note:</strong> The script will fetch variables from the Figma file and save them to{" "}
|
|
266
|
+
<code>public/data/figma-variables.json</code>
|
|
267
|
+
</p>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</main>
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div>
|
|
278
|
+
<header className="page__header">
|
|
279
|
+
<div className="page__header-container">
|
|
280
|
+
<Breadcrumb />
|
|
281
|
+
<div className="page__header-info">
|
|
282
|
+
<div className="page__header-content">
|
|
283
|
+
<h1 className="page__header-title">Figma Variables</h1>
|
|
284
|
+
<p className="page__header-description">
|
|
285
|
+
Published design variables from Figma, fetched via API
|
|
286
|
+
</p>
|
|
287
|
+
</div>
|
|
288
|
+
<div className="page__header-metadata">
|
|
289
|
+
<div className="page__metadata-row">
|
|
290
|
+
<p className="page__metadata-label">File Key</p>
|
|
291
|
+
<p className="page__metadata-value">{data.fileKey}</p>
|
|
292
|
+
</div>
|
|
293
|
+
<div className="page__metadata-row">
|
|
294
|
+
<p className="page__metadata-label">Last fetched</p>
|
|
295
|
+
<p className="page__metadata-value">
|
|
296
|
+
{new Date(data.fetchedAt).toLocaleString()}
|
|
297
|
+
</p>
|
|
298
|
+
</div>
|
|
299
|
+
<div className="page__metadata-row">
|
|
300
|
+
<p className="page__metadata-label">Total Variables</p>
|
|
301
|
+
<p className="page__metadata-value">{data.stats.total}</p>
|
|
302
|
+
</div>
|
|
303
|
+
<div className="page__metadata-row">
|
|
304
|
+
<p className="page__metadata-label">Collections</p>
|
|
305
|
+
<p className="page__metadata-value">{data.stats.collections}</p>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</header>
|
|
311
|
+
<main className="page__content">
|
|
312
|
+
<div className="page__tabs-content-container">
|
|
313
|
+
<div className="figma-variables-demo__filters">
|
|
314
|
+
<div className="figma-variables-demo__filter-group">
|
|
315
|
+
<label htmlFor="collection-filter" className="figma-variables-demo__filter-label">
|
|
316
|
+
Collection:
|
|
317
|
+
</label>
|
|
318
|
+
<select
|
|
319
|
+
id="collection-filter"
|
|
320
|
+
className="figma-variables-demo__filter-select"
|
|
321
|
+
value={selectedCollection}
|
|
322
|
+
onChange={(e) => setSelectedCollection(e.target.value)}
|
|
323
|
+
>
|
|
324
|
+
<option value="all">All Collections</option>
|
|
325
|
+
{data.collections && data.collections.length > 0 && data.collections.map((collection) => (
|
|
326
|
+
<option key={collection.id} value={collection.id}>
|
|
327
|
+
{collection.name} ({collection.variableCount || 0})
|
|
328
|
+
</option>
|
|
329
|
+
))}
|
|
330
|
+
</select>
|
|
331
|
+
</div>
|
|
332
|
+
<div className="figma-variables-demo__filter-group">
|
|
333
|
+
<label htmlFor="search-filter" className="figma-variables-demo__filter-label">
|
|
334
|
+
Search:
|
|
335
|
+
</label>
|
|
336
|
+
<input
|
|
337
|
+
id="search-filter"
|
|
338
|
+
type="text"
|
|
339
|
+
className="figma-variables-demo__filter-input"
|
|
340
|
+
placeholder="Search by name, type, or description..."
|
|
341
|
+
value={searchTerm}
|
|
342
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
343
|
+
/>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<div className="figma-variables-demo__stats">
|
|
348
|
+
<p>
|
|
349
|
+
Showing {filteredVariables.length} of {data.stats.total} variables
|
|
350
|
+
</p>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
{Object.keys(groupedVariables).length === 0 ? (
|
|
354
|
+
<div className="figma-variables-demo__table-wrapper">
|
|
355
|
+
<div className="figma-variables-table__empty">
|
|
356
|
+
No variables found matching your filters.
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
) : (
|
|
360
|
+
Object.entries(groupedVariables).map(([groupName, variables]) => {
|
|
361
|
+
const displayName = groupName === "root" ? "Root Variables" : groupName;
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<div key={groupName} className="figma-variables-group">
|
|
365
|
+
<h3 className="figma-variables-group__title">
|
|
366
|
+
{displayName} <span className="figma-variables-group__count">({variables.length})</span>
|
|
367
|
+
</h3>
|
|
368
|
+
<div className="figma-variables-demo__table-wrapper">
|
|
369
|
+
<table className="figma-variables-table">
|
|
370
|
+
<thead>
|
|
371
|
+
<tr>
|
|
372
|
+
<th className="figma-variables-table__header figma-variables-table__header--name">
|
|
373
|
+
Name
|
|
374
|
+
</th>
|
|
375
|
+
<th className="figma-variables-table__header figma-variables-table__header--mode">
|
|
376
|
+
Light
|
|
377
|
+
</th>
|
|
378
|
+
<th className="figma-variables-table__header figma-variables-table__header--mode">
|
|
379
|
+
Dark
|
|
380
|
+
</th>
|
|
381
|
+
</tr>
|
|
382
|
+
</thead>
|
|
383
|
+
<tbody>
|
|
384
|
+
{variables.map((variable) => {
|
|
385
|
+
const lightMode = variable.modes?.find(
|
|
386
|
+
(m) => m.name.toLowerCase() === "light"
|
|
387
|
+
);
|
|
388
|
+
const darkMode = variable.modes?.find(
|
|
389
|
+
(m) => m.name.toLowerCase() === "dark"
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const lightValue = variable.valuesByModeName?.light ||
|
|
393
|
+
(lightMode ? variable.valuesByMode?.[lightMode.modeId] : null) ||
|
|
394
|
+
variable.value;
|
|
395
|
+
const darkValue = variable.valuesByModeName?.dark ||
|
|
396
|
+
(darkMode ? variable.valuesByMode?.[darkMode.modeId] : null) ||
|
|
397
|
+
variable.value;
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<tr key={variable.id} className="figma-variables-table__row">
|
|
401
|
+
<td className="figma-variables-table__cell figma-variables-table__cell--name">
|
|
402
|
+
<code>{variable.name}</code>
|
|
403
|
+
</td>
|
|
404
|
+
<td className="figma-variables-table__cell figma-variables-table__cell--mode">
|
|
405
|
+
<VariableValue value={lightValue} type={variable.type} variableName={variable.name} />
|
|
406
|
+
</td>
|
|
407
|
+
<td className="figma-variables-table__cell figma-variables-table__cell--mode">
|
|
408
|
+
<VariableValue value={darkValue} type={variable.type} variableName={variable.name} />
|
|
409
|
+
</td>
|
|
410
|
+
</tr>
|
|
411
|
+
);
|
|
412
|
+
})}
|
|
413
|
+
</tbody>
|
|
414
|
+
</table>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
);
|
|
418
|
+
})
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
</main>
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export default FigmaVariablesDemo;
|