├── .github └── FUNDING.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── LICENSE ├── README.md ├── biome.json ├── demo ├── .eslintrc.json ├── .gitignore ├── components │ ├── accordion.tsx │ ├── alert-dialog.tsx │ ├── aspect-ratio.tsx │ ├── avatar.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── context-menu.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── hover-card.tsx │ ├── menubar.tsx │ ├── navigation-menu.tsx │ ├── popover.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── select.tsx │ ├── shared │ │ ├── button.tsx │ │ ├── command-menu.tsx │ │ ├── demo-card.tsx │ │ └── theme-switcher.tsx │ ├── slider.tsx │ ├── switch.tsx │ ├── tabs.tsx │ ├── toast.tsx │ ├── toggle-group.tsx │ ├── toggle.tsx │ ├── toolbar.tsx │ └── tooltip.tsx ├── css │ └── tailwind.css ├── hooks │ ├── use-dark-mode.ts │ └── use-media-query.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public │ ├── favicon.ico │ └── static │ │ ├── og.png │ │ ├── og.webp │ │ └── theme.js ├── tailwind.config.js ├── tsconfig.json ├── utils │ ├── math.ts │ └── random.ts └── vercel.json ├── package.json ├── pnpm-lock.yaml ├── src ├── __snapshots__ │ └── index.test.ts.snap ├── index.test.ts └── index.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ecklf 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm test 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | demo 3 | types 4 | .github 5 | .vscode 6 | .gitignore 7 | tsconfig.json 8 | .scratch -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ecklf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 | 8 |

9 | 10 |

11 | Utilities and variants for styling Radix state 12 |

13 | 14 |
15 | 16 | ![tailwindcss v4 ready](https://img.shields.io/badge/tailwindcss-v4%20ready-0F172A?logo=tailwindcss&style=flat&labelColor=38bdf8&logoColor=ffffff) 17 | ![npm version](https://img.shields.io/npm/v/tailwindcss-radix.svg) 18 | ![npm downloads](https://img.shields.io/npm/dm/tailwindcss-radix.svg) 19 | 20 |
21 | 22 | ## What is this? 23 | 24 | The main purpose of this library is adding classnames for accessing Radix data attributes, which gains you the benefit of auto-completion compared to using `data-*` variants. 25 | 26 | **TL;DR** It's [@headlessui-tailwindcss](https://github.com/tailwindlabs/headlessui/tree/main/packages/@headlessui-tailwindcss) for Radix. 27 | 28 | ## Installation 29 | 30 | ```sh 31 | # For v3 compatibility 32 | pnpm add tailwindcss-radix@3 33 | # For v4 compatibility 34 | pnpm add tailwindcss-radix 35 | ``` 36 | 37 | ## Demo 38 | 39 | Click on the banner to check out the demo components. You can find the code inside the [demo](https://github.com/ecklf/tailwindcss-radix/tree/main/demo) folder. 40 | 41 | ## Usage 42 | 43 | ### With [@plugin directive](https://tailwindcss.com/docs/functions-and-directives#plugin-directive) (recommended) 44 | 45 | **Default prefix** 46 | ```css 47 | /* Generates `radix-[state/side/orientation]-*` utilities for `data-[state/side/orientation]="*"` */ 48 | @plugin "tailwindcss-radix"; 49 | ``` 50 | 51 | **Custom prefix** 52 | ```css 53 | /* Generates `rdx-[state/side/orientation]-*` utilities for `data-[state/side/orientation]="*"` */ 54 | @plugin "tailwindcss-radix" { 55 | variantPrefix: rdx; 56 | } 57 | ``` 58 | 59 | ### With [@config directive](https://tailwindcss.com/docs/functions-and-directives#config-directive) 60 | 61 | **Default prefix** 62 | ```js 63 | module.exports = { 64 | // --snip -- 65 | plugins: [ 66 | // Generates `radix-[state/side/orientation]-*` utilities for `data-[state/side/orientation]="*"` 67 | require("tailwindcss-radix")(), 68 | ], 69 | }; 70 | ``` 71 | 72 | **Custom prefix** 73 | ```js 74 | module.exports = { 75 | // --snip -- 76 | plugins: [ 77 | // Generates `rdx-[state/side/orientation]-*` utilities for `data-[state/side/orientation]="*"` 78 | require("tailwindcss-radix")({ 79 | variantPrefix: "rdx", 80 | }), 81 | ], 82 | }; 83 | ``` 84 | 85 | **Load configuration** 86 | ```css 87 | @config "../../tailwind.config.js"; 88 | ``` 89 | 90 | ### Styling state 91 | 92 | #### Basic usage 93 | 94 | This plugin works with CSS attribute selectors. Use the variants based on the `data-*` attribute added by Radix. 95 | 96 | ```tsx 97 | import React from "react"; 98 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; 99 | 100 | const App = () => { 101 | return ( 102 | 103 | 104 | Trigger 105 | 106 | 107 | Item 108 | 109 | 110 | ); 111 | }; 112 | 113 | export default App; 114 | ``` 115 | 116 | #### Accessing parent state 117 | 118 | When you need to style an element based on the state of a parent element, mark the parent with the `group` class and style the target with `group-radix-*` modifiers. 119 | 120 | Example usage of a conditional transform for a Radix `Accordion`: 121 | 122 | ```tsx 123 | import React from "react"; 124 | import * as AccordionPrimitive from "@radix-ui/react-accordion"; 125 | import { ChevronDownIcon } from "@radix-ui/react-icons"; 126 | 127 | const Accordion = () => { 128 | return ( 129 | 130 | 131 | 132 | 133 |
134 | Item 1 135 | 136 |
137 |
138 |
139 | Content 1 140 |
141 | 142 | 143 | 144 |
145 | Item 2 146 | 147 |
148 |
149 |
150 | Content 2 151 |
152 |
153 | ); 154 | }; 155 | 156 | export default App; 157 | ``` 158 | 159 | #### Accessing sibling state 160 | 161 | When you need to style an element based on the state of a sibling element, mark the sibling with the `peer` class and style the target with `peer-radix-*` modifiers. 162 | 163 | Example usage of a conditional icon color for a sibling of a Radix `Checkbox`: 164 | 165 | ```tsx 166 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 167 | import { CheckIcon, TargetIcon } from "@radix-ui/react-icons"; 168 | import React from "react"; 169 | 170 | interface Props {} 171 | 172 | const App = (props: Props) => { 173 | return ( 174 | <> 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | ); 184 | }; 185 | 186 | export default App; 187 | ``` 188 | 189 | #### Disabled state 190 | 191 | Use the generated `disabled` variant. 192 | 193 | ```tsx 194 | import React from "react"; 195 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; 196 | 197 | const ContextMenu = () => { 198 | return ( 199 | // --snip-- 200 | 204 | Item 205 | 206 | // --snip-- 207 | ); 208 | }; 209 | ``` 210 | 211 | ### CSS Variable Utilities 212 | 213 | #### Origin position 214 | 215 | ```css 216 | .origin-radix-context-menu { 217 | transform-origin: var(--radix-context-menu-content-transform-origin); 218 | } 219 | .origin-radix-dropdown-menu { 220 | transform-origin: var(--radix-dropdown-menu-content-transform-origin); 221 | } 222 | .origin-radix-hover-card { 223 | transform-origin: var(--radix-hover-card-content-transform-origin); 224 | } 225 | .origin-radix-menubar { 226 | transform-origin: var(--radix-menubar-content-transform-origin); 227 | } 228 | .origin-radix-popover { 229 | transform-origin: var(--radix-popover-content-transform-origin); 230 | } 231 | .origin-radix-select { 232 | transform-origin: var(--radix-select-content-transform-origin); 233 | } 234 | .origin-radix-tooltip { 235 | transform-origin: var(--radix-tooltip-content-transform-origin); 236 | } 237 | ``` 238 | 239 | #### Content / Viewport Width / Height 240 | 241 | ```css 242 | .w-radix-accordion-content { 243 | width: var(--radix-accordion-content-width); 244 | } 245 | .h-radix-accordion-content { 246 | height: var(--radix-accordion-content-height); 247 | } 248 | .w-radix-collapsible-content { 249 | width: var(--radix-collapsible-content-width); 250 | } 251 | .h-radix-collapsible-content { 252 | height: var(--radix-collapsible-content-height); 253 | } 254 | .w-radix-navigation-menu-viewport { 255 | width: var(--radix-navigation-menu-viewport-width); 256 | } 257 | .h-radix-navigation-menu-viewport { 258 | height: var(--radix-navigation-menu-viewport-height); 259 | } 260 | ``` 261 | 262 | #### Content Available Width / Height 263 | 264 | ```css 265 | .w-radix-context-menu-content-available { 266 | width: var(--radix-context-menu-content-available-width); 267 | } 268 | .max-w-radix-context-menu-content-available { 269 | max-width: var(--radix-context-menu-content-available-width); 270 | } 271 | .h-radix-context-menu-content-available { 272 | height: var(--radix-context-menu-content-available-height); 273 | } 274 | .max-h-radix-context-menu-content-available { 275 | max-height: var(--radix-context-menu-content-available-height); 276 | } 277 | .w-radix-dropdown-menu-content-available { 278 | width: var(--radix-dropdown-menu-content-available-width); 279 | } 280 | .max-w-radix-dropdown-menu-content-available { 281 | max-width: var(--radix-dropdown-menu-content-available-width); 282 | } 283 | .h-radix-dropdown-menu-content-available { 284 | height: var(--radix-dropdown-menu-content-available-height); 285 | } 286 | .max-h-radix-dropdown-menu-content-available { 287 | max-height: var(--radix-dropdown-menu-content-available-height); 288 | } 289 | .w-radix-hover-card-content-available { 290 | width: var(--radix-hover-card-content-available-width); 291 | } 292 | .max-w-radix-hover-card-content-available { 293 | max-width: var(--radix-hover-card-content-available-width); 294 | } 295 | .h-radix-hover-card-content-available { 296 | height: var(--radix-hover-card-content-available-height); 297 | } 298 | .max-h-radix-hover-card-content-available { 299 | max-height: var(--radix-hover-card-content-available-height); 300 | } 301 | .w-radix-menubar-content-available { 302 | width: var(--radix-menubar-content-available-width); 303 | } 304 | .max-w-radix-menubar-content-available { 305 | max-width: var(--radix-menubar-content-available-width); 306 | } 307 | .h-radix-menubar-content-available { 308 | height: var(--radix-menubar-content-available-height); 309 | } 310 | .max-h-radix-menubar-content-available { 311 | max-height: var(--radix-menubar-content-available-height); 312 | } 313 | .w-radix-popover-content-available { 314 | width: var(--radix-popover-content-available-width); 315 | } 316 | .max-w-radix-popover-content-available { 317 | max-width: var(--radix-popover-content-available-width); 318 | } 319 | .h-radix-popover-content-available { 320 | height: var(--radix-popover-content-available-height); 321 | } 322 | .max-h-radix-popover-content-available { 323 | max-height: var(--radix-popover-content-available-height); 324 | } 325 | .w-radix-select-content-available { 326 | width: var(--radix-select-content-available-width); 327 | } 328 | .max-w-radix-select-content-available { 329 | max-width: var(--radix-select-content-available-width); 330 | } 331 | .h-radix-select-content-available { 332 | height: var(--radix-select-content-available-height); 333 | } 334 | .max-h-radix-select-content-available { 335 | max-height: var(--radix-select-content-available-height); 336 | } 337 | .w-radix-tooltip-content-available { 338 | width: var(--radix-tooltip-content-available-width); 339 | } 340 | .max-w-radix-tooltip-content-available { 341 | max-width: var(--radix-tooltip-content-available-width); 342 | } 343 | .h-radix-tooltip-content-available { 344 | height: var(--radix-tooltip-content-available-height); 345 | } 346 | .max-h-radix-tooltip-content-available { 347 | max-height: var(--radix-tooltip-content-available-height); 348 | } 349 | ``` 350 | 351 | #### Trigger Available Width / Height 352 | 353 | ```css 354 | .w-radix-context-menu-trigger { 355 | width: var(--radix-context-menu-trigger-width); 356 | } 357 | .h-radix-context-menu-trigger { 358 | height: var(--radix-context-menu-trigger-height); 359 | } 360 | .w-radix-dropdown-menu-trigger { 361 | width: var(--radix-dropdown-menu-trigger-width); 362 | } 363 | .h-radix-dropdown-menu-trigger { 364 | height: var(--radix-dropdown-menu-trigger-height); 365 | } 366 | .w-radix-hover-card-trigger { 367 | width: var(--radix-hover-card-trigger-width); 368 | } 369 | .h-radix-hover-card-trigger { 370 | height: var(--radix-hover-card-trigger-height); 371 | } 372 | .w-radix-menubar-trigger { 373 | width: var(--radix-menubar-trigger-width); 374 | } 375 | .h-radix-menubar-trigger { 376 | height: var(--radix-menubar-trigger-height); 377 | } 378 | .w-radix-popover-trigger { 379 | width: var(--radix-popover-trigger-width); 380 | } 381 | .h-radix-popover-trigger { 382 | height: var(--radix-popover-trigger-height); 383 | } 384 | .w-radix-select-trigger { 385 | width: var(--radix-select-trigger-width); 386 | } 387 | .h-radix-select-trigger { 388 | height: var(--radix-select-trigger-height); 389 | } 390 | .w-radix-tooltip-trigger { 391 | width: var(--radix-tooltip-trigger-width); 392 | } 393 | .h-radix-tooltip-trigger { 394 | height: var(--radix-tooltip-trigger-height); 395 | } 396 | ``` 397 | 398 | #### Toast swiping 399 | 400 | ```css 401 | .translate-x-radix-toast-swipe-end-x { 402 | transform: translateX(var(--radix-toast-swipe-end-x)); 403 | } 404 | .translate-y-radix-toast-swipe-end-y { 405 | transform: translateY(var(--radix-toast-swipe-end-y)); 406 | } 407 | .translate-x-radix-toast-swipe-move-x { 408 | transform: translateX(var(--radix-toast-swipe-move-x)); 409 | } 410 | .translate-y-radix-toast-swipe-move-y { 411 | transform: translateY(var(--radix-toast-swipe-move-y)); 412 | } 413 | ``` 414 | 415 | ## Migrate from v1 416 | 417 | To prevent a possible future name clashing the `skipAttributeNames` option has been removed. In case you used this option, please update the class names accordingly. 418 | 419 | ## Migrate from v2 420 | 421 | In case you use `content-available` utilities: 422 | 423 | - Add `-content-available` to the width-based classnames 424 | - Remove `width` and `height` from `content-available-[width|height]` 425 | 426 |
View diff 427 |

428 | 429 | ```diff 430 | -w-radix-context-menu 431 | +w-radix-context-menu-content-available 432 | 433 | -h-radix-context-menu-content-available-height 434 | +h-radix-context-menu-content-available 435 | 436 | -max-w-radix-context-menu-content-available-width 437 | +max-w-radix-context-menu-content-available 438 | 439 | -max-h-radix-context-menu-content-available-height 440 | +max-h-radix-context-menu-content-available 441 | 442 | 443 | -w-radix-dropdown-menu 444 | +w-radix-dropdown-menu-content-available 445 | 446 | -h-radix-dropdown-menu-content-available-height 447 | +h-radix-dropdown-menu-content-available 448 | 449 | -max-w-radix-dropdown-menu-content-available-width 450 | +max-w-radix-dropdown-menu-content-available 451 | 452 | -max-h-radix-dropdown-menu-content-available-height 453 | +max-h-radix-dropdown-menu-content-available 454 | 455 | 456 | -w-radix-hover-card 457 | +w-radix-hover-card-content-available 458 | 459 | -h-radix-hover-card-content-available-height 460 | +h-radix-hover-card-content-available 461 | 462 | -max-w-radix-hover-card-content-available-width 463 | +max-w-radix-hover-card-content-available 464 | 465 | -max-h-radix-hover-card-content-available-height 466 | +max-h-radix-hover-card-content-available 467 | 468 | 469 | -w-radix-menubar 470 | +w-radix-menubar-content-available 471 | 472 | -h-radix-menubar-content-available-height 473 | +h-radix-menubar-content-available 474 | 475 | -max-w-radix-menubar-content-available-width 476 | +max-w-radix-menubar-content-available 477 | 478 | -max-h-radix-menubar-content-available-height 479 | +max-h-radix-menubar-content-available 480 | 481 | 482 | -w-radix-popover 483 | +w-radix-popover-content-available 484 | 485 | -h-radix-popover-content-available-height 486 | +h-radix-popover-content-available 487 | 488 | -max-w-radix-popover-content-available-width 489 | +max-w-radix-popover-content-available 490 | 491 | -max-h-radix-popover-content-available-height 492 | +max-h-radix-popover-content-available 493 | 494 | 495 | -w-radix-select 496 | +w-radix-select-content-available 497 | 498 | -h-radix-select 499 | +h-radix-select-content-available 500 | 501 | -max-w-radix-select-content-available-width 502 | +max-w-radix-select-content-available 503 | 504 | -max-h-radix-select-content-available-height 505 | +max-h-radix-select-content-available 506 | 507 | 508 | -w-radix-tooltip 509 | +w-radix-tooltip-content-available 510 | 511 | -h-radix-tooltip 512 | +h-radix-tooltip-content-available 513 | 514 | -max-w-radix-tooltip-content-available-width 515 | +max-w-radix-tooltip-content-available 516 | 517 | -max-h-radix-tooltip-content-available-height 518 | +max-h-radix-tooltip-content-available 519 | ``` 520 | 521 |

522 |
523 | 524 | ## Migrate from v3 525 | 526 | - Support for disabled `variantPrefix` has been removed. Please use a prefix instead. 527 | 528 | ## License 529 | 530 | [MIT](LICENSE) 531 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", 3 | "formatter": { 4 | "enabled": true, 5 | "formatWithErrors": false, 6 | "indentStyle": "space", 7 | "indentWidth": 2, 8 | "lineEnding": "lf", 9 | "lineWidth": 80, 10 | "attributePosition": "auto", 11 | "ignore": ["dist/", "coverage/", "**/.next/**", "**/*.yaml"] 12 | }, 13 | "organizeImports": { 14 | "enabled": true 15 | }, 16 | "linter": { 17 | "enabled": true, 18 | "rules": { 19 | "recommended": true, 20 | "complexity": { 21 | "noForEach": "off" 22 | } 23 | }, 24 | "ignore": ["dist/", "coverage/", "**/.next/**", "**/*.yaml"] 25 | }, 26 | "javascript": { 27 | "formatter": { 28 | "jsxQuoteStyle": "double", 29 | "quoteProperties": "asNeeded", 30 | "trailingCommas": "es5", 31 | "semicolons": "always", 32 | "arrowParentheses": "always", 33 | "bracketSpacing": true, 34 | "bracketSameLine": false, 35 | "quoteStyle": "double", 36 | "attributePosition": "auto" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /demo/components/accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as AccordionPrimitive from "@radix-ui/react-accordion"; 2 | import { ChevronDownIcon } from "@radix-ui/react-icons"; 3 | import { clsx } from "clsx"; 4 | import React from "react"; 5 | 6 | interface AccordionItem { 7 | header: string; 8 | content: string; 9 | } 10 | 11 | const items: AccordionItem[] = [ 12 | { 13 | header: "What is Radix UI?", 14 | content: 15 | "Radix Primitives is a low-level UI component library with a focus on accessibility, customization and developer experience. You can use these components either as the base layer of your design system, or adopt them incrementally.", 16 | }, 17 | { 18 | header: "Why are goats so popular at Vercel", 19 | content: 20 | "Goats are popular at Vercel for a few reasons. First, they provide a healthier and more sustainable solution for grass cutting and vegetation control. Additionally, goats are able to traverse very steep terrain. This makes them perfect for providing maintenance in areas that are difficult to access. Finally, their presence is said to provide a calming atmosphere, which is especially beneficial in stressful engineering environments.", 21 | }, 22 | { 23 | header: "Is it accessible?", 24 | content: "Yes!", 25 | }, 26 | ]; 27 | 28 | const Accordion = () => { 29 | return ( 30 | 35 | {items.map(({ header, content }, i) => ( 36 | 41 | 42 | 49 | 50 | {header} 51 | 52 | 58 | 59 | 60 | 66 |
72 | {content} 73 |
74 |
75 |
76 | ))} 77 |
78 | ); 79 | }; 80 | 81 | export { Accordion }; 82 | -------------------------------------------------------------------------------- /demo/components/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; 3 | import { clsx } from "clsx"; 4 | import React, { Fragment, useState } from "react"; 5 | import Button from "./shared/button"; 6 | 7 | const AlertDialog = () => { 8 | const [isOpen, setIsOpen] = useState(false); 9 | 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 26 | 30 | 31 | 40 | 50 | 51 | Are you absolutely sure? 52 | 53 | 54 | This action cannot be undone. This will permanently delete your 55 | account and remove your data from our servers. 56 | 57 |
58 | 66 | Cancel 67 | 68 | 76 | Confirm 77 | 78 |
79 |
80 |
81 |
82 |
83 |
84 | ); 85 | }; 86 | 87 | export { AlertDialog }; 88 | -------------------------------------------------------------------------------- /demo/components/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; 2 | import { clsx } from "clsx"; 3 | import React, { Fragment } from "react"; 4 | 5 | const AspectRatio = () => { 6 | return ( 7 | 8 | 12 |
13 |

14 | Vancouver 15 |

16 |
17 |
23 | {/* eslint-disable-next-line @next/next/no-img-element */} 24 | Vancouver by Matt Wang 29 |
30 |
31 |
32 | ); 33 | }; 34 | 35 | export { AspectRatio }; 36 | -------------------------------------------------------------------------------- /demo/components/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as AvatarPrimitive from "@radix-ui/react-avatar"; 2 | import { clsx } from "clsx"; 3 | import React, { Fragment } from "react"; 4 | import { getRandomInitials } from "../utils/random"; 5 | 6 | enum Variant { 7 | Circle = 0, 8 | Rounded = 1, 9 | } 10 | 11 | type AvatarProps = { 12 | variant: Variant; 13 | renderInvalidUrls?: boolean; 14 | isOnline?: boolean; 15 | }; 16 | 17 | const users = [ 18 | "https://images.unsplash.com/photo-1573607217032-18299406d100?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=830&q=80", 19 | "https://images.unsplash.com/photo-1594089426440-ab4513b4d0d0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80", 20 | "https://images.unsplash.com/photo-1549237511-6b64e006ce65?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80", 21 | "https://images.unsplash.com/photo-1546456073-ea246a7bd25f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80", 22 | ]; 23 | 24 | const Avatar = ({ 25 | variant, 26 | isOnline, 27 | renderInvalidUrls = false, 28 | }: AvatarProps) => { 29 | const urls = renderInvalidUrls 30 | ? Array.from({ length: users.length }, () => "") 31 | : users; 32 | 33 | return ( 34 | 35 | {urls.map((src) => ( 36 | 40 | 51 | {isOnline && ( 52 |
61 | 62 |
63 | )} 64 | 74 | 75 | {getRandomInitials()} 76 | 77 | 78 |
79 | ))} 80 |
81 | ); 82 | }; 83 | 84 | Avatar.variant = Variant; 85 | export { Avatar }; 86 | -------------------------------------------------------------------------------- /demo/components/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 2 | import { CheckIcon } from "@radix-ui/react-icons"; 3 | import * as LabelPrimitive from "@radix-ui/react-label"; 4 | import { clsx } from "clsx"; 5 | import React from "react"; 6 | 7 | const Checkbox = () => { 8 | return ( 9 |
10 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | Accept terms and conditions 29 | 30 |
31 | ); 32 | }; 33 | 34 | export { Checkbox }; 35 | -------------------------------------------------------------------------------- /demo/components/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; 2 | import { PlayIcon, Share2Icon, TriangleRightIcon } from "@radix-ui/react-icons"; 3 | import { clsx } from "clsx"; 4 | import React from "react"; 5 | 6 | const Collapsible = () => { 7 | const [isOpen, setIsOpen] = React.useState(true); 8 | 9 | return ( 10 | 11 | 18 |
My Playlists
19 | 20 |
21 | 27 |
28 | {["80s Synthwave", "Maximale Konzentration", "高人氣金曲"].map( 29 | (title) => ( 30 |
38 | {title} 39 |
40 | 41 | 42 |
43 |
44 | ) 45 | )} 46 |
47 |
48 |
49 | ); 50 | }; 51 | 52 | export { Collapsible }; 53 | -------------------------------------------------------------------------------- /demo/components/context-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; 2 | import { 3 | CaretRightIcon, 4 | CheckIcon, 5 | CropIcon, 6 | EyeClosedIcon, 7 | EyeOpenIcon, 8 | FileIcon, 9 | FrameIcon, 10 | GridIcon, 11 | Link2Icon, 12 | MixerHorizontalIcon, 13 | PersonIcon, 14 | TransparencyGridIcon, 15 | } from "@radix-ui/react-icons"; 16 | import { clsx } from "clsx"; 17 | import React, { type ReactNode, useState } from "react"; 18 | import Button from "./shared/button"; 19 | 20 | interface RadixMenuItem { 21 | label: string; 22 | shortcut?: string; 23 | icon?: ReactNode; 24 | } 25 | 26 | interface User { 27 | name: string; 28 | url?: string; 29 | } 30 | 31 | const generalMenuItems: RadixMenuItem[] = [ 32 | { 33 | label: "New File", 34 | icon: , 35 | shortcut: "⌘+N", 36 | }, 37 | { 38 | label: "Settings", 39 | icon: , 40 | shortcut: "⌘+,", 41 | }, 42 | ]; 43 | 44 | const regionToolMenuItems: RadixMenuItem[] = [ 45 | { 46 | label: "Frame", 47 | icon: , 48 | shortcut: "⌘+F", 49 | }, 50 | { 51 | label: "Crop", 52 | icon: , 53 | shortcut: "⌘+S", 54 | }, 55 | ]; 56 | 57 | const users: User[] = [ 58 | { 59 | name: "Adam", 60 | url: "https://github.com/adamwathan.png", 61 | }, 62 | { 63 | name: "Steve", 64 | url: "https://github.com/steveschoger.png", 65 | }, 66 | { 67 | name: "Robin", 68 | url: "https://github.com/robinmalfait.png", 69 | }, 70 | ]; 71 | 72 | const ContextMenu = () => { 73 | const [showGrid, setShowGrid] = useState(false); 74 | const [showUi, setShowUi] = useState(false); 75 | 76 | return ( 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 91 | {generalMenuItems.map(({ label, icon, shortcut }) => ( 92 | 99 | {icon} 100 | 101 | {label} 102 | 103 | {shortcut && {shortcut}} 104 | 105 | ))} 106 | 107 | 108 | 109 | { 112 | if (typeof state === "boolean") { 113 | setShowGrid(state); 114 | } 115 | }} 116 | className={clsx( 117 | "flex w-full cursor-default select-none items-center rounded-md px-2 py-2 text-xs outline-hidden", 118 | "text-gray-400 focus:bg-gray-50 dark:text-gray-500 dark:focus:bg-gray-900" 119 | )} 120 | > 121 | {showGrid ? ( 122 | 123 | ) : ( 124 | 125 | )} 126 | 127 | Show Grid 128 | 129 | 130 | 131 | 132 | 133 | 134 | { 137 | if (typeof state === "boolean") { 138 | setShowUi(state); 139 | } 140 | }} 141 | className={clsx( 142 | "flex w-full cursor-default select-none items-center rounded-md px-2 py-2 text-xs outline-hidden", 143 | "text-gray-400 focus:bg-gray-50 dark:text-gray-500 dark:focus:bg-gray-900" 144 | )} 145 | > 146 | {showUi ? ( 147 | 148 | ) : ( 149 | 150 | )} 151 | 152 | Show UI 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | Region Tools 163 | 164 | 165 | {regionToolMenuItems.map(({ label, icon, shortcut }) => ( 166 | 173 | {icon} 174 | 175 | {label} 176 | 177 | {shortcut && {shortcut}} 178 | 179 | ))} 180 | 181 | 182 | 183 | 184 | 190 | 191 | 192 | Share 193 | 194 | 195 | 196 | 197 | 204 | {users.map(({ name, url }, i) => ( 205 | 212 | {url ? ( 213 | 218 | ) : ( 219 | 220 | )} 221 | 222 | {name} 223 | 224 | 225 | ))} 226 | 227 | 228 | 229 | 230 | 231 | 232 |
233 | ); 234 | }; 235 | 236 | export { ContextMenu }; 237 | -------------------------------------------------------------------------------- /demo/components/dialog.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import * as DialogPrimitive from "@radix-ui/react-dialog"; 3 | import { Cross1Icon } from "@radix-ui/react-icons"; 4 | import { clsx } from "clsx"; 5 | import React, { Fragment, useState } from "react"; 6 | import Button from "./shared/button"; 7 | 8 | const Dialog = () => { 9 | const [isOpen, setIsOpen] = useState(false); 10 | 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 31 | 32 | 41 | 51 | 52 | Edit profile 53 | 54 | 55 | Make changes to your profile here. Click save when you're 56 | done. 57 | 58 |
59 |
60 | {/* Choose your favorite monster */} 61 | 67 | 79 |
80 |
81 | 87 | 99 |
100 |
101 | 102 |
103 | 111 | Save 112 | 113 |
114 | 115 | 121 | 122 | 123 |
124 |
125 |
126 |
127 |
128 | ); 129 | }; 130 | 131 | export { Dialog }; 132 | -------------------------------------------------------------------------------- /demo/components/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; 2 | import { 3 | CaretRightIcon, 4 | CheckIcon, 5 | CropIcon, 6 | EyeClosedIcon, 7 | EyeOpenIcon, 8 | FileIcon, 9 | FrameIcon, 10 | GridIcon, 11 | Link2Icon, 12 | MixerHorizontalIcon, 13 | PersonIcon, 14 | TransparencyGridIcon, 15 | } from "@radix-ui/react-icons"; 16 | import { clsx } from "clsx"; 17 | import React, { type ReactNode, useState } from "react"; 18 | import Button from "./shared/button"; 19 | 20 | interface RadixMenuItem { 21 | label: string; 22 | shortcut?: string; 23 | icon?: ReactNode; 24 | } 25 | 26 | interface User { 27 | name: string; 28 | url?: string; 29 | } 30 | 31 | const generalMenuItems: RadixMenuItem[] = [ 32 | { 33 | label: "New File", 34 | icon: , 35 | shortcut: "⌘+N", 36 | }, 37 | { 38 | label: "Settings", 39 | icon: , 40 | shortcut: "⌘+,", 41 | }, 42 | ]; 43 | 44 | const regionToolMenuItems: RadixMenuItem[] = [ 45 | { 46 | label: "Frame", 47 | icon: , 48 | shortcut: "⌘+F", 49 | }, 50 | { 51 | label: "Crop", 52 | icon: , 53 | shortcut: "⌘+S", 54 | }, 55 | ]; 56 | 57 | const users: User[] = [ 58 | { 59 | name: "Adam", 60 | url: "https://github.com/adamwathan.png", 61 | }, 62 | { 63 | name: "Steve", 64 | url: "https://github.com/steveschoger.png", 65 | }, 66 | { 67 | name: "Robin", 68 | url: "https://github.com/robinmalfait.png", 69 | }, 70 | ]; 71 | 72 | const DropdownMenu = () => { 73 | const [showGrid, setShowGrid] = useState(false); 74 | const [showUi, setShowUi] = useState(false); 75 | 76 | return ( 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 93 | {generalMenuItems.map(({ label, icon, shortcut }) => ( 94 | 101 | {icon} 102 | 103 | {label} 104 | 105 | {shortcut && {shortcut}} 106 | 107 | ))} 108 | 109 | 110 | 111 | { 114 | if (typeof state === "boolean") { 115 | setShowGrid(state); 116 | } 117 | }} 118 | className={clsx( 119 | "flex w-full cursor-default select-none items-center rounded-md px-2 py-2 text-xs outline-hidden", 120 | "text-gray-400 focus:bg-gray-50 dark:text-gray-500 dark:focus:bg-gray-900" 121 | )} 122 | > 123 | {showGrid ? ( 124 | 125 | ) : ( 126 | 127 | )} 128 | 129 | Show Grid 130 | 131 | 132 | 133 | 134 | 135 | 136 | { 139 | if (typeof state === "boolean") { 140 | setShowUi(state); 141 | } 142 | }} 143 | className={clsx( 144 | "flex w-full cursor-default select-none items-center rounded-md px-2 py-2 text-xs outline-hidden", 145 | "text-gray-400 focus:bg-gray-50 dark:text-gray-500 dark:focus:bg-gray-900" 146 | )} 147 | > 148 | {showUi ? ( 149 | 150 | ) : ( 151 | 152 | )} 153 | 154 | Show UI 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Region Tools 165 | 166 | 167 | {regionToolMenuItems.map(({ label, icon, shortcut }) => ( 168 | 175 | {icon} 176 | 177 | {label} 178 | 179 | {shortcut && {shortcut}} 180 | 181 | ))} 182 | 183 | 184 | 185 | 186 | 192 | 193 | 194 | Share 195 | 196 | 197 | 198 | 199 | 206 | {users.map(({ name, url }) => ( 207 | 214 | {url ? ( 215 | 220 | ) : ( 221 | 222 | )} 223 | 224 | {name} 225 | 226 | 227 | ))} 228 | 229 | 230 | 231 | 232 | 233 | 234 |
235 | ); 236 | }; 237 | 238 | export { DropdownMenu }; 239 | -------------------------------------------------------------------------------- /demo/components/hover-card.tsx: -------------------------------------------------------------------------------- 1 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; 2 | import { clsx } from "clsx"; 3 | import React from "react"; 4 | 5 | const TailwindLogo = () => ( 6 | 12 | Tailwind Logo 13 | 17 | 18 | ); 19 | 20 | const HoverCard = () => { 21 | return ( 22 | 23 | 24 |
29 | 30 |
31 |
32 | 42 | 43 | 44 |
45 |
50 | 51 |
52 | 53 |
54 |

55 | Tailwind CSS 56 |

57 | 58 |

59 | A utility-first CSS framework for rapidly building custom user 60 | interfaces. 61 |

62 |
63 |
64 |
65 |
66 | ); 67 | }; 68 | 69 | export { HoverCard }; 70 | -------------------------------------------------------------------------------- /demo/components/menubar.tsx: -------------------------------------------------------------------------------- 1 | import * as MenubarPrimitive from "@radix-ui/react-menubar"; 2 | import { 3 | CheckIcon, 4 | ChevronRightIcon, 5 | DotFilledIcon, 6 | } from "@radix-ui/react-icons"; 7 | import { clsx } from "clsx"; 8 | import React from "react"; 9 | 10 | type MenubarSeparatorProps = Omit< 11 | MenubarPrimitive.MenubarSeparatorProps & React.RefAttributes, 12 | "className" 13 | >; 14 | 15 | const MenubarSeparator = ({ children, ...rest }: MenubarSeparatorProps) => ( 16 | 20 | {children} 21 | 22 | ); 23 | 24 | type MenubarTriggerProps = Omit< 25 | MenubarPrimitive.MenubarTriggerProps & React.RefAttributes, 26 | "className" 27 | >; 28 | 29 | const MenubarTrigger = ({ children, ...rest }: MenubarTriggerProps) => ( 30 | 41 | {children} 42 | 43 | ); 44 | 45 | type MenubarSubTriggerProps = Omit< 46 | MenubarPrimitive.MenubarSubTriggerProps & React.RefAttributes, 47 | "className" 48 | >; 49 | 50 | const MenubarSubTrigger = ({ children, ...rest }: MenubarSubTriggerProps) => ( 51 | 61 |
62 | {children} 63 | 64 |
65 |
66 | ); 67 | 68 | type MenubarItemProps = Omit< 69 | MenubarPrimitive.MenubarItemProps & 70 | React.RefAttributes & { shortcut?: string }, 71 | "className" 72 | >; 73 | 74 | const MenubarItem = ({ children, shortcut, ...rest }: MenubarItemProps) => ( 75 | 86 |
87 | {children} 88 | {shortcut && ( 89 | 90 | {shortcut} 91 | 92 | )} 93 |
94 |
95 | ); 96 | 97 | type MenubarCheckboxItemProps = Omit< 98 | MenubarPrimitive.MenubarCheckboxItemProps & 99 | React.RefAttributes, 100 | "className" 101 | >; 102 | 103 | const MenubarCheckboxItem = ({ 104 | children, 105 | ...rest 106 | }: MenubarCheckboxItemProps) => ( 107 | 118 |
119 |
120 | 121 | 122 | 123 |
124 |
{children}
125 |
126 |
127 | ); 128 | 129 | type MenubarRadioItemProps = Omit< 130 | MenubarPrimitive.MenubarRadioItemProps & React.RefAttributes, 131 | "className" 132 | >; 133 | 134 | const MenubarRadioItem = ({ children, ...rest }: MenubarRadioItemProps) => ( 135 | 146 |
147 |
148 | 149 | 150 | 151 |
152 |
{children}
153 |
154 |
155 | ); 156 | 157 | const RADIO_ITEMS = ["rauchg", "steventey", "0xca0a"]; 158 | const CHECK_ITEMS = ["Always Show Bookmarks Bar", "Always Show Full URLs"]; 159 | 160 | const Menubar = () => { 161 | const [checkedSelection, setCheckedSelection] = React.useState([ 162 | CHECK_ITEMS[1], 163 | ]); 164 | const [radioSelection, setRadioSelection] = React.useState(RADIO_ITEMS[2]); 165 | 166 | const contentClasses = "bg-white dark:bg-gray-800 rounded-md p-1"; 167 | 168 | return ( 169 | 170 | 171 | File 172 | 173 | 179 | New Tab 180 | New Window 181 | New Incognito Window 182 | 183 | 184 | Share 185 | 186 | 191 | Email Link 192 | Messages 193 | Notes 194 | 195 | 196 | 197 | 198 | 199 | Print… 200 | 201 | 202 | 203 | 204 | 205 | Edit 206 | 207 | 213 | Undo 214 | Redo 215 | 216 | 217 | Find 218 | 219 | 220 | 225 | Search the web… 226 | 227 | 228 | Find… 229 | Find Next 230 | Find Previous 231 | 232 | 233 | 234 | 235 | Cut 236 | Copy 237 | Paste 238 | 239 | 240 | 241 | 242 | 243 | View 244 | 245 | 251 | {CHECK_ITEMS.map((item) => ( 252 | 256 | setCheckedSelection((current) => 257 | current.includes(item) 258 | ? current.filter((el) => el !== item) 259 | : current.concat(item) 260 | ) 261 | } 262 | > 263 | {item} 264 | 265 | ))} 266 | 267 | 268 | Reload 269 | Force Reload 270 | 271 | 272 | Toggle Fullscreen 273 | 274 | Hide Sidebar 275 | 276 | 277 | 278 | 279 | 280 | Profiles 281 | 282 | 288 | 292 | {RADIO_ITEMS.map((item) => ( 293 | 294 | {item} 295 | 296 | ))} 297 | 298 | 299 | Edit… 300 | 301 | Add Profile… 302 | 303 | 304 | 305 | 306 | 307 | ); 308 | }; 309 | 310 | export { Menubar }; 311 | -------------------------------------------------------------------------------- /demo/components/navigation-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"; 2 | import { clsx } from "clsx"; 3 | import React from "react"; 4 | 5 | const NavigationMenu = () => { 6 | return ( 7 | 8 | 9 | 10 | 18 | Overview 19 | 20 | 21 | 30 |
31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 53 | Resources 54 | 55 | 56 | 65 |
66 |
67 | 74 | 75 | Tailwind CSS 76 | 77 | 78 |
79 | A utility-first CSS framework for rapidly building custom 80 | user interfaces. 81 |
82 |
83 | 84 | 91 | 92 | Radix UI 93 | 94 | 95 |
96 | An open-source UI component library for building 97 | high-quality, accessible design systems and web apps. 98 |
99 |
100 |
101 |
102 |
103 |
104 | 105 | 106 | 113 | GitHub 114 | 115 | 116 | 117 | 126 |
127 | 128 | 129 | 130 |
139 | 149 |
150 | 151 | ); 152 | }; 153 | 154 | export { NavigationMenu }; 155 | -------------------------------------------------------------------------------- /demo/components/popover.tsx: -------------------------------------------------------------------------------- 1 | import { Cross1Icon } from "@radix-ui/react-icons"; 2 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 3 | import { clsx } from "clsx"; 4 | import React from "react"; 5 | import Button from "./shared/button"; 6 | 7 | const items = [ 8 | { 9 | id: "width", 10 | label: "Width", 11 | defaultValue: "100%", 12 | }, 13 | { 14 | id: "max-width", 15 | label: "Max. Width", 16 | defaultValue: "300px", 17 | }, 18 | { 19 | id: "height", 20 | label: "Height", 21 | defaultValue: "25px", 22 | }, 23 | { 24 | id: "max-height", 25 | label: "Max. Height", 26 | defaultValue: "none", 27 | }, 28 | ]; 29 | 30 | const Popover = () => { 31 | return ( 32 |
33 | 34 | 35 | 36 | 37 | 46 | 47 |

48 | Dimensions 49 |

50 | 51 |
52 | {items.map(({ id, label, defaultValue }) => { 53 | return ( 54 |
58 | 64 | 76 |
77 | ); 78 | })} 79 |
80 | 81 | 87 | 88 | 89 |
90 |
91 |
92 | ); 93 | }; 94 | 95 | export { Popover }; 96 | -------------------------------------------------------------------------------- /demo/components/progress.tsx: -------------------------------------------------------------------------------- 1 | import * as ProgressPrimitive from "@radix-ui/react-progress"; 2 | import React, { useEffect } from "react"; 3 | import { getRandomArbitrary } from "../utils/math"; 4 | 5 | const Progress = () => { 6 | const [progress, setProgress] = React.useState(60); 7 | 8 | useEffect(() => { 9 | let timerId: ReturnType = null; 10 | 11 | timerId = setInterval(() => { 12 | const p = Math.ceil(getRandomArbitrary(0, 100) / 10) * 10; 13 | setProgress(p); 14 | }, 5000); 15 | 16 | return () => { 17 | if (timerId) { 18 | clearInterval(timerId); 19 | } 20 | }; 21 | }, []); 22 | 23 | return ( 24 | 28 | 32 | 33 | ); 34 | }; 35 | 36 | export { Progress }; 37 | -------------------------------------------------------------------------------- /demo/components/radio-group.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import React from "react"; 3 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; 4 | 5 | const starters = [ 6 | { id: "red", title: "Bulbasaur" }, 7 | { id: "green", title: "Charmader" }, 8 | { id: "blue", title: "Squirtle" }, 9 | ]; 10 | 11 | const RadioGroup = () => { 12 | const [_value, setValue] = React.useState(starters[0].title); 13 | 14 | return ( 15 |
16 | 17 | Choose your starter 18 | 19 | 24 |
25 | {starters.map((pokemon) => ( 26 |
27 | 39 | 40 |
41 | 42 | 43 | 49 |
50 | ))} 51 |
52 | 53 | 54 | ); 55 | }; 56 | 57 | export { RadioGroup }; 58 | -------------------------------------------------------------------------------- /demo/components/select.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | CheckIcon, 3 | ChevronDownIcon, 4 | ChevronUpIcon, 5 | } from "@radix-ui/react-icons"; 6 | import * as SelectPrimitive from "@radix-ui/react-select"; 7 | import { clsx } from "clsx"; 8 | import React from "react"; 9 | import Button from "./shared/button"; 10 | 11 | const Select = () => { 12 | return ( 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {["Apple", "Banana", "Blueberry", "Strawberry", "Grapes"].map( 29 | (f) => ( 30 | 40 | {f} 41 | 42 | 43 | 44 | 45 | ) 46 | )} 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | export { Select }; 58 | -------------------------------------------------------------------------------- /demo/components/shared/button.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import React from "react"; 3 | 4 | type Props = Omit, "className"> & {}; 5 | 6 | const Button = React.forwardRef( 7 | ({ children, ...props }, ref) => ( 8 | 26 | ) 27 | ); 28 | 29 | Button.displayName = "Button"; 30 | export default Button; 31 | -------------------------------------------------------------------------------- /demo/components/shared/command-menu.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import React, { useEffect, useState } from "react"; 3 | import { Command } from "cmdk"; 4 | import { DocumentMagnifyingGlassIcon } from "@heroicons/react/24/outline"; 5 | import { DividerVerticalIcon } from "@radix-ui/react-icons"; 6 | 7 | interface CommandMenuProps { 8 | items: T[]; 9 | onSelect?: (data: { 10 | item: T; 11 | modifiers: { 12 | control: boolean; 13 | }; 14 | }) => void; 15 | } 16 | 17 | function CommandMenu({ 18 | items, 19 | onSelect, 20 | }: CommandMenuProps) { 21 | const [open, setOpen] = useState(false); 22 | const [isHoldingModifier, setIsHoldingModifier] = useState(false); 23 | 24 | useEffect(() => { 25 | const down = (e: KeyboardEvent) => { 26 | // Toggle the menu when ⌘K is pressed 27 | if (e.key === "k" && e.metaKey) { 28 | e.preventDefault(); 29 | setOpen((open) => !open); 30 | } 31 | }; 32 | 33 | document.addEventListener("keydown", down); 34 | return () => document.removeEventListener("keydown", down); 35 | }, []); 36 | 37 | useEffect(() => { 38 | const keyDown = (e: KeyboardEvent) => { 39 | if (e.key === "Control") { 40 | setIsHoldingModifier(true); 41 | } 42 | }; 43 | 44 | const keyUp = (e: KeyboardEvent) => { 45 | if (e.key === "Control") { 46 | setIsHoldingModifier(false); 47 | } 48 | }; 49 | 50 | document.addEventListener("keydown", keyDown); 51 | document.addEventListener("keyup", keyUp); 52 | return () => { 53 | document.removeEventListener("keydown", keyDown); 54 | document.removeEventListener("keyup", keyUp); 55 | }; 56 | }, []); 57 | 58 | return ( 59 | 71 |
72 | 81 | { 83 | if (e.key === "Escape") { 84 | setOpen(false); 85 | } 86 | }} 87 | onClick={() => setOpen(false)} 88 | className="select-none hover:cursor-pointer w-auto h-5 flex items-center justify-center absolute rounded-md text-[0.6rem] right-5 top-1/2 text-gray-700 dark:text-gray-300 font-bold -translate-y-1/2 bg-black/10 dark:bg-white/10 px-1.5 leading-none" 89 | > 90 | ESC 91 | 92 |
93 | 97 | 105 | 106 | 107 | No Results 108 | 109 | {items.map((item) => ( 110 | { 113 | onSelect({ item, modifiers: { control: isHoldingModifier } }); 114 | setOpen(false); 115 | }} 116 | className={clsx( 117 | "px-3 py-3", 118 | "cursor-pointer", 119 | "flex items-center rounded-md text-sm text-gray-700 dark:text-gray-300 font-medium", 120 | "aria-selected:bg-black/10 dark:aria-selected:bg-white/10", 121 | "focus:outline-hidden select-none" 122 | )} 123 | > 124 | {item.label} 125 | 126 | ))} 127 | 128 |
129 |
130 | 135 | I am an SVG 136 | 137 | 138 | 139 |
140 |
141 | Go to 142 | 143 | ↵ 144 | 145 |
146 | 147 |
148 | Open code 149 |
150 | 151 | ⌃ 152 | 153 | + 154 | 155 | ↵ 156 | 157 |
158 |
159 |
160 |
161 | 162 | ); 163 | } 164 | 165 | export { CommandMenu }; 166 | -------------------------------------------------------------------------------- /demo/components/shared/demo-card.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import type { ReactNode } from "react"; 3 | 4 | enum Variant { 5 | Default = 0, 6 | ItemsCenter = 1, 7 | JustifyCenter = 2, 8 | } 9 | 10 | type DemoCardProps = { 11 | variant?: Variant; 12 | isNew?: boolean; 13 | children: ReactNode; 14 | data: { 15 | title: string; 16 | link: string; 17 | }; 18 | }; 19 | 20 | const DemoCard = ({ 21 | variant = Variant.Default, 22 | isNew, 23 | children, 24 | data: { title, link }, 25 | }: DemoCardProps) => { 26 | const id = title.replace(" ", "_").toLowerCase(); 27 | 28 | return ( 29 |
30 |
43 | {children} 44 |
45 |
46 | 50 | {title} 51 | 52 | {isNew && ( 53 | 54 | NEW 55 | 56 | )} 57 |
58 | 64 | Code 65 | 66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | DemoCard.variant = Variant; 73 | export { DemoCard }; 74 | -------------------------------------------------------------------------------- /demo/components/shared/theme-switcher.tsx: -------------------------------------------------------------------------------- 1 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; 2 | import { Half2Icon, MoonIcon, SunIcon } from "@radix-ui/react-icons"; 3 | import { clsx } from "clsx"; 4 | import React, { useEffect, useState } from "react"; 5 | 6 | const themes = [ 7 | { 8 | key: "light", 9 | label: "Light", 10 | icon: , 11 | }, 12 | { 13 | key: "dark", 14 | label: "Dark", 15 | icon: , 16 | }, 17 | 18 | { 19 | key: "system", 20 | label: "System", 21 | icon: , 22 | }, 23 | ]; 24 | 25 | const ThemeSwitcher = () => { 26 | const [preferredTheme, setPreferredTheme] = useState(null); 27 | 28 | useEffect(() => { 29 | try { 30 | const found = localStorage.getItem("theme"); 31 | setPreferredTheme(found); 32 | } catch (error) {} 33 | }, []); 34 | 35 | useEffect(() => { 36 | const prefersDarkQuery = window.matchMedia("(prefers-color-scheme: dark)"); 37 | const updateTheme = (_e: MediaQueryListEvent) => { 38 | setPreferredTheme("system"); 39 | }; 40 | prefersDarkQuery.addEventListener("change", updateTheme); 41 | 42 | return () => { 43 | prefersDarkQuery.removeEventListener("change", updateTheme); 44 | }; 45 | }, []); 46 | 47 | return ( 48 |
49 | 50 | 58 | {(() => { 59 | switch (preferredTheme) { 60 | case "light": 61 | return ( 62 | 63 | ); 64 | case "dark": 65 | return ( 66 | 67 | ); 68 | default: 69 | return ( 70 | 71 | ); 72 | } 73 | })()} 74 | {/* {isDark ? "dark" : "light"} */} 75 | 76 | 77 | 78 | 87 | {themes.map(({ key, label, icon }) => { 88 | return ( 89 | { 96 | ( 97 | window as unknown as Window & { 98 | __setPreferredTheme: (key: string) => void; 99 | } 100 | ).__setPreferredTheme(key); 101 | setPreferredTheme(key); 102 | }} 103 | > 104 | {React.cloneElement(icon, { 105 | className: "w-5 h-5 mr-2 text-gray-700 dark:text-gray-300", 106 | })} 107 | 108 | {label} 109 | 110 | 111 | ); 112 | })} 113 | 114 | 115 | 116 |
117 | ); 118 | }; 119 | 120 | export { ThemeSwitcher }; 121 | -------------------------------------------------------------------------------- /demo/components/slider.tsx: -------------------------------------------------------------------------------- 1 | import * as SliderPrimitive from "@radix-ui/react-slider"; 2 | import { clsx } from "clsx"; 3 | import React from "react"; 4 | 5 | const Slider = () => { 6 | return ( 7 | 14 | 15 | 16 | 17 | 23 | 24 | ); 25 | }; 26 | 27 | export { Slider }; 28 | -------------------------------------------------------------------------------- /demo/components/switch.tsx: -------------------------------------------------------------------------------- 1 | import * as SwitchPrimitive from "@radix-ui/react-switch"; 2 | import { clsx } from "clsx"; 3 | import React from "react"; 4 | 5 | const Switch = () => { 6 | return ( 7 | 16 | 23 | 24 | ); 25 | }; 26 | 27 | export { Switch }; 28 | -------------------------------------------------------------------------------- /demo/components/tabs.tsx: -------------------------------------------------------------------------------- 1 | import * as TabsPrimitive from "@radix-ui/react-tabs"; 2 | import { clsx } from "clsx"; 3 | import React from "react"; 4 | 5 | interface Tab { 6 | title: string; 7 | value: string; 8 | } 9 | 10 | const tabs: Tab[] = [ 11 | { 12 | title: "Inbox", 13 | value: "tab1", 14 | }, 15 | { 16 | title: "Today", 17 | value: "tab2", 18 | }, 19 | 20 | { 21 | title: "Upcoming", 22 | value: "tab3", 23 | }, 24 | ]; 25 | 26 | const Tabs = () => { 27 | return ( 28 | 29 | 32 | {tabs.map(({ title, value }) => ( 33 | 47 | 53 | {title} 54 | 55 | 56 | ))} 57 | 58 | {tabs.map(({ value }) => ( 59 | 64 | 65 | { 66 | { 67 | tab1: "Your inbox is empty", 68 | tab2: "Make some coffee", 69 | tab3: "Order more coffee", 70 | }[value] 71 | } 72 | 73 | 74 | ))} 75 | 76 | ); 77 | }; 78 | 79 | export { Tabs }; 80 | -------------------------------------------------------------------------------- /demo/components/toast.tsx: -------------------------------------------------------------------------------- 1 | import * as ToastPrimitive from "@radix-ui/react-toast"; 2 | import { clsx } from "clsx"; 3 | import React from "react"; 4 | import { useMediaQuery } from "../hooks/use-media-query"; 5 | import Button from "./shared/button"; 6 | 7 | const Toast = () => { 8 | const [open, setOpen] = React.useState(false); 9 | const isMd = useMediaQuery("(min-width: 768px)"); 10 | 11 | return ( 12 | 13 | 27 | 43 |
44 |
45 |
46 | 47 | Pull Request Review 48 | 49 | 50 | Someone requested your review on{" "} 51 | repository/branch 52 | 53 |
54 |
55 |
56 |
57 |
58 | { 62 | e.preventDefault(); 63 | window.open("https://github.com"); 64 | }} 65 | > 66 | Review 67 | 68 |
69 |
70 | 71 | Dismiss 72 | 73 |
74 |
75 |
76 |
77 |
78 | 79 | 80 |
81 | ); 82 | }; 83 | 84 | export { Toast }; 85 | -------------------------------------------------------------------------------- /demo/components/toggle-group.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | FontBoldIcon, 3 | FontItalicIcon, 4 | UnderlineIcon, 5 | } from "@radix-ui/react-icons"; 6 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"; 7 | import { clsx } from "clsx"; 8 | import React, { type ReactElement } from "react"; 9 | 10 | interface ToggleItem { 11 | value: string; 12 | label: string; 13 | icon: ReactElement; 14 | } 15 | 16 | const settings: ToggleItem[] = [ 17 | { 18 | value: "bold", 19 | label: "Font bold", 20 | icon: , 21 | }, 22 | { 23 | value: "italic", 24 | label: "Font italic", 25 | icon: , 26 | }, 27 | { 28 | value: "underline", 29 | label: "Underline", 30 | icon: , 31 | }, 32 | ]; 33 | 34 | const ToggleGroup = () => { 35 | return ( 36 | 37 | {settings.map(({ value, label, icon }, i) => ( 38 | 50 | {React.cloneElement(icon, { 51 | className: "w-5 h-5 text-gray-700 dark:text-gray-100", 52 | })} 53 | 54 | ))} 55 | 56 | ); 57 | }; 58 | 59 | export { ToggleGroup }; 60 | -------------------------------------------------------------------------------- /demo/components/toggle.tsx: -------------------------------------------------------------------------------- 1 | import { StarFilledIcon, StarIcon } from "@radix-ui/react-icons"; 2 | import * as TogglePrimitive from "@radix-ui/react-toggle"; 3 | import React, { useState } from "react"; 4 | import Button from "./shared/button"; 5 | 6 | const Toggle = () => { 7 | const [starred, setStarred] = useState(false); 8 | 9 | return ( 10 | 15 | 23 | 24 | ); 25 | }; 26 | 27 | export { Toggle }; 28 | -------------------------------------------------------------------------------- /demo/components/toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | FontBoldIcon, 3 | FontItalicIcon, 4 | TextAlignCenterIcon, 5 | TextAlignLeftIcon, 6 | TextAlignRightIcon, 7 | UnderlineIcon, 8 | } from "@radix-ui/react-icons"; 9 | import * as ToolbarPrimitive from "@radix-ui/react-toolbar"; 10 | import { clsx } from "clsx"; 11 | import React, { type ReactElement } from "react"; 12 | 13 | interface ToggleItem { 14 | value: string; 15 | label: string; 16 | icon: ReactElement; 17 | } 18 | 19 | const fontSettings: ToggleItem[] = [ 20 | { 21 | value: "bold", 22 | label: "Font bold", 23 | icon: , 24 | }, 25 | { 26 | value: "italic", 27 | label: "Font italic", 28 | icon: , 29 | }, 30 | { 31 | value: "underline", 32 | label: "Underline", 33 | icon: , 34 | }, 35 | ]; 36 | 37 | const textSettings: ToggleItem[] = [ 38 | { 39 | value: "left", 40 | label: "Text left", 41 | icon: , 42 | }, 43 | { 44 | value: "center", 45 | label: "Text center", 46 | icon: , 47 | }, 48 | { 49 | value: "right", 50 | label: "Text right", 51 | icon: , 52 | }, 53 | ]; 54 | 55 | const Toolbar = () => { 56 | return ( 57 | 58 | 59 | {fontSettings.map(({ value, label, icon }, i) => ( 60 | 72 | {React.cloneElement(icon, { 73 | className: "w-5 h-5 text-gray-700 dark:text-gray-100", 74 | })} 75 | 76 | ))} 77 | 78 | 79 | 84 | {textSettings.map(({ value, label, icon }, i) => ( 85 | 97 | {React.cloneElement(icon, { 98 | className: "w-5 h-5 text-gray-700 dark:text-gray-100", 99 | })} 100 | 101 | ))} 102 | 103 | 104 | 105 | 106 | 107 | 115 | Edited 2 hours ago 116 | 117 | 118 | 119 | ); 120 | }; 121 | 122 | export { Toolbar }; 123 | -------------------------------------------------------------------------------- /demo/components/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"; 2 | import { clsx } from "clsx"; 3 | import React from "react"; 4 | import Button from "./shared/button"; 5 | 6 | const Tooltip = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | Sorry, but our princess is in another castle 27 | 28 | 29 | 30 | 31 | ); 32 | }; 33 | 34 | export { Tooltip }; 35 | -------------------------------------------------------------------------------- /demo/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @import url("https://rsms.me/inter/inter.css") layer(base); 2 | 3 | @import "tailwindcss"; 4 | 5 | @config '../tailwind.config.js'; 6 | 7 | @theme { 8 | --animate-accordion-slide-down: accordion-slide-down 300ms 9 | cubic-bezier(0.87, 0, 0.13, 1); 10 | 11 | @keyframes accordion-slide-down { 12 | 0% { 13 | height: 0; 14 | } 15 | 100% { 16 | height: var(--radix-accordion-content-height); 17 | } 18 | } 19 | 20 | --animate-accordion-slide-up: accordion-slide-up 300ms 21 | cubic-bezier(0.87, 0, 0.13, 1); 22 | 23 | @keyframes accordion-slide-up { 24 | 0% { 25 | height: var(--radix-accordion-content-height); 26 | } 27 | 100% { 28 | height: 0; 29 | } 30 | } 31 | 32 | --animate-collapsible-slide-down: collapsible-slide-down 300ms 33 | cubic-bezier(0.87, 0, 0.13, 1); 34 | 35 | @keyframes collapsible-slide-down { 36 | 0% { 37 | height: 0; 38 | } 39 | 100% { 40 | height: var(--radix-collapsible-content-height); 41 | } 42 | } 43 | 44 | --animate-collapsible-slide-up: collapsible-slide-up 300ms 45 | cubic-bezier(0.87, 0, 0.13, 1); 46 | 47 | @keyframes collapsible-slide-up { 48 | 0% { 49 | height: var(--radix-collapsible-content-height); 50 | } 51 | 100% { 52 | height: 0; 53 | } 54 | } 55 | } 56 | 57 | /* 58 | The default border color has changed to `currentColor` in Tailwind CSS v4, 59 | so we've added these compatibility styles to make sure everything still 60 | looks the same as it did with Tailwind CSS v3. 61 | 62 | If we ever want to remove these styles, we need to add an explicit border 63 | color utility to any element that depends on these defaults. 64 | */ 65 | @layer base { 66 | *, 67 | ::after, 68 | ::before, 69 | ::backdrop, 70 | ::file-selector-button { 71 | border-color: var(--color-gray-200, currentColor); 72 | } 73 | } 74 | 75 | @layer base { 76 | :root { 77 | @apply scroll-smooth font-display antialiased; 78 | } 79 | 80 | html { 81 | @apply bg-white; 82 | scrollbar-color: #00000080 transparent; 83 | scrollbar-width: auto; 84 | } 85 | 86 | html[class*="dark"] { 87 | @apply bg-gray-900; 88 | scrollbar-color: #ffffffb3 transparent; 89 | scrollbar-width: auto; 90 | } 91 | 92 | ::-webkit-scrollbar { 93 | height: 8px; 94 | width: 8px; 95 | } 96 | 97 | ::-webkit-scrollbar-track { 98 | @apply bg-transparent; 99 | } 100 | 101 | ::-webkit-scrollbar-thumb { 102 | @apply rounded-full bg-black/50 dark:bg-white/70; 103 | } 104 | 105 | ::-webkit-scrollbar-thumb:hover { 106 | @apply bg-black/60 dark:hover:bg-white/80; 107 | } 108 | 109 | button { 110 | /* Disables tap highlight color on Chrome */ 111 | -webkit-tap-highlight-color: transparent; 112 | } 113 | 114 | /* Input darkmode fix for checkbox / radio */ 115 | .dark [type="checkbox"]:checked, 116 | .dark [type="radio"]:checked { 117 | background-color: currentColor; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /demo/hooks/use-dark-mode.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useDarkMode = () => { 4 | const [isDark, setIsDark] = useState(true); 5 | 6 | useEffect(() => { 7 | const observer = new MutationObserver(() => { 8 | const containsDarkClass = 9 | document.documentElement.classList.contains("dark"); 10 | setIsDark(containsDarkClass); 11 | }); 12 | 13 | observer.observe(document.documentElement, { attributes: true }); 14 | return () => { 15 | observer.disconnect(); 16 | }; 17 | }, []); 18 | 19 | return isDark; 20 | }; 21 | -------------------------------------------------------------------------------- /demo/hooks/use-media-query.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export function useMediaQuery(query: string) { 4 | const [matches, setMatches] = useState(false); 5 | useEffect( 6 | () => { 7 | const mediaQuery = window.matchMedia(query); 8 | // Update the state with the current value 9 | setMatches(mediaQuery.matches); 10 | // Create an event listener 11 | const handler = (event: MediaQueryListEvent) => setMatches(event.matches); 12 | // Attach the event listener to know when the matches value changes 13 | mediaQuery.addEventListener("change", handler); 14 | // Remove the event listener on cleanup 15 | return () => mediaQuery.removeEventListener("change", handler); 16 | }, 17 | [query] // Empty array ensures effect is only run on mount and unmount 18 | ); 19 | return matches; 20 | } 21 | -------------------------------------------------------------------------------- /demo/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /demo/next.config.js: -------------------------------------------------------------------------------- 1 | const withTM = require("next-transpile-modules")(["react-github-btn"]); 2 | 3 | module.exports = withTM(); 4 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "NEXT_TELEMETRY_DISABLED=1 next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "@headlessui/react": "^1.7.18", 10 | "@heroicons/react": "^2.1.3", 11 | "@radix-ui/react-accordion": "^1.1.2", 12 | "@radix-ui/react-alert-dialog": "^1.0.5", 13 | "@radix-ui/react-aspect-ratio": "^1.0.3", 14 | "@radix-ui/react-avatar": "^1.0.4", 15 | "@radix-ui/react-checkbox": "^1.0.4", 16 | "@radix-ui/react-collapsible": "^1.0.3", 17 | "@radix-ui/react-context-menu": "^2.1.5", 18 | "@radix-ui/react-dialog": "^1.0.5", 19 | "@radix-ui/react-dropdown-menu": "^2.0.6", 20 | "@radix-ui/react-hover-card": "^1.0.7", 21 | "@radix-ui/react-icons": "^1.3.0", 22 | "@radix-ui/react-label": "^2.0.2", 23 | "@radix-ui/react-menubar": "^1.0.4", 24 | "@radix-ui/react-navigation-menu": "^1.1.4", 25 | "@radix-ui/react-popover": "^1.0.7", 26 | "@radix-ui/react-progress": "^1.0.3", 27 | "@radix-ui/react-radio-group": "^1.1.3", 28 | "@radix-ui/react-scroll-area": "^1.0.5", 29 | "@radix-ui/react-select": "^2.0.0", 30 | "@radix-ui/react-separator": "^1.0.3", 31 | "@radix-ui/react-slider": "^1.1.2", 32 | "@radix-ui/react-switch": "^1.0.3", 33 | "@radix-ui/react-tabs": "^1.0.4", 34 | "@radix-ui/react-toast": "^1.1.5", 35 | "@radix-ui/react-toggle": "^1.0.3", 36 | "@radix-ui/react-toggle-group": "^1.0.4", 37 | "@radix-ui/react-toolbar": "^1.0.4", 38 | "@radix-ui/react-tooltip": "^1.0.7", 39 | "@vercel/analytics": "^1.2.2", 40 | "clsx": "^2.1.0", 41 | "cmdk": "^1.0.0", 42 | "next": "14.1.4", 43 | "next-seo": "^6.5.0", 44 | "react": "^18.2.0", 45 | "react-dom": "^18.2.0", 46 | "react-github-btn": "^1.4.0" 47 | }, 48 | "devDependencies": { 49 | "@tailwindcss/forms": "^0.5.7", 50 | "@tailwindcss/postcss": "^4.0.0", 51 | "@types/node": "^20.11.30", 52 | "@types/react": "^18.2.69", 53 | "eslint": "8.57.0", 54 | "eslint-config-next": "14.1.4", 55 | "next-transpile-modules": "^10.0.1", 56 | "postcss": "^8.4.38", 57 | "prettier": "^3.2.5", 58 | "prettier-plugin-tailwindcss": "^0.6.11", 59 | "tailwindcss": "^4.0.0", 60 | "tailwindcss-radix": "^4.0.1", 61 | "typescript": "^5.4.3" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /demo/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../css/tailwind.css"; 2 | import { Analytics } from "@vercel/analytics/react"; 3 | 4 | function App({ Component, pageProps }) { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /demo/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-sync-scripts */ 2 | import Document, { Head, Html, Main, NextScript } from "next/document"; 3 | 4 | class MyDocument extends Document { 5 | static async getInitialProps(ctx) { 6 | const initialProps = await Document.getInitialProps(ctx); 7 | return { ...initialProps }; 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 | 14 | 15 |