@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.
- package/.eslintrc.cjs +18 -0
- package/.github/workflows/deploy-docs.yml +59 -0
- package/.github/workflows/publish.yml +19 -0
- package/README.md +5 -0
- package/index.html +13 -0
- package/lib/catalyst/alert.jsx +110 -0
- package/lib/catalyst/avatar.jsx +64 -0
- package/lib/catalyst/badge.jsx +72 -0
- package/lib/catalyst/checkbox.jsx +160 -0
- package/lib/catalyst/dialog.jsx +101 -0
- package/lib/catalyst/dropdown.jsx +202 -0
- package/lib/catalyst/fieldset.jsx +87 -0
- package/lib/catalyst/input.jsx +78 -0
- package/lib/catalyst/listbox.jsx +195 -0
- package/lib/catalyst/pagination.jsx +75 -0
- package/lib/catalyst/radio.jsx +144 -0
- package/lib/catalyst/select.jsx +78 -0
- package/lib/catalyst/switch.jsx +203 -0
- package/lib/catalyst/textarea.jsx +59 -0
- package/lib/components/breadcrumbs.jsx +46 -0
- package/lib/components/button.jsx +219 -0
- package/lib/components/display/headings.jsx +39 -0
- package/lib/components/display/text.jsx +48 -0
- package/lib/components/hero.jsx +51 -0
- package/lib/components/layout/container.jsx +13 -0
- package/lib/components/layout/usace-box.jsx +26 -0
- package/lib/components/link.jsx +18 -0
- package/lib/components/table.jsx +146 -0
- package/lib/composite/footer/essayons.jsx +12 -0
- package/lib/composite/footer/footer-links.jsx +33 -0
- package/lib/composite/footer/index.jsx +148 -0
- package/lib/composite/footer/logo-banner.jsx +55 -0
- package/lib/composite/footer/social-links.jsx +60 -0
- package/lib/composite/header/index.jsx +83 -0
- package/lib/composite/header/navbar-links.jsx +113 -0
- package/lib/composite/header/usa-banner/USABanner.jsx +114 -0
- package/lib/composite/header/usa-banner/usa-banner.css +220 -0
- package/lib/composite/site-wrapper/index.jsx +52 -0
- package/lib/index.jsx +19 -0
- package/package.json +39 -0
- package/postcss.config.js +6 -0
- package/public/Magnifying_glass_icon.svg +5 -0
- package/public/armystar-logo-rb.svg +4 -0
- package/public/cwbi-logo.png +0 -0
- package/public/essayonscrest.png +0 -0
- package/public/icon-dot-gov.svg +1 -0
- package/public/icon-https.svg +1 -0
- package/public/nww-lucky-peak-dam.jpg +0 -0
- package/public/rsgis-logo.png +0 -0
- package/public/us_flag_small.png +0 -0
- package/public/usace-logo-color.svg +1 -0
- package/src/App.jsx +38 -0
- package/src/app-bundles/index.js +4 -0
- package/src/app-bundles/route-bundle.js +36 -0
- package/src/app-components/code-example.jsx +17 -0
- package/src/app-components/copy-button.jsx +32 -0
- package/src/app-components/props-table.jsx +43 -0
- package/src/app-components/sub-site-banner.jsx +15 -0
- package/src/app-pages/documentation/_docs-page.jsx +31 -0
- package/src/app-pages/documentation/_docs-sidebar.jsx +111 -0
- package/src/app-pages/documentation/_template.jsx +55 -0
- package/src/app-pages/documentation/app-shell/index.jsx +39 -0
- package/src/app-pages/documentation/app-shell/site-wrapper.jsx +190 -0
- package/src/app-pages/documentation/buttons/index.jsx +170 -0
- package/src/app-pages/documentation/index.jsx +57 -0
- package/src/app-pages/documentation/layout/container.jsx +68 -0
- package/src/app-pages/documentation/layout/index.jsx +52 -0
- package/src/app-pages/documentation/layout/usace-box.jsx +76 -0
- package/src/app-pages/documentation/navigation/breadcrumbs.jsx +119 -0
- package/src/app-pages/documentation/navigation/index.jsx +31 -0
- package/src/app-pages/documentation/types/index.jsx +31 -0
- package/src/app-pages/documentation/types/link.jsx +72 -0
- package/src/app-pages/home.jsx +71 -0
- package/src/index.css +3 -0
- package/src/main.jsx +18 -0
- package/tailwind.config.js +31 -0
- 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
|
+
}
|