@usace/groundwork 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.eslintrc.cjs +18 -0
  2. package/.github/workflows/deploy-docs.yml +59 -0
  3. package/.github/workflows/publish.yml +19 -0
  4. package/README.md +5 -0
  5. package/index.html +13 -0
  6. package/lib/catalyst/alert.jsx +110 -0
  7. package/lib/catalyst/avatar.jsx +64 -0
  8. package/lib/catalyst/badge.jsx +72 -0
  9. package/lib/catalyst/checkbox.jsx +160 -0
  10. package/lib/catalyst/dialog.jsx +101 -0
  11. package/lib/catalyst/dropdown.jsx +202 -0
  12. package/lib/catalyst/fieldset.jsx +87 -0
  13. package/lib/catalyst/input.jsx +78 -0
  14. package/lib/catalyst/listbox.jsx +195 -0
  15. package/lib/catalyst/pagination.jsx +75 -0
  16. package/lib/catalyst/radio.jsx +144 -0
  17. package/lib/catalyst/select.jsx +78 -0
  18. package/lib/catalyst/switch.jsx +203 -0
  19. package/lib/catalyst/textarea.jsx +59 -0
  20. package/lib/components/breadcrumbs.jsx +46 -0
  21. package/lib/components/button.jsx +219 -0
  22. package/lib/components/display/headings.jsx +39 -0
  23. package/lib/components/display/text.jsx +48 -0
  24. package/lib/components/hero.jsx +51 -0
  25. package/lib/components/layout/container.jsx +13 -0
  26. package/lib/components/layout/usace-box.jsx +26 -0
  27. package/lib/components/link.jsx +18 -0
  28. package/lib/components/table.jsx +146 -0
  29. package/lib/composite/footer/essayons.jsx +12 -0
  30. package/lib/composite/footer/footer-links.jsx +33 -0
  31. package/lib/composite/footer/index.jsx +148 -0
  32. package/lib/composite/footer/logo-banner.jsx +55 -0
  33. package/lib/composite/footer/social-links.jsx +60 -0
  34. package/lib/composite/header/index.jsx +83 -0
  35. package/lib/composite/header/navbar-links.jsx +113 -0
  36. package/lib/composite/header/usa-banner/USABanner.jsx +114 -0
  37. package/lib/composite/header/usa-banner/usa-banner.css +220 -0
  38. package/lib/composite/site-wrapper/index.jsx +52 -0
  39. package/lib/index.jsx +19 -0
  40. package/package.json +39 -0
  41. package/postcss.config.js +6 -0
  42. package/public/Magnifying_glass_icon.svg +5 -0
  43. package/public/armystar-logo-rb.svg +4 -0
  44. package/public/cwbi-logo.png +0 -0
  45. package/public/essayonscrest.png +0 -0
  46. package/public/icon-dot-gov.svg +1 -0
  47. package/public/icon-https.svg +1 -0
  48. package/public/nww-lucky-peak-dam.jpg +0 -0
  49. package/public/rsgis-logo.png +0 -0
  50. package/public/us_flag_small.png +0 -0
  51. package/public/usace-logo-color.svg +1 -0
  52. package/src/App.jsx +38 -0
  53. package/src/app-bundles/index.js +4 -0
  54. package/src/app-bundles/route-bundle.js +36 -0
  55. package/src/app-components/code-example.jsx +17 -0
  56. package/src/app-components/copy-button.jsx +32 -0
  57. package/src/app-components/props-table.jsx +43 -0
  58. package/src/app-components/sub-site-banner.jsx +15 -0
  59. package/src/app-pages/documentation/_docs-page.jsx +31 -0
  60. package/src/app-pages/documentation/_docs-sidebar.jsx +111 -0
  61. package/src/app-pages/documentation/_template.jsx +55 -0
  62. package/src/app-pages/documentation/app-shell/index.jsx +39 -0
  63. package/src/app-pages/documentation/app-shell/site-wrapper.jsx +190 -0
  64. package/src/app-pages/documentation/buttons/index.jsx +170 -0
  65. package/src/app-pages/documentation/index.jsx +57 -0
  66. package/src/app-pages/documentation/layout/container.jsx +68 -0
  67. package/src/app-pages/documentation/layout/index.jsx +52 -0
  68. package/src/app-pages/documentation/layout/usace-box.jsx +76 -0
  69. package/src/app-pages/documentation/navigation/breadcrumbs.jsx +119 -0
  70. package/src/app-pages/documentation/navigation/index.jsx +31 -0
  71. package/src/app-pages/documentation/types/index.jsx +31 -0
  72. package/src/app-pages/documentation/types/link.jsx +72 -0
  73. package/src/app-pages/home.jsx +71 -0
  74. package/src/index.css +3 -0
  75. package/src/main.jsx +18 -0
  76. package/tailwind.config.js +31 -0
  77. package/vite.config.js +46 -0
package/.eslintrc.cjs ADDED
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: { browser: true, es2020: true },
4
+ extends: [
5
+ 'eslint:recommended',
6
+ 'plugin:@typescript-eslint/recommended',
7
+ 'plugin:react-hooks/recommended',
8
+ ],
9
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
10
+ parser: '@typescript-eslint/parser',
11
+ plugins: ['react-refresh'],
12
+ rules: {
13
+ 'react-refresh/only-export-components': [
14
+ 'warn',
15
+ { allowConstantExport: true },
16
+ ],
17
+ },
18
+ }
@@ -0,0 +1,59 @@
1
+ name: Deploy to GH Pages
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build:
10
+ name: Build
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout repo
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Node
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 20
21
+
22
+ - name: Get Package Version
23
+ id: get_version
24
+ uses: stevenbenitez/get-package-version-action@v1
25
+
26
+ - name: Install dependencies
27
+ run: npm ci
28
+
29
+ - name: Build project
30
+ env:
31
+ VITE_HOMEPAGE: "https://usace.github.io/groundwork"
32
+ run: npm run build-docs
33
+
34
+ - name: Upload production-ready build files
35
+ uses: actions/upload-pages-artifact@v3
36
+ with:
37
+ path: ./docs
38
+
39
+ # Deploy job
40
+ deploy:
41
+ # Add a dependency to the build job
42
+ needs: build
43
+
44
+ # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
45
+ permissions:
46
+ pages: write # to deploy to Pages
47
+ id-token: write # to verify the deployment originates from an appropriate source
48
+
49
+ # Deploy to the github-pages environment
50
+ environment:
51
+ name: github-pages
52
+ url: ${{ steps.deployment.outputs.page_url }}
53
+
54
+ # Specify runner + deployment step
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - name: Deploy to GitHub Pages
58
+ id: deployment
59
+ uses: actions/deploy-pages@v4 # or the latest "vX.X.X" version tag for this action
@@ -0,0 +1,19 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish-to-npm:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-node@v4
13
+ with:
14
+ node-version: 20
15
+ registry-url: https://registry.npmjs.org
16
+ - run: npm ci
17
+ - run: npm run pub
18
+ env:
19
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Groundwork React Components
2
+
3
+ Groundwork provides a basic set of React components designed for use by USACE. We're working on making all of the components as accessible as possible to provide a consistent foundation for public-facing or internal applications.
4
+
5
+ See the documentation at https://usace.github.io/groundwork.
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/usace-logo-color.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Groundwork</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,110 @@
1
+ import {
2
+ Description as HeadlessDescription,
3
+ Dialog as HeadlessDialog,
4
+ DialogPanel as HeadlessDialogPanel,
5
+ DialogTitle as HeadlessDialogTitle,
6
+ Transition as HeadlessTransition,
7
+ TransitionChild as HeadlessTransitionChild,
8
+ } from '@headlessui/react'
9
+ import clsx from 'clsx'
10
+ import { Fragment } from 'react'
11
+ import { Text } from './text'
12
+
13
+ const sizes = {
14
+ xs: 'sm:max-w-xs',
15
+ sm: 'sm:max-w-sm',
16
+ md: 'sm:max-w-md',
17
+ lg: 'sm:max-w-lg',
18
+ xl: 'sm:max-w-xl',
19
+ '2xl': 'sm:max-w-2xl',
20
+ '3xl': 'sm:max-w-3xl',
21
+ '4xl': 'sm:max-w-4xl',
22
+ '5xl': 'sm:max-w-5xl',
23
+ }
24
+
25
+ export function Alert({ open, onClose, size = 'md', className, children, ...props }) {
26
+ return (
27
+ <HeadlessTransition appear as={Fragment} show={open} {...props}>
28
+ <HeadlessDialog onClose={onClose}>
29
+ <HeadlessTransitionChild
30
+ as={Fragment}
31
+ enter="ease-out duration-100"
32
+ enterFrom="opacity-0"
33
+ enterTo="opacity-100"
34
+ leave="ease-in duration-100"
35
+ leaveFrom="opacity-100"
36
+ leaveTo="opacity-0"
37
+ >
38
+ <div className="fixed inset-0 flex w-screen justify-center overflow-y-auto bg-zinc-950/15 px-2 py-2 focus:outline-0 sm:px-6 sm:py-8 lg:px-8 lg:py-16 dark:bg-zinc-950/50" />
39
+ </HeadlessTransitionChild>
40
+
41
+ <HeadlessTransitionChild
42
+ className="fixed inset-0 w-screen overflow-y-auto pt-6 sm:pt-0"
43
+ enter="ease-out duration-100"
44
+ enterFrom="opacity-0"
45
+ enterTo="opacity-100"
46
+ leave="ease-in duration-100"
47
+ leaveFrom="opacity-100"
48
+ leaveTo="opacity-0"
49
+ >
50
+ <div className="grid min-h-full grid-rows-[1fr_auto_1fr] justify-items-center p-8 sm:grid-rows-[1fr_auto_3fr] sm:p-4">
51
+ <HeadlessTransitionChild
52
+ as={HeadlessDialogPanel}
53
+ className={clsx(
54
+ className,
55
+ sizes[size],
56
+ 'row-start-2 w-full rounded-2xl bg-white p-8 shadow-lg ring-1 ring-zinc-950/10 sm:rounded-2xl sm:p-6 dark:bg-zinc-900 dark:ring-white/10 forced-colors:outline'
57
+ )}
58
+ enter="ease-out duration-100"
59
+ enterFrom="scale-95"
60
+ enterTo="scale-100"
61
+ leave="ease-in duration-100"
62
+ leaveFrom="scale-100"
63
+ leaveTo="scale-100"
64
+ >
65
+ {children}
66
+ </HeadlessTransitionChild>
67
+ </div>
68
+ </HeadlessTransitionChild>
69
+ </HeadlessDialog>
70
+ </HeadlessTransition>
71
+ )
72
+ }
73
+
74
+ export function AlertTitle({ className, ...props }) {
75
+ return (
76
+ <HeadlessDialogTitle
77
+ {...props}
78
+ className={clsx(
79
+ className,
80
+ 'text-balance text-center text-base/6 font-semibold text-zinc-950 sm:text-wrap sm:text-left sm:text-sm/6 dark:text-white'
81
+ )}
82
+ />
83
+ )
84
+ }
85
+
86
+ export function AlertDescription({ className, ...props }) {
87
+ return (
88
+ <HeadlessDescription
89
+ as={Text}
90
+ {...props}
91
+ className={clsx(className, 'mt-2 text-pretty text-center sm:text-left')}
92
+ />
93
+ )
94
+ }
95
+
96
+ export function AlertBody({ className, ...props }) {
97
+ return <div {...props} className={clsx(className, 'mt-4')} />
98
+ }
99
+
100
+ export function AlertActions({ className, ...props }) {
101
+ return (
102
+ <div
103
+ {...props}
104
+ className={clsx(
105
+ className,
106
+ 'mt-6 flex flex-col-reverse items-center justify-end gap-3 *:w-full sm:mt-4 sm:flex-row sm:*:w-auto'
107
+ )}
108
+ />
109
+ )
110
+ }
@@ -0,0 +1,64 @@
1
+ import { Button as HeadlessButton } from '@headlessui/react'
2
+ import clsx from 'clsx'
3
+ import React from 'react'
4
+ import { TouchTarget } from './button'
5
+ import { Link } from './link'
6
+
7
+ export function Avatar({ src = null, square = false, initials, alt = '', className, ...props }) {
8
+ return (
9
+ <span
10
+ data-slot="avatar"
11
+ className={clsx(
12
+ className,
13
+
14
+ // Basic layout
15
+ 'inline-grid align-middle *:col-start-1 *:row-start-1',
16
+
17
+ // Add the correct border radius
18
+ square ? 'rounded-[20%] *:rounded-[20%]' : 'rounded-full *:rounded-full'
19
+ )}
20
+ {...props}
21
+ >
22
+ {initials && (
23
+ <svg
24
+ className="select-none fill-current text-[48px] font-medium uppercase"
25
+ viewBox="0 0 100 100"
26
+ aria-hidden={alt ? undefined : 'true'}
27
+ >
28
+ {alt && <title>{alt}</title>}
29
+ <text x="50%" y="50%" alignmentBaseline="middle" dominantBaseline="middle" textAnchor="middle" dy=".125em">
30
+ {initials}
31
+ </text>
32
+ </svg>
33
+ )}
34
+ {src && <img src={src} alt={alt} />}
35
+ {/* Add an inset border that sits on top of the image */}
36
+ <span className="ring-1 ring-inset ring-black/5 dark:ring-white/5 forced-colors:outline" aria-hidden="true" />
37
+ </span>
38
+ )
39
+ }
40
+
41
+ export const AvatarButton = React.forwardRef(function AvatarButton(
42
+ { src, square = false, initials, alt, className, ...props },
43
+ ref
44
+ ) {
45
+ let classes = clsx(
46
+ className,
47
+ square ? 'rounded-lg' : 'rounded-full',
48
+ 'relative focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500'
49
+ )
50
+
51
+ return 'href' in props ? (
52
+ <Link {...props} className={classes} ref={ref}>
53
+ <TouchTarget>
54
+ <Avatar src={src} square={square} initials={initials} alt={alt} />
55
+ </TouchTarget>
56
+ </Link>
57
+ ) : (
58
+ <HeadlessButton {...props} className={classes} ref={ref}>
59
+ <TouchTarget>
60
+ <Avatar src={src} square={square} initials={initials} alt={alt} />
61
+ </TouchTarget>
62
+ </HeadlessButton>
63
+ )
64
+ })
@@ -0,0 +1,72 @@
1
+ import { Button as HeadlessButton } from '@headlessui/react'
2
+ import clsx from 'clsx'
3
+ import React from 'react'
4
+ import { TouchTarget } from './button'
5
+ import { Link } from './link'
6
+
7
+ const colors = {
8
+ red: 'bg-red-500/15 text-red-700 group-data-[hover]:bg-red-500/25 dark:bg-red-500/10 dark:text-red-400 dark:group-data-[hover]:bg-red-500/20',
9
+ orange:
10
+ 'bg-orange-500/15 text-orange-700 group-data-[hover]:bg-orange-500/25 dark:bg-orange-500/10 dark:text-orange-400 dark:group-data-[hover]:bg-orange-500/20',
11
+ amber:
12
+ 'bg-amber-400/20 text-amber-700 group-data-[hover]:bg-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400 dark:group-data-[hover]:bg-amber-400/15',
13
+ yellow:
14
+ 'bg-yellow-400/20 text-yellow-700 group-data-[hover]:bg-yellow-400/30 dark:bg-yellow-400/10 dark:text-yellow-300 dark:group-data-[hover]:bg-yellow-400/15',
15
+ lime: 'bg-lime-400/20 text-lime-700 group-data-[hover]:bg-lime-400/30 dark:bg-lime-400/10 dark:text-lime-300 dark:group-data-[hover]:bg-lime-400/15',
16
+ green:
17
+ 'bg-green-500/15 text-green-700 group-data-[hover]:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:group-data-[hover]:bg-green-500/20',
18
+ emerald:
19
+ 'bg-emerald-500/15 text-emerald-700 group-data-[hover]:bg-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400 dark:group-data-[hover]:bg-emerald-500/20',
20
+ teal: 'bg-teal-500/15 text-teal-700 group-data-[hover]:bg-teal-500/25 dark:bg-teal-500/10 dark:text-teal-300 dark:group-data-[hover]:bg-teal-500/20',
21
+ cyan: 'bg-cyan-400/20 text-cyan-700 group-data-[hover]:bg-cyan-400/30 dark:bg-cyan-400/10 dark:text-cyan-300 dark:group-data-[hover]:bg-cyan-400/15',
22
+ sky: 'bg-sky-500/15 text-sky-700 group-data-[hover]:bg-sky-500/25 dark:bg-sky-500/10 dark:text-sky-300 dark:group-data-[hover]:bg-sky-500/20',
23
+ blue: 'bg-blue-500/15 text-blue-700 group-data-[hover]:bg-blue-500/25 dark:text-blue-400 dark:group-data-[hover]:bg-blue-500/25',
24
+ indigo:
25
+ 'bg-indigo-500/15 text-indigo-700 group-data-[hover]:bg-indigo-500/25 dark:text-indigo-400 dark:group-data-[hover]:bg-indigo-500/20',
26
+ violet:
27
+ 'bg-violet-500/15 text-violet-700 group-data-[hover]:bg-violet-500/25 dark:text-violet-400 dark:group-data-[hover]:bg-violet-500/20',
28
+ purple:
29
+ 'bg-purple-500/15 text-purple-700 group-data-[hover]:bg-purple-500/25 dark:text-purple-400 dark:group-data-[hover]:bg-purple-500/20',
30
+ fuchsia:
31
+ 'bg-fuchsia-400/15 text-fuchsia-700 group-data-[hover]:bg-fuchsia-400/25 dark:bg-fuchsia-400/10 dark:text-fuchsia-400 dark:group-data-[hover]:bg-fuchsia-400/20',
32
+ pink: 'bg-pink-400/15 text-pink-700 group-data-[hover]:bg-pink-400/25 dark:bg-pink-400/10 dark:text-pink-400 dark:group-data-[hover]:bg-pink-400/20',
33
+ rose: 'bg-rose-400/15 text-rose-700 group-data-[hover]:bg-rose-400/25 dark:bg-rose-400/10 dark:text-rose-400 dark:group-data-[hover]:bg-rose-400/20',
34
+ zinc: 'bg-zinc-600/10 text-zinc-700 group-data-[hover]:bg-zinc-600/20 dark:bg-white/5 dark:text-zinc-400 dark:group-data-[hover]:bg-white/10',
35
+ }
36
+
37
+ export function Badge({ color = 'zinc', className, ...props }) {
38
+ return (
39
+ <span
40
+ {...props}
41
+ className={clsx(
42
+ className,
43
+ 'inline-flex items-center gap-x-1.5 rounded-md px-1.5 py-0.5 text-sm/5 font-medium sm:text-xs/5 forced-colors:outline',
44
+ colors[color]
45
+ )}
46
+ />
47
+ )
48
+ }
49
+
50
+ export const BadgeButton = React.forwardRef(function BadgeButton(
51
+ { color = 'zinc', className, children, ...props },
52
+ ref
53
+ ) {
54
+ let classes = clsx(
55
+ className,
56
+ 'group relative inline-flex rounded-md focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500'
57
+ )
58
+
59
+ return 'href' in props ? (
60
+ <Link {...props} className={classes} ref={ref}>
61
+ <TouchTarget>
62
+ <Badge color={color}>{children}</Badge>
63
+ </TouchTarget>
64
+ </Link>
65
+ ) : (
66
+ <HeadlessButton {...props} className={classes} ref={ref}>
67
+ <TouchTarget>
68
+ <Badge color={color}>{children}</Badge>
69
+ </TouchTarget>
70
+ </HeadlessButton>
71
+ )
72
+ })
@@ -0,0 +1,160 @@
1
+ import { Checkbox as HeadlessCheckbox, Field as HeadlessField } from '@headlessui/react'
2
+ import { clsx } from 'clsx'
3
+
4
+ export function CheckboxGroup({ className, ...props }) {
5
+ return (
6
+ <div
7
+ data-slot="control"
8
+ {...props}
9
+ className={clsx(
10
+ className,
11
+
12
+ // Basic groups
13
+ 'space-y-3',
14
+
15
+ // With descriptions
16
+ 'has-[[data-slot=description]]:space-y-6 [&_[data-slot=label]]:has-[[data-slot=description]]:font-medium'
17
+ )}
18
+ />
19
+ )
20
+ }
21
+
22
+ export function CheckboxField({ className, ...props }) {
23
+ return (
24
+ <HeadlessField
25
+ data-slot="field"
26
+ {...props}
27
+ className={clsx(
28
+ className,
29
+
30
+ // Base layout
31
+ 'grid grid-cols-[1.125rem_1fr] items-center gap-x-4 gap-y-1 sm:grid-cols-[1rem_1fr]',
32
+
33
+ // Control layout
34
+ '[&>[data-slot=control]]:col-start-1 [&>[data-slot=control]]:row-start-1 [&>[data-slot=control]]:justify-self-center',
35
+
36
+ // Label layout
37
+ '[&>[data-slot=label]]:col-start-2 [&>[data-slot=label]]:row-start-1 [&>[data-slot=label]]:justify-self-start',
38
+
39
+ // Description layout
40
+ '[&>[data-slot=description]]:col-start-2 [&>[data-slot=description]]:row-start-2',
41
+
42
+ // With description
43
+ '[&_[data-slot=label]]:has-[[data-slot=description]]:font-medium'
44
+ )}
45
+ />
46
+ )
47
+ }
48
+
49
+ const base = [
50
+ // Basic layout
51
+ 'relative isolate flex size-[1.125rem] items-center justify-center rounded-[0.3125rem] sm:size-4',
52
+
53
+ // Background color + shadow applied to inset pseudo element, so shadow blends with border in light mode
54
+ 'before:absolute before:inset-0 before:-z-10 before:rounded-[calc(0.3125rem-1px)] before:bg-white before:shadow',
55
+
56
+ // Background color when checked
57
+ 'before:group-data-[checked]:bg-[--checkbox-checked-bg]',
58
+
59
+ // Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
60
+ 'dark:before:hidden',
61
+
62
+ // Background color applied to control in dark mode
63
+ 'dark:bg-white/5 dark:group-data-[checked]:bg-[--checkbox-checked-bg]',
64
+
65
+ // Border
66
+ 'border border-zinc-950/15 group-data-[checked]:border-transparent group-data-[checked]:group-data-[hover]:border-transparent group-data-[hover]:border-zinc-950/30 group-data-[checked]:bg-[--checkbox-checked-border]',
67
+ 'dark:border-white/15 dark:group-data-[checked]:border-white/5 dark:group-data-[checked]:group-data-[hover]:border-white/5 dark:group-data-[hover]:border-white/30',
68
+
69
+ // Inner highlight shadow
70
+ 'after:absolute after:inset-0 after:rounded-[calc(0.3125rem-1px)] after:shadow-[inset_0_1px_theme(colors.white/15%)]',
71
+ 'dark:after:-inset-px dark:after:hidden dark:after:rounded-[0.3125rem] dark:group-data-[checked]:after:block',
72
+
73
+ // Focus ring
74
+ 'group-data-[focus]:outline group-data-[focus]:outline-2 group-data-[focus]:outline-offset-2 group-data-[focus]:outline-blue-500',
75
+
76
+ // Disabled state
77
+ 'group-data-[disabled]:opacity-50',
78
+ 'group-data-[disabled]:border-zinc-950/25 group-data-[disabled]:bg-zinc-950/5 group-data-[disabled]:[--checkbox-check:theme(colors.zinc.950/50%)] group-data-[disabled]:before:bg-transparent',
79
+ 'dark:group-data-[disabled]:border-white/20 dark:group-data-[disabled]:bg-white/[2.5%] dark:group-data-[disabled]:[--checkbox-check:theme(colors.white/50%)] dark:group-data-[disabled]:group-data-[checked]:after:hidden',
80
+
81
+ // Forced colors mode
82
+ 'forced-colors:[--checkbox-check:HighlightText] forced-colors:[--checkbox-checked-bg:Highlight] forced-colors:group-data-[disabled]:[--checkbox-check:Highlight]',
83
+ 'dark:forced-colors:[--checkbox-check:HighlightText] dark:forced-colors:[--checkbox-checked-bg:Highlight] dark:forced-colors:group-data-[disabled]:[--checkbox-check:Highlight]',
84
+ ]
85
+
86
+ const colors = {
87
+ 'dark/zinc': [
88
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.900)] [--checkbox-checked-border:theme(colors.zinc.950/90%)]',
89
+ 'dark:[--checkbox-checked-bg:theme(colors.zinc.600)]',
90
+ ],
91
+ 'dark/white': [
92
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.900)] [--checkbox-checked-border:theme(colors.zinc.950/90%)]',
93
+ 'dark:[--checkbox-check:theme(colors.zinc.900)] dark:[--checkbox-checked-bg:theme(colors.white)] dark:[--checkbox-checked-border:theme(colors.zinc.950/15%)]',
94
+ ],
95
+ white:
96
+ '[--checkbox-check:theme(colors.zinc.900)] [--checkbox-checked-bg:theme(colors.white)] [--checkbox-checked-border:theme(colors.zinc.950/15%)]',
97
+ dark: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.900)] [--checkbox-checked-border:theme(colors.zinc.950/90%)]',
98
+ zinc: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.600)] [--checkbox-checked-border:theme(colors.zinc.700/90%)]',
99
+ red: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.red.600)] [--checkbox-checked-border:theme(colors.red.700/90%)]',
100
+ orange:
101
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.orange.500)] [--checkbox-checked-border:theme(colors.orange.600/90%)]',
102
+ amber:
103
+ '[--checkbox-check:theme(colors.amber.950)] [--checkbox-checked-bg:theme(colors.amber.400)] [--checkbox-checked-border:theme(colors.amber.500/80%)]',
104
+ yellow:
105
+ '[--checkbox-check:theme(colors.yellow.950)] [--checkbox-checked-bg:theme(colors.yellow.300)] [--checkbox-checked-border:theme(colors.yellow.400/80%)]',
106
+ lime: '[--checkbox-check:theme(colors.lime.950)] [--checkbox-checked-bg:theme(colors.lime.300)] [--checkbox-checked-border:theme(colors.lime.400/80%)]',
107
+ green:
108
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.green.600)] [--checkbox-checked-border:theme(colors.green.700/90%)]',
109
+ emerald:
110
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.emerald.600)] [--checkbox-checked-border:theme(colors.emerald.700/90%)]',
111
+ teal: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.teal.600)] [--checkbox-checked-border:theme(colors.teal.700/90%)]',
112
+ cyan: '[--checkbox-check:theme(colors.cyan.950)] [--checkbox-checked-bg:theme(colors.cyan.300)] [--checkbox-checked-border:theme(colors.cyan.400/80%)]',
113
+ sky: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.sky.500)] [--checkbox-checked-border:theme(colors.sky.600/80%)]',
114
+ blue: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.blue.600)] [--checkbox-checked-border:theme(colors.blue.700/90%)]',
115
+ indigo:
116
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.indigo.500)] [--checkbox-checked-border:theme(colors.indigo.600/90%)]',
117
+ violet:
118
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.violet.500)] [--checkbox-checked-border:theme(colors.violet.600/90%)]',
119
+ purple:
120
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.purple.500)] [--checkbox-checked-border:theme(colors.purple.600/90%)]',
121
+ fuchsia:
122
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.fuchsia.500)] [--checkbox-checked-border:theme(colors.fuchsia.600/90%)]',
123
+ pink: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.pink.500)] [--checkbox-checked-border:theme(colors.pink.600/90%)]',
124
+ rose: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.rose.500)] [--checkbox-checked-border:theme(colors.rose.600/90%)]',
125
+ }
126
+
127
+ export function Checkbox({ color = 'dark/zinc', className, ...props }) {
128
+ return (
129
+ <HeadlessCheckbox
130
+ data-slot="control"
131
+ className={clsx(className, 'group inline-flex focus:outline-none')}
132
+ {...props}
133
+ >
134
+ <span className={clsx([base, colors[color]])}>
135
+ <svg
136
+ className="size-4 stroke-[--checkbox-check] opacity-0 group-data-[checked]:opacity-100 sm:h-3.5 sm:w-3.5"
137
+ viewBox="0 0 14 14"
138
+ fill="none"
139
+ >
140
+ {/* Checkmark icon */}
141
+ <path
142
+ className="opacity-100 group-data-[indeterminate]:opacity-0"
143
+ d="M3 8L6 11L11 3.5"
144
+ strokeWidth={2}
145
+ strokeLinecap="round"
146
+ strokeLinejoin="round"
147
+ />
148
+ {/* Indeterminate icon */}
149
+ <path
150
+ className="opacity-0 group-data-[indeterminate]:opacity-100"
151
+ d="M3 7H11"
152
+ strokeWidth={2}
153
+ strokeLinecap="round"
154
+ strokeLinejoin="round"
155
+ />
156
+ </svg>
157
+ </span>
158
+ </HeadlessCheckbox>
159
+ )
160
+ }
@@ -0,0 +1,101 @@
1
+ import {
2
+ Description as HeadlessDescription,
3
+ Dialog as HeadlessDialog,
4
+ DialogPanel as HeadlessDialogPanel,
5
+ DialogTitle as HeadlessDialogTitle,
6
+ Transition as HeadlessTransition,
7
+ TransitionChild as HeadlessTransitionChild,
8
+ } from '@headlessui/react'
9
+ import clsx from 'clsx'
10
+ import { Fragment } from 'react'
11
+ import { Text } from './text'
12
+
13
+ const sizes = {
14
+ xs: 'sm:max-w-xs',
15
+ sm: 'sm:max-w-sm',
16
+ md: 'sm:max-w-md',
17
+ lg: 'sm:max-w-lg',
18
+ xl: 'sm:max-w-xl',
19
+ '2xl': 'sm:max-w-2xl',
20
+ '3xl': 'sm:max-w-3xl',
21
+ '4xl': 'sm:max-w-4xl',
22
+ '5xl': 'sm:max-w-5xl',
23
+ }
24
+
25
+ export function Dialog({ open, onClose, size = 'lg', className, children, ...props }) {
26
+ return (
27
+ <HeadlessTransition appear as={Fragment} show={open} {...props}>
28
+ <HeadlessDialog onClose={onClose}>
29
+ <HeadlessTransitionChild
30
+ as={Fragment}
31
+ enter="ease-out duration-100"
32
+ enterFrom="opacity-0"
33
+ enterTo="opacity-100"
34
+ leave="ease-in duration-100"
35
+ leaveFrom="opacity-100"
36
+ leaveTo="opacity-0"
37
+ >
38
+ <div className="fixed inset-0 flex w-screen justify-center overflow-y-auto bg-zinc-950/25 px-2 py-2 focus:outline-0 sm:px-6 sm:py-8 lg:px-8 lg:py-16 dark:bg-zinc-950/50" />
39
+ </HeadlessTransitionChild>
40
+
41
+ <HeadlessTransitionChild
42
+ className="fixed inset-0 w-screen overflow-y-auto pt-6 sm:pt-0"
43
+ enter="ease-out duration-100"
44
+ enterFrom="opacity-0 translate-y-12 sm:translate-y-0"
45
+ enterTo="opacity-100 translate-y-0"
46
+ leave="ease-in duration-100"
47
+ leaveFrom="opacity-100 translate-y-0"
48
+ leaveTo="opacity-0 translate-y-12 sm:translate-y-0"
49
+ >
50
+ <div className="grid min-h-full grid-rows-[1fr_auto] justify-items-center sm:grid-rows-[1fr_auto_3fr] sm:p-4">
51
+ <HeadlessTransitionChild
52
+ as={HeadlessDialogPanel}
53
+ className={clsx(
54
+ className,
55
+ sizes[size],
56
+ 'row-start-2 w-full min-w-0 rounded-t-3xl bg-white p-[--gutter] shadow-lg ring-1 ring-zinc-950/10 [--gutter:theme(spacing.8)] sm:mb-auto sm:rounded-2xl dark:bg-zinc-900 dark:ring-white/10 forced-colors:outline'
57
+ )}
58
+ enter="ease-out duration-100"
59
+ enterFrom="sm:scale-95"
60
+ enterTo="sm:scale-100"
61
+ leave="ease-in duration-100"
62
+ leaveFrom="sm:scale-100"
63
+ leaveTo="sm:scale-100"
64
+ >
65
+ {children}
66
+ </HeadlessTransitionChild>
67
+ </div>
68
+ </HeadlessTransitionChild>
69
+ </HeadlessDialog>
70
+ </HeadlessTransition>
71
+ )
72
+ }
73
+
74
+ export function DialogTitle({ className, ...props }) {
75
+ return (
76
+ <HeadlessDialogTitle
77
+ {...props}
78
+ className={clsx(className, 'text-balance text-lg/6 font-semibold text-zinc-950 sm:text-base/6 dark:text-white')}
79
+ />
80
+ )
81
+ }
82
+
83
+ export function DialogDescription({ className, ...props }) {
84
+ return <HeadlessDescription as={Text} {...props} className={clsx(className, 'mt-2 text-pretty')} />
85
+ }
86
+
87
+ export function DialogBody({ className, ...props }) {
88
+ return <div {...props} className={clsx(className, 'mt-6')} />
89
+ }
90
+
91
+ export function DialogActions({ className, ...props }) {
92
+ return (
93
+ <div
94
+ {...props}
95
+ className={clsx(
96
+ className,
97
+ 'mt-8 flex flex-col-reverse items-center justify-end gap-3 *:w-full sm:flex-row sm:*:w-auto'
98
+ )}
99
+ />
100
+ )
101
+ }