├── .github └── workflows │ ├── chromatic.yml │ └── main.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .storybook ├── main.ts ├── preview.ts └── styles.css ├── LICENSE ├── README.md ├── example ├── .npmignore ├── .prettierrc ├── index.html ├── index.scss ├── index.tsx ├── package.json ├── tsconfig.json ├── vite.config.ts └── yarn.lock ├── package.json ├── screenshot.png ├── screenshots ├── VarAngle.png ├── VarButton.png ├── VarCategory.png ├── VarColor.png ├── VarDisplay.png ├── VarFile.png ├── VarImage.png ├── VarMedia.png ├── VarNumber.png ├── VarSelect.png ├── VarSlider.png ├── VarString.png ├── VarToggle.png └── VarXY.png ├── src ├── VarAngle.tsx ├── VarArray.tsx ├── VarBase.tsx ├── VarButton.tsx ├── VarCategory.tsx ├── VarColor.tsx ├── VarDisplay.tsx ├── VarFile.tsx ├── VarImage.tsx ├── VarMedia.tsx ├── VarNumber.tsx ├── VarScope.tsx ├── VarSelect.tsx ├── VarSlider.tsx ├── VarString.tsx ├── VarToggle.tsx ├── VarUI.scss ├── VarUI.tsx ├── VarXY.tsx ├── common │ ├── Number.tsx │ ├── VarUIContext.ts │ └── roundValue.ts ├── icons │ ├── IconDown.tsx │ ├── IconUp.tsx │ └── IconUpload.tsx └── index.tsx ├── stories ├── VarAngle.stories.tsx ├── VarButton.stories.tsx ├── VarCategory.stories.tsx ├── VarColor.stories.tsx ├── VarDisplay.stories.tsx ├── VarFile.stories.tsx ├── VarImage.stories.tsx ├── VarMedia.stories.tsx ├── VarNumber.stories.tsx ├── VarSelect.stories.tsx ├── VarSlider.stories.tsx ├── VarString.stories.tsx ├── VarToggle.stories.tsx └── VarXY.stories.tsx ├── test ├── VarAngle.test.tsx ├── VarArray.test.tsx ├── VarBase.test.tsx ├── VarButton.test.tsx ├── VarCategory.test.tsx ├── VarColor.test.tsx ├── VarDisplay.test.tsx ├── VarFile.test.tsx ├── VarImage.test.tsx ├── VarMedia.test.tsx ├── VarNumber.test.tsx ├── VarScope.test.tsx ├── VarSelect.test.tsx ├── VarSlider.test.tsx ├── VarString.test.tsx ├── VarToggle.test.tsx ├── VarUI.test.tsx ├── common │ ├── VarUIContext.test.tsx │ └── roundValue.test.ts ├── setupTests.ts └── tsconfig.json ├── tsconfig.json ├── vite.config.ts ├── vite.storybook.config.ts ├── vitest.config.ts └── yarn.lock /.github/workflows/chromatic.yml: -------------------------------------------------------------------------------- 1 | # Workflow name 2 | name: 'Chromatic' 3 | 4 | # Event for the workflow 5 | on: push 6 | 7 | # List of jobs 8 | jobs: 9 | chromatic-deployment: 10 | # Operating System 11 | runs-on: ubuntu-latest 12 | # Job steps 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Install dependencies 16 | run: yarn 17 | # 👇 Adds Chromatic as a step in the workflow 18 | - name: Publish to Chromatic 19 | uses: chromaui/action@v1 20 | # Chromatic GitHub Action options 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | # 👇 Chromatic projectToken, refer to the manage page to obtain it. 24 | projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [18.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: yarn install 23 | - run: yarn test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | .parcel-cache 7 | build 8 | storybook-static 9 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | example 3 | test 4 | stories 5 | vi.config.js 6 | tsdx.config.js 7 | yarn-error.log 8 | yarn.lock 9 | .prettierrc 10 | tsconfig.json 11 | .github 12 | storybook 13 | storybook-static 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true, 4 | "semi": true, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite'; 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-links', 7 | '@storybook/addon-essentials', 8 | '@storybook/addon-onboarding', 9 | '@storybook/addon-interactions', 10 | ], 11 | framework: { 12 | name: '@storybook/react-vite', 13 | options: { 14 | builder: { 15 | viteConfigPath: './vite.storybook.config.ts', 16 | }, 17 | }, 18 | }, 19 | docs: { 20 | autodocs: 'tag', 21 | }, 22 | }; 23 | export default config; 24 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import './styles.css'; 2 | import type { Preview } from '@storybook/react'; 3 | 4 | const preview: Preview = { 5 | parameters: { 6 | actions: { argTypesRegex: '^on[A-Z].*' }, 7 | controls: { 8 | matchers: { 9 | color: /(background|color)$/i, 10 | date: /Date$/, 11 | }, 12 | }, 13 | }, 14 | }; 15 | 16 | export default preview; 17 | -------------------------------------------------------------------------------- /.storybook/styles.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #storybook-root { 4 | height: 100%; 5 | } 6 | 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 9 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 10 | } 11 | 12 | #storybook-root { 13 | max-width: 400px; 14 | margin: 0 auto; 15 | background: #11111a; 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2024, Mat Sz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted (subject to the limitations in the disclaimer 6 | below) provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY 20 | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 21 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 23 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 27 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 28 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | Screenshot 5 | 6 |

7 |

8 | 9 |

10 | workflow 11 | 12 | npm 13 | npm 14 | NPM 15 | 16 | 17 | 18 | 19 |

20 | 21 | **react-var-ui** is a simple React component library for variable setting and preview, inspired by iOS settings, [react-dat-gui](https://github.com/claus/react-dat-gui) and [dat.gui](https://github.com/dataarts/dat.gui). 22 | 23 | While some code from react-dat-gui was used, this library functions in a completely different way. The codebase uses modern React code practices such as hooks and functional components. Instead of iterating over the children array, react-var-ui uses a Context. Creation of custom components is also easier. 24 | 25 | What makes react-var-ui different when compared to similar libraries such as [Leva](https://github.com/pmndrs/leva) or [react-dat-gui](https://github.com/claus/react-dat-gui), react-var-ui doesn't force itself to float over all your content, instead react-var-ui lives peacefully inside of the React node it is placed in. Unlike Leva and much more like react-dat-gui, react-var-ui relies on a local state variable, providing the developer with more flexibility. While this might seem less convenient, it allows for more customization and usage of multiple instances of react-dat-gui within one project. 26 | 27 | ## Table of contents 28 | 29 | - [Installation](#installation) 30 | - [Example usage](#example-usage) 31 | - [Testing](#testing) 32 | - [Utility components](#utility-components) 33 | - [VarUI](#varui-) 34 | - [VarCategory](#varcategory-) 35 | - [VarArray](#vararray-) 36 | - [Input components](#input-components) 37 | - [Base properties](#base-properties) 38 | - [VarAngle](#varangle-) 39 | - [VarBase](#varbase-) 40 | - [VarButton](#varbutton-) 41 | - [VarColor](#varcolor-) 42 | - [VarDisplay](#vardisplay-) 43 | - [VarFile](#varfile-) 44 | - [VarImage](#varimage-) 45 | - [VarMedia](#varmedia-) 46 | - [VarNumber](#varnumber-) 47 | - [VarSelect](#varselect-) 48 | - [VarSlider](#varslider-) 49 | - [VarString](#varstring-) 50 | - [VarToggle](#vartoggle-) 51 | - [VarXY](#varxy-) 52 | - [Theme customization](#theme-customization) 53 | - [Custom input components](#custom-input-components) 54 | 55 | ## Installation 56 | 57 | Install `react-var-ui` with either npm or yarn: 58 | 59 | ``` 60 | yarn add react-var-ui 61 | # or 62 | npm install react-var-ui 63 | ``` 64 | 65 | Then include the CSS with: 66 | 67 | ```css 68 | /* In your CSS/SCSS file: */ 69 | @import 'react-var-ui/index.css'; 70 | ``` 71 | 72 | or: 73 | 74 | ```js 75 | // In your JS/TS file (assuming your bundler supports loading CSS files): 76 | import 'react-var-ui/index.css'; 77 | ``` 78 | 79 | ## Example usage 80 | 81 | ```jsx 82 | const [values, setValues] = React.useState({ 83 | toggle: true, 84 | color: '#FF0000', 85 | select: 1, 86 | slider: 0.4, 87 | xy: [0, 0.2], 88 | string: 'Hello world!', 89 | }); 90 | 91 | return ( 92 | 93 | 94 | 95 | 96 | 104 | 111 | 112 | 113 | alert('clicked!')} /> 114 | 115 | 116 | ); 117 | ``` 118 | 119 | ## Testing 120 | 121 | react-var-ui uses jest for automated unit tests and storybook for manually testing the UI. 122 | 123 | You can run unit tests after installing by running: 124 | 125 | yarn test 126 | 127 | Storybook can be ran with: 128 | 129 | yarn storybook 130 | 131 | To run the example app, you first need to start the project with: 132 | 133 | yarn start 134 | 135 | And then enter the example directory and start the app: 136 | 137 | cd ./example 138 | yarn start 139 | 140 | (make sure to run `yarn install` before.) 141 | 142 | ## Utility components 143 | 144 |

<VarUI />

145 | 146 | This is the main component which provides a Context for other components. It is not required to use this component - other components accept `onChange` and `value` properties which provide a similar functionality. 147 | 148 | #### Required properties 149 | 150 | | Property | Description | Type | 151 | | ------------- | ------------------------------------------------------------------- | ------------------------------------- | 152 | | values | A JavaScript object or array to be mutated by the input components. | object | 153 | | onChange | The function to be called with the entire changed object. | (values: object) => void | 154 | | onChangeValue | The function to be called when one value is changed. | (path: string, newValue: any) => void | 155 | 156 | #### Optional properties 157 | 158 | | Property | Description | Type | 159 | | --------- | ---------------------------------------------- | ------ | 160 | | className | Additional class names for the wrapper object. | string | 161 | 162 |

<VarCategory />

163 | 164 |

165 | VarCategory screenshot 166 |

167 | 168 | Category component for grouping inputs. 169 | 170 | #### Required properties 171 | 172 | | Property | Description | Type | 173 | | -------- | --------------- | --------- | 174 | | label | Category label. | ReactNode | 175 | 176 | #### Optional properties 177 | 178 | | Property | Description | Type | 179 | | ----------- | --------------------------------------------------- | ------- | 180 | | className | Additional class names on the wrapping div element. | string | 181 | | collapsible | Should display the Collapse/Expand button. | boolean | 182 | 183 |

<VarArray />

184 | 185 | Renders an array value as a list of elements. 186 | 187 | #### Optional properties 188 | 189 | | Property | Description | Type | 190 | | --------- | ------------------------------------------------------- | ----------------------------------------------------------------- | 191 | | className | Additional class names on the wrapping div element. | string | 192 | | disabled | Should the component and its children be disabled. | boolean | 193 | | children | Renders children with the array element as its context. | ReactNode \| (element: T, index: number, array: T[]) => ReactNode | 194 | 195 |

<VarScope />

196 | 197 | Creates a context with a certain path as base. 198 | 199 | #### Optional properties 200 | 201 | | Property | Description | Type | 202 | | -------- | ----------------------------- | ------ | 203 | | path | Path to use as the base path. | string | 204 | 205 | #### Examples 206 | 207 | With ReactNode children: 208 | 209 | ```tsx 210 | 211 | 212 | 213 | 214 | 215 | ``` 216 | 217 | With function: 218 | 219 | ```tsx 220 | 221 | 222 | {(element, index) => } 223 | 224 | 225 | ``` 226 | 227 | ## Input components 228 | 229 | ### Base properties 230 | 231 | Most input components accept the following base properties. 232 | 233 | Does not apply to ``. 234 | 235 | #### Optional properties 236 | 237 | _T is component's value type._ 238 | 239 | | Property | Description | Type | 240 | | ------------ | --------------------------------------------------------------------------------------------------------------- | ------------------ | 241 | | label | Label to be shown left to the input. | ReactNode | 242 | | className | Additional class names on the wrapping div element. | string | 243 | | path | Variable path in the data object. | string | 244 | | value | Current value (only used if context and path aren't available).
In most cases you aren't going to need this. | T | 245 | | defaultValue | Default value for components that support resetting (on double click for example). | T | 246 | | disabled | Should the component be disabled. | boolean | 247 | | readOnly | Should the component be read-only. | boolean | 248 | | onChange | On change event, called with the new value if provided.
In most cases you aren't going to need this. | (value: T) => void | 249 | | children | Children. Only rendered when provided directly to the VarBase component. | ReactNode | 250 | 251 |

<VarAngle />

252 | 253 |

254 | VarAngle screenshot 255 |

256 | 257 | Angle picker component. Accepts and provides numbers (radians). 258 | 259 | _T = number_ (rad) 260 | 261 |

<VarBase />

262 | 263 | Base VarUI input component. Doesn't do anything besides displaying the label. 264 | 265 | Used to construct other components from. 266 | 267 |

<VarButton />

268 | 269 |

270 | VarButton screenshot 271 |

272 | 273 | Button component. 274 | 275 | **Does not accept any of the base component properties.** 276 | 277 | #### Required properties 278 | 279 | | Property | Description | Type | 280 | | ----------- | --------------- | --------- | 281 | | buttonLabel | Category label. | ReactNode | 282 | 283 | #### Optional properties 284 | 285 | | Property | Description | Type | 286 | | -------- | ---------------------------------- | ---------- | 287 | | onClick | Called when the button is clicked. | () => void | 288 | | disabled | Should the component be disabled. | boolean | 289 | 290 |

<VarColor />

291 | 292 |

293 | VarColor screenshot 294 |

295 | 296 | Color picker component. Returns and accepts values in form of hex color strings. 297 | 298 | Uses [@uiw/react-color-sketch](https://uiwjs.github.io/react-color/#/sketch) to render the color picker. 299 | 300 | _T = string_ (#XXXXXX) 301 | 302 | #### Optional properties 303 | 304 | | Property | Description | Type | 305 | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | 306 | | alpha | Should allow picking alpha values?
If true, the result hex code will contain extra two characters representing the alpha value, from 00 to FF. | boolean | 307 | 308 |

<VarDisplay />

309 | 310 |

311 | VarDisplay screenshot 312 |

313 | 314 | A simple component that displays a string or a numeric value. 315 | 316 | **Only accepts path and value. Does not change any values.** 317 | 318 | _T = string | number_ 319 | 320 |

<VarFile />

321 | 322 |

323 | VarFile screenshot 324 |

325 | 326 | A simple file input component. Accepts and provides File instances. 327 | 328 | _T = File_ 329 | 330 | #### Optional properties 331 | 332 | | Property | Description | Type | 333 | | --------------- | -------------------------------------------------------------------------- | ------- | 334 | | accept | List of accepted file types. | string | 335 | | displayMetadata | Whether file information should be displayed in the field. (default: true) | boolean | 336 | 337 |

<VarImage />

338 | 339 |

340 | VarImage screenshot 341 |

342 | 343 | A simple image input component. Accepts and provides blob/data URLs. 344 | 345 | _T = string_ 346 | 347 |

<VarMedia />

348 | 349 |

350 | VarMedia screenshot 351 |

352 | 353 | Media (audio/video/image) input component. Accepts and provides a blob URL. 354 | 355 | If acceptImage, acceptAudio and acceptVideo are all false, the component will accept all 3. 356 | 357 | _T = string_ 358 | 359 | #### Optional properties 360 | 361 | | Property | Description | Type | 362 | | ----------- | --------------------------------------------------- | ------- | 363 | | acceptImage | Whether the component should accept image/\* files. | boolean | 364 | | acceptAudio | Whether the component should accept audio/\* files. | boolean | 365 | | acceptVideo | Whether the component should accept video/\* files. | boolean | 366 | 367 |

<VarNumber />

368 | 369 |

370 | VarNumber screenshot 371 |

372 | 373 | Integer/float number component. Accepts and provides numbers. 374 | 375 | _T = number_ 376 | 377 | #### Optional properties 378 | 379 | | Property | Description | Type | 380 | | ----------- | -------------------------------------------------------------------------- | ------- | 381 | | min | Minimum value. | number | 382 | | max | Maximum value. | number | 383 | | step | Step. | number | 384 | | integer | Should the end result be rounded to an integer value. | boolean | 385 | | showInput | If true will display an editable input, otherwise shows a read only value. | boolean | 386 | | showButtons | If true will display buttons that increase and decrease the value by step. | boolean | 387 | 388 |

<VarSelect />

389 | 390 |

391 | VarSelect screenshot 392 |

393 | 394 | Select component. Returns and accepts either `value` from option object or `key` when `value` is not provided. 395 | 396 | _T = any_ 397 | 398 | #### Required properties 399 | 400 | | Property | Description | Type | 401 | | -------- | ------------------------ | ------------------ | 402 | | options | Options to be displayed. | IVarSelectOption[] | 403 | 404 | #### Interface: `IVarSelectOption` 405 | 406 | **Required:** 407 | 408 | | Property | Description | Type | 409 | | -------- | ------------------------------------------------------------------- | --------- | 410 | | key | Key for the option. Also used as value if `value` is not specified. | ReactText | 411 | | label | Option label. | string | 412 | 413 | **Optional:** 414 | 415 | | Property | Description | Type | 416 | | -------- | ------------------------------------------------------------------------------------------------------------------------ | ---- | 417 | | value | Option value. Key will be used if not specified.
**Note: Will be serialized to JSON and deserialized when selected.** | any | 418 | 419 |

<VarSlider />

420 | 421 |

422 | VarSlider screenshot 423 |

424 | 425 | Integer/float slider component. Accepts and provides numbers. 426 | 427 | _T = number_ 428 | 429 | #### Required properties 430 | 431 | | Property | Description | Type | 432 | | -------- | -------------- | ------ | 433 | | min | Minimum value. | number | 434 | | max | Maximum value. | number | 435 | | step | Step. | number | 436 | 437 | #### Optional properties 438 | 439 | | Property | Description | Type | 440 | | ----------- | -------------------------------------------------------------------------- | ------- | 441 | | integer | Should the end result be rounded to an integer value. | boolean | 442 | | showInput | If true will display an editable input, otherwise shows a read only value. | boolean | 443 | | showButtons | If true will display buttons that increase and decrease the value by step. | boolean | 444 | 445 |

<VarString />

446 | 447 |

448 | VarString screenshot 449 |

450 | 451 | String input component. Accepts and provides a string value. 452 | 453 | _T = string_ 454 | 455 | #### Optional properties 456 | 457 | | Property | Description | Type | 458 | | --------- | ------------------------------- | ------- | 459 | | maxLength | Maximum length of the text. | number | 460 | | multiline | Should the field be a textarea? | boolean | 461 | 462 |

<VarToggle />

463 | 464 |

465 | VarToggle screenshot 466 |

467 | 468 | Checkbox/toggle component. Accepts and returns a boolean (true/false). 469 | 470 | _T = boolean_ 471 | 472 |

<VarXY />

473 | 474 |

475 | VarXY screenshot 476 |

477 | 478 | XY offset picker. Accepts and provides an array in form of [x, y]. 479 | 480 | _T = [number (x), number (y)]_ 481 | 482 | #### Optional properties 483 | 484 | | Property | Description | Type | 485 | | -------- | -------------- | ------------------------ | 486 | | min | Minimum value. | [number (x), number (y)] | 487 | | max | Maximum value. | [number (x), number (y)] | 488 | | step | Step. | [number (x), number (y)] | 489 | 490 | ## Theme customization 491 | 492 | The colors can be customized as such (provided are default values): 493 | 494 | ```css 495 | .react-var-ui { 496 | /* Foreground color, used for text. */ 497 | --react-var-ui-foreground-color: #ddd; 498 | 499 | /* Background color, used for category header backgrounds. */ 500 | --react-var-ui-background-color: #11111a; 501 | 502 | /* Accent color, used for active parts of sliders, toggles and XY. */ 503 | --react-var-ui-accent-color: #77f; 504 | 505 | /* Input background color. */ 506 | --react-var-ui-input-background-color: #353542; 507 | 508 | /* Input background color (when hovered). */ 509 | --react-var-ui-input-background-hover-color: #424253; 510 | 511 | /* Input background color (when pressed). Only applies to buttons. */ 512 | --react-var-ui-input-background-pressed-color: #2b2b37; 513 | 514 | /* Label background color. */ 515 | --react-var-ui-label-background-normal-color: #22222a; 516 | 517 | /* Label background color (when hovered). */ 518 | --react-var-ui-label-background-hover-color: #2a2a33; 519 | 520 | /* Label border color. */ 521 | --react-var-ui-label-border-color: #33333a; 522 | } 523 | ``` 524 | 525 | ## Custom input components 526 | 527 | react-var-ui provides a `` component and a `useVarUIValue` hook designed to facilitate creation of custom components. 528 | 529 | ### Example usage 530 | 531 | ```tsx 532 | import React from 'react'; 533 | import { useVarUIValue, IVarBaseInputProps, VarBase } from 'react-var-ui'; 534 | 535 | // Please specify the . 536 | export interface IVarCustomProps extends IVarBaseInputProps {} 537 | 538 | /** 539 | * Custom input component. In this example, it's a simple text input. 540 | */ 541 | export const VarCustom = ({ 542 | label, 543 | path, 544 | value, 545 | onChange, 546 | disabled, 547 | className, 548 | }: IVarCustomProps): JSX.Element => { 549 | /** 550 | * currentValue will contain the current value from the value object 551 | * (at a given path) or value from properties if that's not available. 552 | * 553 | * setCurrentValue will set the value onto a given path in the object 554 | * and call onChange if available. 555 | * 556 | * All arguments are optional, path/object-based value changes take 557 | * precedence. 558 | */ 559 | const [currentValue, setCurrentValue] = useVarUIValue(path, value, onChange); 560 | 561 | /** 562 | * We're wrapping our component in VarBase which provides the default 563 | * label. 564 | * 565 | * It is necessary to wrap what should appear on the right in a . 566 | * If this behavior is undesired, a
with grid-column: 1 / 3; can 567 | * be used. 568 | */ 569 | return ( 570 | 571 | 572 | setCurrentValue(e.target.value)} 577 | /> 578 | 579 | 580 | ); 581 | }; 582 | ``` 583 | -------------------------------------------------------------------------------- /example/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist -------------------------------------------------------------------------------- /example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true, 4 | "semi": true, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Playground 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/index.scss: -------------------------------------------------------------------------------- 1 | @import '../src/VarUI.scss'; 2 | 3 | body { 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 5 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 6 | background: #dddddd; 7 | margin: 0; 8 | } 9 | 10 | h1, 11 | .values { 12 | margin-left: 20px; 13 | } 14 | 15 | .values { 16 | margin-top: 20px; 17 | } 18 | 19 | .example { 20 | display: block; 21 | grid-gap: 20px; 22 | } 23 | 24 | .react-var-ui { 25 | background: #11111a; 26 | } 27 | 28 | @media screen and (min-width: 960px) { 29 | .example { 30 | padding: 0 20px; 31 | display: grid; 32 | grid-template-columns: 400px 1fr; 33 | } 34 | 35 | .values { 36 | margin-top: 0; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import './index.scss'; 5 | 6 | import { 7 | VarUI, 8 | VarColor, 9 | VarToggle, 10 | VarSelect, 11 | VarSlider, 12 | VarXY, 13 | VarCategory, 14 | VarButton, 15 | VarString, 16 | VarAngle, 17 | VarDisplay, 18 | VarNumber, 19 | VarImage, 20 | VarFile, 21 | VarMedia, 22 | } from '../src'; 23 | 24 | const App = () => { 25 | const [values, setValues] = React.useState({ 26 | toggle: true, 27 | color: '#FF0000', 28 | colorAlpha: '#FF0000DD', 29 | select: 'zero', 30 | slider: 0.4, 31 | number: 1, 32 | string: 'test', 33 | angle: 0, 34 | xy: [0, 0], 35 | image: undefined, 36 | file: undefined, 37 | media: undefined, 38 | }); 39 | 40 | return ( 41 |
42 |

VarUI example

43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 59 | 67 | 77 | 85 | 93 | 101 | 102 | 103 | 108 | 109 | 110 | alert('clicked!')} 113 | /> 114 | alert('clicked!')} 118 | /> 119 | 120 | 121 | 122 | 123 | 124 |
125 |
126 | Values: 127 |
128 | {Object.entries(values).map(([key, value]) => ( 129 | 130 |
{key}
131 |
132 | {typeof value === 'boolean' 133 | ? value 134 | ? 'true' 135 | : 'false' 136 | : Array.isArray(value) 137 | ? value.join(', ') 138 | : value?.toString()} 139 |
140 |
141 | ))} 142 |
143 |
144 |
145 |
146 | ); 147 | }; 148 | 149 | ReactDOM.render(, document.getElementById('root')); 150 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "build": "vite build" 9 | }, 10 | "devDependencies": { 11 | "@vitejs/plugin-react": "^4.0.4", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "typescript": "^5.2.2", 15 | "vite": "^4.4.9" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "declaration": true, 7 | "lib": ["ES2020", "DOM"], 8 | "downlevelIteration": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "jsx": "react" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(() => ({ 6 | build: { 7 | outDir: './build', 8 | }, 9 | plugins: [react()], 10 | })); 11 | -------------------------------------------------------------------------------- /example/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@ampproject/remapping@^2.2.0": 6 | version "2.2.1" 7 | resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" 8 | integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== 9 | dependencies: 10 | "@jridgewell/gen-mapping" "^0.3.0" 11 | "@jridgewell/trace-mapping" "^0.3.9" 12 | 13 | "@babel/code-frame@^7.22.13": 14 | version "7.22.13" 15 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" 16 | integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== 17 | dependencies: 18 | "@babel/highlight" "^7.22.13" 19 | chalk "^2.4.2" 20 | 21 | "@babel/compat-data@^7.22.9": 22 | version "7.22.9" 23 | resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" 24 | integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== 25 | 26 | "@babel/core@^7.22.9": 27 | version "7.22.15" 28 | resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.15.tgz#15d4fd03f478a459015a4b94cfbb3bd42c48d2f4" 29 | integrity sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA== 30 | dependencies: 31 | "@ampproject/remapping" "^2.2.0" 32 | "@babel/code-frame" "^7.22.13" 33 | "@babel/generator" "^7.22.15" 34 | "@babel/helper-compilation-targets" "^7.22.15" 35 | "@babel/helper-module-transforms" "^7.22.15" 36 | "@babel/helpers" "^7.22.15" 37 | "@babel/parser" "^7.22.15" 38 | "@babel/template" "^7.22.15" 39 | "@babel/traverse" "^7.22.15" 40 | "@babel/types" "^7.22.15" 41 | convert-source-map "^1.7.0" 42 | debug "^4.1.0" 43 | gensync "^1.0.0-beta.2" 44 | json5 "^2.2.3" 45 | semver "^6.3.1" 46 | 47 | "@babel/generator@^7.22.15": 48 | version "7.22.15" 49 | resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" 50 | integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== 51 | dependencies: 52 | "@babel/types" "^7.22.15" 53 | "@jridgewell/gen-mapping" "^0.3.2" 54 | "@jridgewell/trace-mapping" "^0.3.17" 55 | jsesc "^2.5.1" 56 | 57 | "@babel/helper-compilation-targets@^7.22.15": 58 | version "7.22.15" 59 | resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" 60 | integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== 61 | dependencies: 62 | "@babel/compat-data" "^7.22.9" 63 | "@babel/helper-validator-option" "^7.22.15" 64 | browserslist "^4.21.9" 65 | lru-cache "^5.1.1" 66 | semver "^6.3.1" 67 | 68 | "@babel/helper-environment-visitor@^7.22.5": 69 | version "7.22.5" 70 | resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" 71 | integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== 72 | 73 | "@babel/helper-function-name@^7.22.5": 74 | version "7.22.5" 75 | resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" 76 | integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== 77 | dependencies: 78 | "@babel/template" "^7.22.5" 79 | "@babel/types" "^7.22.5" 80 | 81 | "@babel/helper-hoist-variables@^7.22.5": 82 | version "7.22.5" 83 | resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" 84 | integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== 85 | dependencies: 86 | "@babel/types" "^7.22.5" 87 | 88 | "@babel/helper-module-imports@^7.22.15": 89 | version "7.22.15" 90 | resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" 91 | integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== 92 | dependencies: 93 | "@babel/types" "^7.22.15" 94 | 95 | "@babel/helper-module-transforms@^7.22.15": 96 | version "7.22.15" 97 | resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.15.tgz#40ad2f6950f143900e9c1c72363c0b431a606082" 98 | integrity sha512-l1UiX4UyHSFsYt17iQ3Se5pQQZZHa22zyIXURmvkmLCD4t/aU+dvNWHatKac/D9Vm9UES7nvIqHs4jZqKviUmQ== 99 | dependencies: 100 | "@babel/helper-environment-visitor" "^7.22.5" 101 | "@babel/helper-module-imports" "^7.22.15" 102 | "@babel/helper-simple-access" "^7.22.5" 103 | "@babel/helper-split-export-declaration" "^7.22.6" 104 | "@babel/helper-validator-identifier" "^7.22.15" 105 | 106 | "@babel/helper-plugin-utils@^7.22.5": 107 | version "7.22.5" 108 | resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" 109 | integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== 110 | 111 | "@babel/helper-simple-access@^7.22.5": 112 | version "7.22.5" 113 | resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" 114 | integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== 115 | dependencies: 116 | "@babel/types" "^7.22.5" 117 | 118 | "@babel/helper-split-export-declaration@^7.22.6": 119 | version "7.22.6" 120 | resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" 121 | integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== 122 | dependencies: 123 | "@babel/types" "^7.22.5" 124 | 125 | "@babel/helper-string-parser@^7.22.5": 126 | version "7.22.5" 127 | resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" 128 | integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== 129 | 130 | "@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.5": 131 | version "7.22.15" 132 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" 133 | integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== 134 | 135 | "@babel/helper-validator-option@^7.22.15": 136 | version "7.22.15" 137 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" 138 | integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== 139 | 140 | "@babel/helpers@^7.22.15": 141 | version "7.22.15" 142 | resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" 143 | integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== 144 | dependencies: 145 | "@babel/template" "^7.22.15" 146 | "@babel/traverse" "^7.22.15" 147 | "@babel/types" "^7.22.15" 148 | 149 | "@babel/highlight@^7.22.13": 150 | version "7.22.13" 151 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" 152 | integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== 153 | dependencies: 154 | "@babel/helper-validator-identifier" "^7.22.5" 155 | chalk "^2.4.2" 156 | js-tokens "^4.0.0" 157 | 158 | "@babel/parser@^7.22.15": 159 | version "7.22.16" 160 | resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" 161 | integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== 162 | 163 | "@babel/plugin-transform-react-jsx-self@^7.22.5": 164 | version "7.22.5" 165 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz#ca2fdc11bc20d4d46de01137318b13d04e481d8e" 166 | integrity sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g== 167 | dependencies: 168 | "@babel/helper-plugin-utils" "^7.22.5" 169 | 170 | "@babel/plugin-transform-react-jsx-source@^7.22.5": 171 | version "7.22.5" 172 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz#49af1615bfdf6ed9d3e9e43e425e0b2b65d15b6c" 173 | integrity sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w== 174 | dependencies: 175 | "@babel/helper-plugin-utils" "^7.22.5" 176 | 177 | "@babel/template@^7.22.15", "@babel/template@^7.22.5": 178 | version "7.22.15" 179 | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" 180 | integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== 181 | dependencies: 182 | "@babel/code-frame" "^7.22.13" 183 | "@babel/parser" "^7.22.15" 184 | "@babel/types" "^7.22.15" 185 | 186 | "@babel/traverse@^7.22.15": 187 | version "7.22.15" 188 | resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.15.tgz#75be4d2d6e216e880e93017f4e2389aeb77ef2d9" 189 | integrity sha512-DdHPwvJY0sEeN4xJU5uRLmZjgMMDIvMPniLuYzUVXj/GGzysPl0/fwt44JBkyUIzGJPV8QgHMcQdQ34XFuKTYQ== 190 | dependencies: 191 | "@babel/code-frame" "^7.22.13" 192 | "@babel/generator" "^7.22.15" 193 | "@babel/helper-environment-visitor" "^7.22.5" 194 | "@babel/helper-function-name" "^7.22.5" 195 | "@babel/helper-hoist-variables" "^7.22.5" 196 | "@babel/helper-split-export-declaration" "^7.22.6" 197 | "@babel/parser" "^7.22.15" 198 | "@babel/types" "^7.22.15" 199 | debug "^4.1.0" 200 | globals "^11.1.0" 201 | 202 | "@babel/types@^7.22.15", "@babel/types@^7.22.5": 203 | version "7.22.15" 204 | resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.15.tgz#266cb21d2c5fd0b3931e7a91b6dd72d2f617d282" 205 | integrity sha512-X+NLXr0N8XXmN5ZsaQdm9U2SSC3UbIYq/doL++sueHOTisgZHoKaQtZxGuV2cUPQHMfjKEfg/g6oy7Hm6SKFtA== 206 | dependencies: 207 | "@babel/helper-string-parser" "^7.22.5" 208 | "@babel/helper-validator-identifier" "^7.22.15" 209 | to-fast-properties "^2.0.0" 210 | 211 | "@esbuild/android-arm64@0.18.20": 212 | version "0.18.20" 213 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" 214 | integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== 215 | 216 | "@esbuild/android-arm@0.18.20": 217 | version "0.18.20" 218 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" 219 | integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== 220 | 221 | "@esbuild/android-x64@0.18.20": 222 | version "0.18.20" 223 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" 224 | integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== 225 | 226 | "@esbuild/darwin-arm64@0.18.20": 227 | version "0.18.20" 228 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" 229 | integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== 230 | 231 | "@esbuild/darwin-x64@0.18.20": 232 | version "0.18.20" 233 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" 234 | integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== 235 | 236 | "@esbuild/freebsd-arm64@0.18.20": 237 | version "0.18.20" 238 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" 239 | integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== 240 | 241 | "@esbuild/freebsd-x64@0.18.20": 242 | version "0.18.20" 243 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" 244 | integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== 245 | 246 | "@esbuild/linux-arm64@0.18.20": 247 | version "0.18.20" 248 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" 249 | integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== 250 | 251 | "@esbuild/linux-arm@0.18.20": 252 | version "0.18.20" 253 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" 254 | integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== 255 | 256 | "@esbuild/linux-ia32@0.18.20": 257 | version "0.18.20" 258 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" 259 | integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== 260 | 261 | "@esbuild/linux-loong64@0.18.20": 262 | version "0.18.20" 263 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" 264 | integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== 265 | 266 | "@esbuild/linux-mips64el@0.18.20": 267 | version "0.18.20" 268 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" 269 | integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== 270 | 271 | "@esbuild/linux-ppc64@0.18.20": 272 | version "0.18.20" 273 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" 274 | integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== 275 | 276 | "@esbuild/linux-riscv64@0.18.20": 277 | version "0.18.20" 278 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" 279 | integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== 280 | 281 | "@esbuild/linux-s390x@0.18.20": 282 | version "0.18.20" 283 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" 284 | integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== 285 | 286 | "@esbuild/linux-x64@0.18.20": 287 | version "0.18.20" 288 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" 289 | integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== 290 | 291 | "@esbuild/netbsd-x64@0.18.20": 292 | version "0.18.20" 293 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" 294 | integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== 295 | 296 | "@esbuild/openbsd-x64@0.18.20": 297 | version "0.18.20" 298 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" 299 | integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== 300 | 301 | "@esbuild/sunos-x64@0.18.20": 302 | version "0.18.20" 303 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" 304 | integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== 305 | 306 | "@esbuild/win32-arm64@0.18.20": 307 | version "0.18.20" 308 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" 309 | integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== 310 | 311 | "@esbuild/win32-ia32@0.18.20": 312 | version "0.18.20" 313 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" 314 | integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== 315 | 316 | "@esbuild/win32-x64@0.18.20": 317 | version "0.18.20" 318 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" 319 | integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== 320 | 321 | "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": 322 | version "0.3.3" 323 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" 324 | integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== 325 | dependencies: 326 | "@jridgewell/set-array" "^1.0.1" 327 | "@jridgewell/sourcemap-codec" "^1.4.10" 328 | "@jridgewell/trace-mapping" "^0.3.9" 329 | 330 | "@jridgewell/resolve-uri@^3.1.0": 331 | version "3.1.1" 332 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" 333 | integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== 334 | 335 | "@jridgewell/set-array@^1.0.1": 336 | version "1.1.2" 337 | resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" 338 | integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== 339 | 340 | "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": 341 | version "1.4.15" 342 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" 343 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== 344 | 345 | "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": 346 | version "0.3.19" 347 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" 348 | integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== 349 | dependencies: 350 | "@jridgewell/resolve-uri" "^3.1.0" 351 | "@jridgewell/sourcemap-codec" "^1.4.14" 352 | 353 | "@vitejs/plugin-react@^4.0.4": 354 | version "4.0.4" 355 | resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz#31c3f779dc534e045c4b134e7cf7b150af0a7646" 356 | integrity sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g== 357 | dependencies: 358 | "@babel/core" "^7.22.9" 359 | "@babel/plugin-transform-react-jsx-self" "^7.22.5" 360 | "@babel/plugin-transform-react-jsx-source" "^7.22.5" 361 | react-refresh "^0.14.0" 362 | 363 | ansi-styles@^3.2.1: 364 | version "3.2.1" 365 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 366 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 367 | dependencies: 368 | color-convert "^1.9.0" 369 | 370 | browserslist@^4.21.9: 371 | version "4.21.10" 372 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" 373 | integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== 374 | dependencies: 375 | caniuse-lite "^1.0.30001517" 376 | electron-to-chromium "^1.4.477" 377 | node-releases "^2.0.13" 378 | update-browserslist-db "^1.0.11" 379 | 380 | caniuse-lite@^1.0.30001517: 381 | version "1.0.30001528" 382 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001528.tgz#479972fc705b996f1114336c0032418a215fd0aa" 383 | integrity sha512-0Db4yyjR9QMNlsxh+kKWzQtkyflkG/snYheSzkjmvdEtEXB1+jt7A2HmSEiO6XIJPIbo92lHNGNySvE5pZcs5Q== 384 | 385 | chalk@^2.4.2: 386 | version "2.4.2" 387 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 388 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 389 | dependencies: 390 | ansi-styles "^3.2.1" 391 | escape-string-regexp "^1.0.5" 392 | supports-color "^5.3.0" 393 | 394 | color-convert@^1.9.0: 395 | version "1.9.3" 396 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 397 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 398 | dependencies: 399 | color-name "1.1.3" 400 | 401 | color-name@1.1.3: 402 | version "1.1.3" 403 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 404 | integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== 405 | 406 | convert-source-map@^1.7.0: 407 | version "1.9.0" 408 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" 409 | integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== 410 | 411 | debug@^4.1.0: 412 | version "4.3.4" 413 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 414 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 415 | dependencies: 416 | ms "2.1.2" 417 | 418 | electron-to-chromium@^1.4.477: 419 | version "1.4.510" 420 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.510.tgz#446c50d7533c1e71a84b00a3b37ab06dd601d890" 421 | integrity sha512-xPfLIPFcN/WLXBpQ/K4UgE98oUBO5Tia6BD4rkSR0wE7ep/PwBVlgvPJQrIBpmJGVAmUzwPKuDbVt9XV6+uC2g== 422 | 423 | esbuild@^0.18.10: 424 | version "0.18.20" 425 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" 426 | integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== 427 | optionalDependencies: 428 | "@esbuild/android-arm" "0.18.20" 429 | "@esbuild/android-arm64" "0.18.20" 430 | "@esbuild/android-x64" "0.18.20" 431 | "@esbuild/darwin-arm64" "0.18.20" 432 | "@esbuild/darwin-x64" "0.18.20" 433 | "@esbuild/freebsd-arm64" "0.18.20" 434 | "@esbuild/freebsd-x64" "0.18.20" 435 | "@esbuild/linux-arm" "0.18.20" 436 | "@esbuild/linux-arm64" "0.18.20" 437 | "@esbuild/linux-ia32" "0.18.20" 438 | "@esbuild/linux-loong64" "0.18.20" 439 | "@esbuild/linux-mips64el" "0.18.20" 440 | "@esbuild/linux-ppc64" "0.18.20" 441 | "@esbuild/linux-riscv64" "0.18.20" 442 | "@esbuild/linux-s390x" "0.18.20" 443 | "@esbuild/linux-x64" "0.18.20" 444 | "@esbuild/netbsd-x64" "0.18.20" 445 | "@esbuild/openbsd-x64" "0.18.20" 446 | "@esbuild/sunos-x64" "0.18.20" 447 | "@esbuild/win32-arm64" "0.18.20" 448 | "@esbuild/win32-ia32" "0.18.20" 449 | "@esbuild/win32-x64" "0.18.20" 450 | 451 | escalade@^3.1.1: 452 | version "3.1.1" 453 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 454 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 455 | 456 | escape-string-regexp@^1.0.5: 457 | version "1.0.5" 458 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 459 | integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== 460 | 461 | fsevents@~2.3.2: 462 | version "2.3.3" 463 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" 464 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== 465 | 466 | gensync@^1.0.0-beta.2: 467 | version "1.0.0-beta.2" 468 | resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 469 | integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== 470 | 471 | globals@^11.1.0: 472 | version "11.12.0" 473 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 474 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 475 | 476 | has-flag@^3.0.0: 477 | version "3.0.0" 478 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 479 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 480 | 481 | "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: 482 | version "4.0.0" 483 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 484 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 485 | 486 | jsesc@^2.5.1: 487 | version "2.5.2" 488 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 489 | integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== 490 | 491 | json5@^2.2.3: 492 | version "2.2.3" 493 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" 494 | integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== 495 | 496 | loose-envify@^1.1.0: 497 | version "1.4.0" 498 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 499 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 500 | dependencies: 501 | js-tokens "^3.0.0 || ^4.0.0" 502 | 503 | lru-cache@^5.1.1: 504 | version "5.1.1" 505 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" 506 | integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== 507 | dependencies: 508 | yallist "^3.0.2" 509 | 510 | ms@2.1.2: 511 | version "2.1.2" 512 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 513 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 514 | 515 | nanoid@^3.3.6: 516 | version "3.3.6" 517 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" 518 | integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== 519 | 520 | node-releases@^2.0.13: 521 | version "2.0.13" 522 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" 523 | integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== 524 | 525 | picocolors@^1.0.0: 526 | version "1.0.0" 527 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 528 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 529 | 530 | postcss@^8.4.27: 531 | version "8.4.29" 532 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" 533 | integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== 534 | dependencies: 535 | nanoid "^3.3.6" 536 | picocolors "^1.0.0" 537 | source-map-js "^1.0.2" 538 | 539 | react-dom@^18.2.0: 540 | version "18.2.0" 541 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" 542 | integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== 543 | dependencies: 544 | loose-envify "^1.1.0" 545 | scheduler "^0.23.0" 546 | 547 | react-refresh@^0.14.0: 548 | version "0.14.0" 549 | resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" 550 | integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== 551 | 552 | react@^18.2.0: 553 | version "18.2.0" 554 | resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" 555 | integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== 556 | dependencies: 557 | loose-envify "^1.1.0" 558 | 559 | rollup@^3.27.1: 560 | version "3.29.0" 561 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.0.tgz#1b40e64818afc979c7e5bef93de675829288986b" 562 | integrity sha512-nszM8DINnx1vSS+TpbWKMkxem0CDWk3cSit/WWCBVs9/JZ1I/XLwOsiUglYuYReaeWWSsW9kge5zE5NZtf/a4w== 563 | optionalDependencies: 564 | fsevents "~2.3.2" 565 | 566 | scheduler@^0.23.0: 567 | version "0.23.0" 568 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" 569 | integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== 570 | dependencies: 571 | loose-envify "^1.1.0" 572 | 573 | semver@^6.3.1: 574 | version "6.3.1" 575 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" 576 | integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== 577 | 578 | source-map-js@^1.0.2: 579 | version "1.0.2" 580 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 581 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 582 | 583 | supports-color@^5.3.0: 584 | version "5.5.0" 585 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 586 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 587 | dependencies: 588 | has-flag "^3.0.0" 589 | 590 | to-fast-properties@^2.0.0: 591 | version "2.0.0" 592 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 593 | integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== 594 | 595 | typescript@^5.2.2: 596 | version "5.2.2" 597 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" 598 | integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== 599 | 600 | update-browserslist-db@^1.0.11: 601 | version "1.0.11" 602 | resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" 603 | integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== 604 | dependencies: 605 | escalade "^3.1.1" 606 | picocolors "^1.0.0" 607 | 608 | vite@^4.4.9: 609 | version "4.4.9" 610 | resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d" 611 | integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA== 612 | dependencies: 613 | esbuild "^0.18.10" 614 | postcss "^8.4.27" 615 | rollup "^3.27.1" 616 | optionalDependencies: 617 | fsevents "~2.3.2" 618 | 619 | yallist@^3.0.2: 620 | version "3.1.1" 621 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" 622 | integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== 623 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.5.2", 3 | "license": "BSD-3-Clause-Clear", 4 | "exports": { 5 | ".": { 6 | "import": "./dist/react-var-ui.js", 7 | "types": "./dist/index.d.ts" 8 | }, 9 | "./dist/index.css": { 10 | "require": "./dist/index.css", 11 | "import": "./dist/index.css" 12 | }, 13 | "./index.css": { 14 | "require": "./dist/index.css", 15 | "import": "./dist/index.css" 16 | } 17 | }, 18 | "type": "module", 19 | "main": "dist/react-var-ui.js", 20 | "typings": "dist/index.d.ts", 21 | "module": "dist/react-var-ui.js", 22 | "files": [ 23 | "dist" 24 | ], 25 | "engines": { 26 | "node": ">=10" 27 | }, 28 | "scripts": { 29 | "build": "vite build", 30 | "test": "vitest", 31 | "lint": "eslint src", 32 | "prepare": "vite build", 33 | "chromatic": "npx chromatic --exit-zero-on-changes", 34 | "storybook": "storybook dev -p 6006", 35 | "build-storybook": "storybook build", 36 | "coverage": "vitest --coverage --run" 37 | }, 38 | "peerDependencies": { 39 | "react": ">=16" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/mat-sz/react-var-ui.git" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/mat-sz/react-var-ui/issues" 47 | }, 48 | "homepage": "https://github.com/mat-sz/react-var-ui", 49 | "name": "react-var-ui", 50 | "author": "Mat Sz ", 51 | "keywords": [ 52 | "react", 53 | "dat.gui", 54 | "react-component", 55 | "react-slider", 56 | "slider", 57 | "input", 58 | "range", 59 | "library", 60 | "typescript" 61 | ], 62 | "resolutions": { 63 | "string-width": "4.2.3", 64 | "strip-ansi": "6.0.1", 65 | "wrap-ansi": "7.0.0" 66 | }, 67 | "devDependencies": { 68 | "@rollup/plugin-typescript": "^11.1.3", 69 | "@storybook/addon-essentials": "^7.4.0", 70 | "@storybook/addon-interactions": "^7.4.0", 71 | "@storybook/addon-links": "^7.4.0", 72 | "@storybook/addon-onboarding": "^1.0.8", 73 | "@storybook/blocks": "^7.4.0", 74 | "@storybook/preview-api": "^7.5.3", 75 | "@storybook/react": "^7.4.0", 76 | "@storybook/react-vite": "^7.4.0", 77 | "@storybook/testing-library": "^0.2.0", 78 | "@testing-library/jest-dom": "^6.1.3", 79 | "@testing-library/react": "^14.0.0", 80 | "@types/react": "^18.2.21", 81 | "@types/react-dom": "^18.2.7", 82 | "@types/testing-library__jest-dom": "^5.14.1", 83 | "@vitejs/plugin-react": "^4.0.4", 84 | "@vitest/coverage-v8": "^0.34.6", 85 | "canvas": "^2.11.2", 86 | "chromatic": "^7.1.0", 87 | "husky": "^6.0.0", 88 | "jsdom": "^22.1.0", 89 | "react": "^18.2.0", 90 | "react-dom": "^18.2.0", 91 | "sass": "^1.34.1", 92 | "storybook": "^7.4.0", 93 | "typescript": "^5.2.2", 94 | "vite": "^4.4.9", 95 | "vitest": "^0.34.3" 96 | }, 97 | "dependencies": { 98 | "@uiw/react-color-sketch": "^2.0.5", 99 | "filesize": "^8.0.6", 100 | "radash": "^11.0.0", 101 | "react-use-pointer-drag": "^0.1.1" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshot.png -------------------------------------------------------------------------------- /screenshots/VarAngle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarAngle.png -------------------------------------------------------------------------------- /screenshots/VarButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarButton.png -------------------------------------------------------------------------------- /screenshots/VarCategory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarCategory.png -------------------------------------------------------------------------------- /screenshots/VarColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarColor.png -------------------------------------------------------------------------------- /screenshots/VarDisplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarDisplay.png -------------------------------------------------------------------------------- /screenshots/VarFile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarFile.png -------------------------------------------------------------------------------- /screenshots/VarImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarImage.png -------------------------------------------------------------------------------- /screenshots/VarMedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarMedia.png -------------------------------------------------------------------------------- /screenshots/VarNumber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarNumber.png -------------------------------------------------------------------------------- /screenshots/VarSelect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarSelect.png -------------------------------------------------------------------------------- /screenshots/VarSlider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarSlider.png -------------------------------------------------------------------------------- /screenshots/VarString.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarString.png -------------------------------------------------------------------------------- /screenshots/VarToggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarToggle.png -------------------------------------------------------------------------------- /screenshots/VarXY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mat-sz/react-var-ui/c9fd00c9a9865aedd93a68c3e2fc5d7381fbea61/screenshots/VarXY.png -------------------------------------------------------------------------------- /src/VarAngle.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo, useRef } from 'react'; 2 | import { usePointerDrag } from 'react-use-pointer-drag'; 3 | 4 | import { useVarUIValue } from './common/VarUIContext'; 5 | import { IVarBaseInputProps, VarBase } from './VarBase'; 6 | 7 | const PI2 = Math.PI * 2; 8 | 9 | function wrap(angle: number) { 10 | return (PI2 + (angle % PI2)) % PI2; 11 | } 12 | 13 | export interface IVarAngleProps extends IVarBaseInputProps {} 14 | 15 | /** 16 | * Angle picker component. Accepts and provides numbers (radians). 17 | */ 18 | export const VarAngle = ({ 19 | label, 20 | path, 21 | value, 22 | onChange, 23 | disabled, 24 | readOnly, 25 | defaultValue = 0, 26 | className, 27 | error, 28 | errorPath, 29 | }: IVarAngleProps): JSX.Element => { 30 | const controlRef = useRef(null); 31 | const [currentValue, setCurrentValue, currentError] = useVarUIValue({ 32 | path, 33 | fallbackValue: value, 34 | onChange, 35 | error, 36 | errorPath, 37 | }); 38 | const degrees = useMemo( 39 | () => Math.round(wrap(currentValue) * (180 / Math.PI)), 40 | [currentValue] 41 | ); 42 | 43 | const { dragProps } = usePointerDrag({ 44 | onMove: ({ x, y }) => { 45 | const div = controlRef.current!; 46 | const rect = div.getBoundingClientRect(); 47 | const centerX = rect.left + rect.width / 2; 48 | const centerY = rect.top + rect.height / 2; 49 | setCurrentValue(wrap(Math.atan2(y - centerY, x - centerX) + Math.PI / 2)); 50 | }, 51 | }); 52 | 53 | useEffect(() => { 54 | controlRef.current?.addEventListener('wheel', e => e.preventDefault()); 55 | }, []); 56 | 57 | return ( 58 | 65 | {degrees}° 66 |
67 |
72 | typeof defaultValue !== 'undefined' && setCurrentValue(defaultValue) 73 | } 74 | onWheel={e => { 75 | setCurrentValue(wrap(currentValue + 0.5 * Math.sign(e.deltaY))); 76 | }} 77 | title="Angle" 78 | {...dragProps()} 79 | >
80 |
81 |
82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /src/VarArray.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { clone, get, set } from 'radash'; 3 | 4 | import { useVarUIValue, VarUIContext } from './common/VarUIContext'; 5 | import { IVarBaseInputProps, VarBase } from './VarBase'; 6 | 7 | export interface IVarArrayProps 8 | extends Omit, 'label' | 'children' | 'readOnly'> { 9 | children?: ReactNode | ((element: T, index: number, array: T[]) => ReactNode); 10 | } 11 | 12 | /** 13 | * Array input component. 14 | */ 15 | export const VarArray = ({ 16 | path, 17 | value, 18 | onChange, 19 | disabled, 20 | className, 21 | children, 22 | error, 23 | errorPath, 24 | }: IVarArrayProps): JSX.Element => { 25 | const [currentValue, setCurrentValue, currentError] = useVarUIValue({ 26 | path, 27 | fallbackValue: value, 28 | onChange, 29 | error, 30 | errorPath, 31 | }); 32 | 33 | return ( 34 |
41 | {currentValue?.map((element, index, array) => { 42 | return ( 43 | 47 | typeof path === 'string' ? get(element, path) : undefined, 48 | setValue: (path: string, newValue: any) => { 49 | const newArray = [...currentValue]; 50 | newArray[index] = 51 | path === '' ? newValue : set(clone(element), path, newValue); 52 | setCurrentValue(newArray); 53 | }, 54 | getError: (path?: string) => { 55 | const elementError = currentError?.[index]; 56 | return elementError && path 57 | ? get(elementError, path) 58 | : undefined; 59 | }, 60 | }} 61 | key={index} 62 | > 63 | {typeof children === 'function' 64 | ? children(element, index, array) 65 | : children} 66 | 67 | ); 68 | })} 69 |
70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /src/VarBase.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | 3 | export interface IVarBaseProps { 4 | /** 5 | * Label to be shown left to the input. 6 | */ 7 | label?: ReactNode; 8 | 9 | /** 10 | * Additional class names on the wrapping div element. 11 | */ 12 | className?: string; 13 | 14 | /** 15 | * Should the component be disabled. 16 | */ 17 | disabled?: boolean; 18 | 19 | /** 20 | * Should the component be read-only. 21 | */ 22 | readOnly?: boolean; 23 | 24 | /** 25 | * Children. Only rendered when provided directly to the VarBase component. 26 | */ 27 | children?: ReactNode; 28 | 29 | /** 30 | * Should keep children in a column, with every child having a width of 100%. 31 | */ 32 | column?: boolean; 33 | 34 | /** 35 | * Error to display. 36 | */ 37 | error?: string; 38 | } 39 | 40 | export interface IVarBaseValueProps { 41 | /** 42 | * Variable path in the data object. 43 | */ 44 | path?: string; 45 | 46 | /** 47 | * Current value (only used if context and path aren't available). 48 | * In most cases you aren't going to need this. 49 | */ 50 | value?: T; 51 | 52 | /** 53 | * Default value for components that support resetting (on double click for example). 54 | */ 55 | defaultValue?: T; 56 | 57 | /** 58 | * On change event, called with the new value if provided. 59 | * In most cases you aren't going to need this. 60 | */ 61 | onChange?: (value: T) => void; 62 | 63 | /** 64 | * Error path to resolve in object. (default: same as path) 65 | */ 66 | errorPath?: string; 67 | 68 | error?: string; 69 | } 70 | 71 | export interface IVarBaseInputProps 72 | extends IVarBaseProps, 73 | IVarBaseValueProps {} 74 | 75 | /** 76 | * Base VarUI input component. Doesn't do anything besides displaying the label. 77 | * Used to construct other components from. 78 | */ 79 | export const VarBase = ({ 80 | label, 81 | children, 82 | className, 83 | disabled, 84 | readOnly, 85 | column = false, 86 | error, 87 | }: IVarBaseProps): JSX.Element => { 88 | return ( 89 |
101 | {!!label && {label}} 102 | {children} 103 | {error ?
{error}
: null} 104 |
105 | ); 106 | }; 107 | -------------------------------------------------------------------------------- /src/VarButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | 3 | import { IVarBaseProps, VarBase } from './VarBase'; 4 | 5 | export interface IVarButtonProps extends IVarBaseProps { 6 | /** 7 | * Called when the button is clicked. 8 | */ 9 | onClick?: () => void; 10 | 11 | /** 12 | * Text for the button. 13 | */ 14 | buttonLabel: ReactNode; 15 | 16 | /** 17 | * Should the component be disabled. 18 | */ 19 | disabled?: boolean; 20 | } 21 | 22 | /** 23 | * Button component. Only provides a onClick property. 24 | */ 25 | export const VarButton = ({ 26 | label, 27 | onClick, 28 | buttonLabel, 29 | disabled, 30 | className, 31 | error, 32 | }: IVarButtonProps): JSX.Element => { 33 | return ( 34 | 40 | 41 | 44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /src/VarCategory.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useState } from 'react'; 2 | import { IconDown } from './icons/IconDown'; 3 | import { IconUp } from './icons/IconUp'; 4 | 5 | export interface IVarCategoryProps { 6 | /** 7 | * Category label. 8 | */ 9 | label: ReactNode; 10 | 11 | /** 12 | * Additional class names on the wrapping div element. 13 | */ 14 | className?: string; 15 | 16 | /** 17 | * Allows the category to be collapsed if true. 18 | */ 19 | collapsible?: boolean; 20 | 21 | children?: React.ReactNode; 22 | } 23 | 24 | /** 25 | * Category component for grouping inputs. 26 | */ 27 | export const VarCategory = ({ 28 | label, 29 | className, 30 | children, 31 | collapsible, 32 | }: IVarCategoryProps): JSX.Element => { 33 | const [isCollapsed, setCollapsed] = useState(false); 34 | 35 | return ( 36 |
37 |
38 | {label} 39 | {collapsible && ( 40 | 47 | )} 48 |
49 | {(!collapsible || !isCollapsed) &&
{children}
} 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/VarColor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | import ColorPicker from '@uiw/react-color-sketch'; 3 | 4 | import { useVarUIValue } from './common/VarUIContext'; 5 | import { IVarBaseInputProps, VarBase } from './VarBase'; 6 | 7 | export interface IVarColorProps extends IVarBaseInputProps { 8 | /** 9 | * Should allow picking alpha values? 10 | * If true, the result hex code will contain extra two characters representing the alpha value, from 00 to FF. 11 | */ 12 | alpha?: boolean; 13 | } 14 | 15 | /** 16 | * Color picker component. Returns and accepts values in form of hex color strings. 17 | */ 18 | export const VarColor = ({ 19 | label, 20 | path, 21 | value, 22 | onChange, 23 | alpha, 24 | disabled, 25 | readOnly, 26 | className, 27 | error, 28 | errorPath, 29 | }: IVarColorProps): JSX.Element => { 30 | const [currentValue, setCurrentValue, currentError] = useVarUIValue({ 31 | path, 32 | fallbackValue: value, 33 | onChange, 34 | error, 35 | errorPath, 36 | }); 37 | 38 | const [show, setShow] = useState(false); 39 | 40 | return ( 41 | 48 | 49 | {currentValue} 50 |
51 |
setShow(show => !show)} 54 | > 55 |
60 |
61 | {show ? ( 62 |
63 |
setShow(false)} 66 | /> 67 | { 70 | if (alpha) { 71 | setCurrentValue(result.hexa); 72 | } else { 73 | setCurrentValue(result.hex); 74 | } 75 | }} 76 | disableAlpha={!alpha} 77 | /> 78 |
79 | ) : null} 80 |
81 | 82 | 83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /src/VarDisplay.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useVarUIValue } from './common/VarUIContext'; 4 | import { IVarBaseProps, VarBase } from './VarBase'; 5 | 6 | export interface IVarDisplayProps extends IVarBaseProps { 7 | /** 8 | * Variable path in the data object. 9 | */ 10 | path?: string; 11 | 12 | /** 13 | * Current value (only used if context and path aren't available). 14 | * In most cases you aren't going to need this. 15 | */ 16 | value?: string | number; 17 | 18 | errorPath?: string; 19 | 20 | unit?: string; 21 | } 22 | 23 | /** 24 | * A simple component that displays a string or a numeric value. 25 | */ 26 | export const VarDisplay = ({ 27 | label, 28 | path, 29 | value, 30 | disabled, 31 | readOnly, 32 | className, 33 | error, 34 | errorPath, 35 | unit, 36 | }: IVarDisplayProps): JSX.Element => { 37 | const [currentValue, _, currentError] = useVarUIValue({ 38 | path, 39 | fallbackValue: value, 40 | error, 41 | errorPath, 42 | }); 43 | 44 | return ( 45 | 52 | 53 | {currentValue} 54 | {unit} 55 | 56 | 57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /src/VarFile.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import filesize from 'filesize'; 3 | 4 | import { useVarUIValue } from './common/VarUIContext'; 5 | import { IconUpload } from './icons/IconUpload'; 6 | import { IVarBaseInputProps, VarBase } from './VarBase'; 7 | 8 | export interface IVarFileProps extends IVarBaseInputProps { 9 | /** 10 | * Accepted file types. 11 | */ 12 | accept?: string; 13 | 14 | /** 15 | * Show metadata. 16 | * Default: true. 17 | */ 18 | displayMetadata?: boolean; 19 | } 20 | 21 | /** 22 | * File input component. Accepts and provides a File instance. 23 | */ 24 | export const VarFile = ({ 25 | label, 26 | path, 27 | value, 28 | onChange, 29 | disabled, 30 | readOnly, 31 | className, 32 | accept, 33 | displayMetadata = true, 34 | error, 35 | errorPath, 36 | }: IVarFileProps): JSX.Element => { 37 | const [currentValue, setCurrentValue, currentError] = useVarUIValue({ 38 | path, 39 | fallbackValue: value, 40 | onChange, 41 | error, 42 | errorPath, 43 | }); 44 | 45 | const onFileChange = useCallback( 46 | (e: React.ChangeEvent) => { 47 | if (!e.target.files?.length) { 48 | return; 49 | } 50 | 51 | const file = e.target.files[0]; 52 | setCurrentValue(file); 53 | }, 54 | [setCurrentValue] 55 | ); 56 | 57 | return ( 58 | 65 | {currentValue?.name} 66 |
67 | {displayMetadata && !!currentValue && ( 68 |
69 |
Size: {filesize(currentValue.size)}
70 |
Type: {currentValue.type || 'unknown'}
71 |
72 | )} 73 | 74 | 80 |
81 |
82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /src/VarImage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | 3 | import { useVarUIValue } from './common/VarUIContext'; 4 | import { IconUpload } from './icons/IconUpload'; 5 | import { IVarBaseInputProps, VarBase } from './VarBase'; 6 | 7 | export interface IVarImageProps extends IVarBaseInputProps {} 8 | 9 | /** 10 | * Image input component. Accepts and provides a blob URL. 11 | */ 12 | export const VarImage = ({ 13 | label, 14 | path, 15 | value, 16 | onChange, 17 | disabled, 18 | readOnly, 19 | className, 20 | error, 21 | errorPath, 22 | }: IVarImageProps): JSX.Element => { 23 | const [currentValue, setCurrentValue, currentError] = useVarUIValue({ 24 | path, 25 | fallbackValue: value, 26 | onChange, 27 | error, 28 | errorPath, 29 | }); 30 | 31 | const onFileChange = useCallback( 32 | (e: React.ChangeEvent) => { 33 | if (!e.target.files?.length) { 34 | return; 35 | } 36 | 37 | const file = e.target.files[0]; 38 | const url = URL.createObjectURL(file); 39 | setCurrentValue(url); 40 | }, 41 | [setCurrentValue] 42 | ); 43 | 44 | return ( 45 | 53 |
54 |
63 | 64 | 70 |
71 |
72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /src/VarMedia.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useMemo, useState } from 'react'; 2 | 3 | import { useVarUIValue } from './common/VarUIContext'; 4 | import { IconUpload } from './icons/IconUpload'; 5 | import { IVarBaseInputProps, VarBase } from './VarBase'; 6 | 7 | export interface IVarMediaProps extends IVarBaseInputProps { 8 | /** 9 | * Whether the component should accept image/* files. 10 | */ 11 | acceptImage?: boolean; 12 | 13 | /** 14 | * Whether the component should accept audio/* files. 15 | */ 16 | acceptAudio?: boolean; 17 | 18 | /** 19 | * Whether the component should accept video/* files. 20 | */ 21 | acceptVideo?: boolean; 22 | } 23 | 24 | /** 25 | * Media (audio/video/image) input component. Accepts and provides a blob URL. 26 | * 27 | * If acceptImage, acceptAudio and acceptVideo are all false, the component will accept all 3. 28 | */ 29 | export const VarMedia = ({ 30 | label, 31 | path, 32 | value, 33 | onChange, 34 | disabled, 35 | readOnly, 36 | className, 37 | acceptImage, 38 | acceptAudio, 39 | acceptVideo, 40 | error, 41 | errorPath, 42 | }: IVarMediaProps): JSX.Element => { 43 | const [currentValue, setCurrentValue, currentError] = useVarUIValue({ 44 | path, 45 | fallbackValue: value, 46 | onChange, 47 | error, 48 | errorPath, 49 | }); 50 | const [type, setType] = useState(); 51 | const accept = useMemo(() => { 52 | let accept = ''; 53 | 54 | if (acceptImage) { 55 | accept += 'image/*,'; 56 | } 57 | 58 | if (acceptAudio) { 59 | accept += 'audio/*,'; 60 | } 61 | 62 | if (acceptVideo) { 63 | accept += 'video/*,'; 64 | } 65 | 66 | if (accept.endsWith(',')) { 67 | accept = accept.slice(0, -1); 68 | } 69 | 70 | if (!accept) { 71 | accept = 'image/*,audio/*,video/*'; 72 | } 73 | 74 | return accept; 75 | }, [acceptImage, acceptAudio, acceptVideo]); 76 | 77 | const onFileChange = useCallback( 78 | (e: React.ChangeEvent) => { 79 | if (!e.target.files?.length) { 80 | return; 81 | } 82 | 83 | const file = e.target.files[0]; 84 | const url = URL.createObjectURL(file); 85 | setCurrentValue(url); 86 | }, 87 | [setCurrentValue, setType] 88 | ); 89 | 90 | const updatePreview = useCallback( 91 | async (url: string) => { 92 | if (!url) { 93 | setType(undefined); 94 | return; 95 | } 96 | 97 | const res = await fetch(url); 98 | setType(res?.headers?.get('Content-Type')?.split('/')?.[0]); 99 | }, 100 | [setType] 101 | ); 102 | 103 | useEffect(() => { 104 | updatePreview(currentValue); 105 | }, [currentValue]); 106 | 107 | let preview = ( 108 |
109 | {currentValue ? 'Unsupported file type.' : ''} 110 |
111 | ); 112 | 113 | switch (type) { 114 | case 'image': 115 | preview = ( 116 |
123 | ); 124 | break; 125 | case 'video': 126 | preview = ( 127 |