@kennycorrea/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/dist/atoms/Avatar/Avatar.d.ts +7 -0
- package/dist/atoms/Avatar/Avatar.js +19 -0
- package/dist/atoms/Avatar/index.d.ts +1 -0
- package/dist/atoms/Avatar/index.js +1 -0
- package/dist/atoms/Badge/Badge.d.ts +7 -0
- package/dist/atoms/Badge/Badge.js +23 -0
- package/dist/atoms/Badge/index.d.ts +1 -0
- package/dist/atoms/Badge/index.js +1 -0
- package/dist/atoms/Button/Button.d.ts +2 -0
- package/dist/atoms/Button/Button.js +34 -0
- package/dist/atoms/Button/Button.types.d.ts +40 -0
- package/dist/atoms/Button/Button.types.js +1 -0
- package/dist/atoms/Button/index.d.ts +2 -0
- package/dist/atoms/Button/index.js +2 -0
- package/dist/atoms/Card/Card.d.ts +16 -0
- package/dist/atoms/Card/Card.js +22 -0
- package/dist/atoms/Card/index.d.ts +1 -0
- package/dist/atoms/Card/index.js +1 -0
- package/dist/atoms/Checkbox/Checkbox.d.ts +11 -0
- package/dist/atoms/Checkbox/Checkbox.js +14 -0
- package/dist/atoms/Checkbox/index.d.ts +1 -0
- package/dist/atoms/Checkbox/index.js +1 -0
- package/dist/atoms/ContextMenu/ContextMenu.d.ts +31 -0
- package/dist/atoms/ContextMenu/ContextMenu.js +24 -0
- package/dist/atoms/ContextMenu/index.d.ts +1 -0
- package/dist/atoms/ContextMenu/index.js +1 -0
- package/dist/atoms/Dialog/Dialog.d.ts +19 -0
- package/dist/atoms/Dialog/Dialog.js +13 -0
- package/dist/atoms/Dialog/index.d.ts +1 -0
- package/dist/atoms/Dialog/index.js +1 -0
- package/dist/atoms/Icon/Icon.d.ts +48 -0
- package/dist/atoms/Icon/Icon.js +43 -0
- package/dist/atoms/Icon/index.d.ts +1 -0
- package/dist/atoms/Icon/index.js +1 -0
- package/dist/atoms/Input/Input.d.ts +15 -0
- package/dist/atoms/Input/Input.js +11 -0
- package/dist/atoms/Input/index.d.ts +1 -0
- package/dist/atoms/Input/index.js +1 -0
- package/dist/atoms/Label/Label.d.ts +12 -0
- package/dist/atoms/Label/Label.js +14 -0
- package/dist/atoms/Label/index.d.ts +1 -0
- package/dist/atoms/Label/index.js +1 -0
- package/dist/atoms/Popover/Popover.d.ts +16 -0
- package/dist/atoms/Popover/Popover.js +11 -0
- package/dist/atoms/Popover/index.d.ts +1 -0
- package/dist/atoms/Popover/index.js +1 -0
- package/dist/atoms/Progress/Progress.d.ts +9 -0
- package/dist/atoms/Progress/Progress.js +12 -0
- package/dist/atoms/Progress/index.d.ts +1 -0
- package/dist/atoms/Progress/index.js +1 -0
- package/dist/atoms/Select/Select.d.ts +25 -0
- package/dist/atoms/Select/Select.js +16 -0
- package/dist/atoms/Select/index.d.ts +1 -0
- package/dist/atoms/Select/index.js +1 -0
- package/dist/atoms/Separator/Separator.d.ts +5 -0
- package/dist/atoms/Separator/Separator.js +6 -0
- package/dist/atoms/Separator/index.d.ts +1 -0
- package/dist/atoms/Separator/index.js +1 -0
- package/dist/atoms/Spinner/Spinner.d.ts +12 -0
- package/dist/atoms/Spinner/Spinner.js +15 -0
- package/dist/atoms/Spinner/index.d.ts +1 -0
- package/dist/atoms/Spinner/index.js +1 -0
- package/dist/atoms/Switch/Switch.d.ts +4 -0
- package/dist/atoms/Switch/Switch.js +7 -0
- package/dist/atoms/Switch/index.d.ts +1 -0
- package/dist/atoms/Switch/index.js +1 -0
- package/dist/atoms/Textarea/Textarea.d.ts +6 -0
- package/dist/atoms/Textarea/Textarea.js +7 -0
- package/dist/atoms/Textarea/index.d.ts +1 -0
- package/dist/atoms/Textarea/index.js +1 -0
- package/dist/atoms/Tooltip/Tooltip.d.ts +26 -0
- package/dist/atoms/Tooltip/Tooltip.js +25 -0
- package/dist/atoms/Tooltip/index.d.ts +1 -0
- package/dist/atoms/Tooltip/index.js +1 -0
- package/dist/atoms/index.d.ts +18 -0
- package/dist/atoms/index.js +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/strings.d.ts +39 -0
- package/dist/strings.js +26 -0
- package/dist/utils/cn.d.ts +3 -0
- package/dist/utils/cn.js +6 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/theme.d.ts +33 -0
- package/dist/utils/theme.js +72 -0
- package/package.json +55 -0
- package/tokens.css +240 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @kennycorrea/ui
|
|
2
|
+
|
|
3
|
+
Upfront's house component layer: the atoms over Radix Themes, the token bridge, and the accent
|
|
4
|
+
logic. Shared by the dashboard (`upfront-web-app`), the marketing site (`upfront-web-site`) and
|
|
5
|
+
the admin app (`upfront-admin`).
|
|
6
|
+
|
|
7
|
+
**The product brief, the roadmap and every design document live in the `upfront` repository**,
|
|
8
|
+
not here. This file covers only what is true about this package.
|
|
9
|
+
|
|
10
|
+
## Why it exists
|
|
11
|
+
|
|
12
|
+
Two apps said `variant="secondary"` and `text-ink-muted`, and each carried its own copy of what
|
|
13
|
+
those meant. `docs/ecosystem.md` in the product repository called starting a third app the
|
|
14
|
+
trigger, and this is the trigger being pulled.
|
|
15
|
+
|
|
16
|
+
**The drift, measured rather than assumed.** It was real, and it was concentrated in the
|
|
17
|
+
components rather than the colours:
|
|
18
|
+
|
|
19
|
+
| | dashboard | site | visible? |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `Button` `secondary` | Themes' `soft` | Themes' `surface` | **yes** |
|
|
22
|
+
| `danger` variant | yes | absent | **yes** |
|
|
23
|
+
| sizes | `sm`/`md`/`lg` | raw Themes `1`–`4` | **yes** |
|
|
24
|
+
| `href` prop | absent | yes | **yes** |
|
|
25
|
+
| `--font-sans` | `'Inter', …` | `ui-sans-serif, …` | no — neither app loads Inter |
|
|
26
|
+
| `--color-ink` fallback | `#1d3b31` | `#1a211f` | only if Radix's CSS fails to load |
|
|
27
|
+
| `--color-hairline` fallback | 12% | 10% | same |
|
|
28
|
+
|
|
29
|
+
The `.radix-themes` blocks — the *live* colour assignments — had been kept in step by hand and
|
|
30
|
+
agreed. It was the `@theme` fallbacks underneath them that had not, which is drift that is real
|
|
31
|
+
and invisible: the least urgent kind and the easiest to leave lying around for years.
|
|
32
|
+
|
|
33
|
+
So the honest case for this package is the component API, not the palette.
|
|
34
|
+
|
|
35
|
+
## The Button disagreement, resolved
|
|
36
|
+
|
|
37
|
+
**`secondary` now means exactly one thing: Themes' `soft`.** The site's bordered, translucent
|
|
38
|
+
look is still available and is called **`outline`**.
|
|
39
|
+
|
|
40
|
+
Making one of them simply win would have been a decision about the word rather than about the
|
|
41
|
+
need — the site wants the bordered button because it sits over a dark hero, and the dashboard
|
|
42
|
+
wants the tinted one because it sits on a panel. Both needs are real; only the name was
|
|
43
|
+
ambiguous.
|
|
44
|
+
|
|
45
|
+
The package's `Button` is the superset: `loading`, `fullWidth`, ref forwarding and `danger` from
|
|
46
|
+
the dashboard, `href` from the site. Nothing was dropped.
|
|
47
|
+
|
|
48
|
+
## Using it
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { Button, Card, Select, UiStringsProvider } from '@kennycorrea/ui';
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
And in the app's own `index.css`:
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
@import 'tailwindcss';
|
|
58
|
+
@source '../node_modules/@kennycorrea/ui/dist';
|
|
59
|
+
@import '@kennycorrea/ui/tokens.css';
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
⚠ **The `@source` line is not optional.** Tailwind does not scan `node_modules`, so without it
|
|
63
|
+
every utility class used *inside* the atoms — `w-full`, `inline-flex` — is missing from the
|
|
64
|
+
generated CSS. The components come out subtly unstyled and nothing appears in the console.
|
|
65
|
+
|
|
66
|
+
## What is deliberately not here
|
|
67
|
+
|
|
68
|
+
- **Dictionaries.** Words belong to an app. The two strings the atoms say on their own behalf —
|
|
69
|
+
a standing spinner's label and an empty select's placeholder — come from `UiStringsProvider`,
|
|
70
|
+
which each app feeds from its own dictionary. Defaults are English so a host that forgets to
|
|
71
|
+
wrap gets something legible rather than something wrong.
|
|
72
|
+
- **Auth, stores, API clients.** Same reason, and they were never shared.
|
|
73
|
+
- **`ACCENT_LABELS`.** The accent *list* is structure and lives here; what each one is called is
|
|
74
|
+
words and does not.
|
|
75
|
+
- **Molecules and organisms.** `FormField`, `ConfirmDialog` and the rest stay in the dashboard
|
|
76
|
+
until a second app wants one. Moving a component here should be a response to a second
|
|
77
|
+
consumer, not an anticipation of one.
|
|
78
|
+
|
|
79
|
+
## Colour scales
|
|
80
|
+
|
|
81
|
+
Each app imports only the Radix colour scales it uses, in `main.tsx`, and **the import order is
|
|
82
|
+
load-bearing** — scales define `--gray-*` literally and `base.css` remaps it onto the chosen
|
|
83
|
+
`grayColor`, so `base.css` must come last. Adding an accent to `ACCENTS` means adding its scale
|
|
84
|
+
in every app that offers it, or it resolves to nothing.
|
|
85
|
+
|
|
86
|
+
## Releasing
|
|
87
|
+
|
|
88
|
+
`npm run build` compiles to `dist/` with type declarations. The version in `package.json` is the
|
|
89
|
+
contract: bump it, and bump the dependency in each consumer.
|
|
90
|
+
|
|
91
|
+
**`files` in `package.json` is the published surface**: `dist`, `tokens.css` and this README.
|
|
92
|
+
`src` is deliberately not in it — consumers import from `dist`, and shipping the TypeScript
|
|
93
|
+
source as well would let an app import a path that bypasses the build and the types.
|
|
94
|
+
|
|
95
|
+
`prepublishOnly` runs the build, so `npm publish` cannot ship a stale `dist` — which would
|
|
96
|
+
otherwise publish silently, with a version number that says nothing about it. `dist/` is
|
|
97
|
+
gitignored and present in the tarball, which is the right way round: the build output is not
|
|
98
|
+
worth reviewing in a diff and is worth shipping.
|
|
99
|
+
|
|
100
|
+
**The scope is `@kennycorrea` on npm.** `upfront` was not available, so the account was converted
|
|
101
|
+
into an organisation. Settling that before the first publish rather than after was worth doing:
|
|
102
|
+
the name is an import line in three apps, so renaming it was free today and would have cost a
|
|
103
|
+
major version and three coordinated bumps after a release.
|
|
104
|
+
|
|
105
|
+
GitHub Packages was the alternative and would have forced `@kennyrafael/ui`, because it requires
|
|
106
|
+
the scope to match the repository owner. `publishConfig.access: "public"` is set, because a scoped
|
|
107
|
+
package defaults to restricted and restricted needs a paid plan.
|
|
108
|
+
|
|
109
|
+
### The publish workflow
|
|
110
|
+
|
|
111
|
+
`.github/workflows/npm-publish.yml`: a `build` job that runs `npm test`, then `publish-npm`
|
|
112
|
+
which needs it.
|
|
113
|
+
|
|
114
|
+
⚠ **Pushing to `main` does not publish, and is not meant to.** The trigger is
|
|
115
|
+
`release: [created]` — a GitHub *release*, which means a tag and a release object. A commit on
|
|
116
|
+
`main` fires nothing and leaves no run in the Actions tab, which looks identical to a broken
|
|
117
|
+
workflow. `workflow_dispatch` is also enabled, so the first publish and any retry can be run from
|
|
118
|
+
the Actions tab without inventing a version to hang a release off.
|
|
119
|
+
|
|
120
|
+
Two things that fail at the `npm publish` step rather than earlier, and are worth checking before
|
|
121
|
+
blaming the workflow:
|
|
122
|
+
|
|
123
|
+
- **The token must be a repository or organisation Actions secret**, not an account-level one.
|
|
124
|
+
GitHub's account-level secrets are for Codespaces; Actions reads repository, environment and
|
|
125
|
+
organisation secrets. The step references `secrets.npm_token`, and secret names are
|
|
126
|
+
case-insensitive, so `NPM_TOKEN` matches.
|
|
127
|
+
- **It must be an npm _Automation_ token** (or a granular token with publish rights). A classic
|
|
128
|
+
*Publish* token fails with `EOTP` the moment the account or org requires 2FA for publishing,
|
|
129
|
+
because there is no one there to type a one-time password. That failure reads like a bad
|
|
130
|
+
credential and is not one.
|
|
131
|
+
|
|
132
|
+
⚠ **`npm test` has to keep existing.** It is lint plus a typecheck rather than unit tests — there
|
|
133
|
+
are no unit tests here — and it is load-bearing for a reason that has nothing to do with quality:
|
|
134
|
+
without a `test` script the `build` job fails on `Missing script: "test"`, `publish-npm` never
|
|
135
|
+
runs because it needs `build`, and **the release publishes nothing while looking like it worked.**
|
|
136
|
+
That was the state when the workflow was first added.
|
|
137
|
+
|
|
138
|
+
### Where it is consumed from today
|
|
139
|
+
|
|
140
|
+
⚠ **The three apps still depend on `file:../ui`**, which works while they sit side by side on one
|
|
141
|
+
disk and does **not** work on Vercel, where each project builds from its own repository.
|
|
142
|
+
|
|
143
|
+
Once this is published, each consumer's dependency becomes a version instead of a path:
|
|
144
|
+
|
|
145
|
+
```jsonc
|
|
146
|
+
// web, site and admin package.json
|
|
147
|
+
"@kennycorrea/ui": "^0.1.0" // instead of "file:../ui"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
That is one line in three files and no code change. Note that `file:` installs a **symlink**, so
|
|
151
|
+
local development currently picks up edits to this package immediately; a published version will
|
|
152
|
+
not, and an app wanting to test an unreleased change wants `npm link` or a `file:` override
|
|
153
|
+
rather than a copy.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface AvatarProps {
|
|
2
|
+
/** Absent for most providers — the fallback is the normal case, not the error case. */
|
|
3
|
+
src?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function Avatar({ src, name, className }: AvatarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar as ThemedAvatar } from '@radix-ui/themes';
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
/** Two letters from the name, which is all a small circle has room for. */
|
|
5
|
+
function initialsOf(name) {
|
|
6
|
+
if (!name)
|
|
7
|
+
return '?';
|
|
8
|
+
const words = name.trim().split(/\s+/);
|
|
9
|
+
const first = words[0]?.[0] ?? '';
|
|
10
|
+
// First and last, not the first two: "Ana Silva Pereira" is AP to anyone who knows her.
|
|
11
|
+
const last = words.length > 1 ? (words[words.length - 1]?.[0] ?? '') : '';
|
|
12
|
+
return (first + last).toUpperCase();
|
|
13
|
+
}
|
|
14
|
+
export function Avatar({ src, name, className }) {
|
|
15
|
+
return (_jsx(ThemedAvatar, { size: "2", radius: "full", variant: "soft", src: src,
|
|
16
|
+
// Rendered until the image has actually decoded, so a slow logo does not flash a
|
|
17
|
+
// broken-image icon and a URL that 404s degrades to the initials.
|
|
18
|
+
fallback: initialsOf(name), className: cn(className) }));
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Avatar';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Avatar';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Badge as ThemedBadge } from '@radix-ui/themes';
|
|
2
|
+
import type { ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
export type BadgeVariant = 'neutral' | 'brand' | 'success' | 'warning' | 'danger';
|
|
4
|
+
export interface BadgeProps extends Omit<ComponentPropsWithoutRef<typeof ThemedBadge>, 'color' | 'variant'> {
|
|
5
|
+
variant?: BadgeVariant;
|
|
6
|
+
}
|
|
7
|
+
export declare function Badge({ variant, ...props }: BadgeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Badge as ThemedBadge } from '@radix-ui/themes';
|
|
3
|
+
/**
|
|
4
|
+
* Upfront's five meanings, in Themes' colour vocabulary.
|
|
5
|
+
*
|
|
6
|
+
* **`brand` names no colour on purpose.** Themes falls back to whatever accent the theme
|
|
7
|
+
* carries, so a badge that means "this is ours" follows the business's chosen colour while
|
|
8
|
+
* the four that mean something specific do not. It said `jade` until accents became
|
|
9
|
+
* choosable, which left the service badges green on a purple dashboard.
|
|
10
|
+
*
|
|
11
|
+
* `success` is jade and stays jade. Green means fine in a way purple does not, and a shop
|
|
12
|
+
* that liked orange should not end up with an orange "paid".
|
|
13
|
+
*/
|
|
14
|
+
const COLORS = {
|
|
15
|
+
neutral: 'gray',
|
|
16
|
+
brand: undefined,
|
|
17
|
+
success: 'jade',
|
|
18
|
+
warning: 'amber',
|
|
19
|
+
danger: 'red',
|
|
20
|
+
};
|
|
21
|
+
export function Badge({ variant = 'neutral', ...props }) {
|
|
22
|
+
return _jsx(ThemedBadge, { color: COLORS[variant], variant: "soft", radius: "full", ...props });
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Badge';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Badge';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Button as ThemedButton } from '@radix-ui/themes';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
/**
|
|
6
|
+
* Upfront's intents, expressed in Radix Themes' variant-and-colour vocabulary.
|
|
7
|
+
*
|
|
8
|
+
* The mapping exists so three apps keep saying `variant="danger"` rather than
|
|
9
|
+
* `variant="solid" color="red"` at sixty call sites — and so that when the house style changes
|
|
10
|
+
* its mind about what "secondary" looks like, it changes here and everywhere at once. That
|
|
11
|
+
* second half is what the dashboard and the site did not have, and why they drifted.
|
|
12
|
+
*/
|
|
13
|
+
const VARIANTS = {
|
|
14
|
+
primary: { variant: 'solid' },
|
|
15
|
+
secondary: { variant: 'soft' },
|
|
16
|
+
outline: { variant: 'surface' },
|
|
17
|
+
ghost: { variant: 'ghost' },
|
|
18
|
+
danger: { variant: 'solid', color: 'red' },
|
|
19
|
+
};
|
|
20
|
+
/** Themes sizes are 1–4; ours are the three a form actually needs. */
|
|
21
|
+
const SIZES = { sm: '2', md: '3', lg: '4' };
|
|
22
|
+
export const Button = forwardRef(function Button({ variant = 'primary', size = 'md', loading = false, fullWidth = false, asChild = false, href, className, disabled, children, ...props }, ref) {
|
|
23
|
+
const intent = VARIANTS[variant];
|
|
24
|
+
// An anchor is `asChild` underneath, so everything below treats the two the same: neither a
|
|
25
|
+
// link nor a slotted child may be given `loading` or `disabled`, because those are button
|
|
26
|
+
// attributes and Themes would hand them to whatever element it renders instead.
|
|
27
|
+
const slotted = asChild || href !== undefined;
|
|
28
|
+
const themed = (_jsx(ThemedButton, { ref: ref, asChild: slotted, variant: intent.variant, color: intent.color, size: SIZES[size],
|
|
29
|
+
// Themes renders its own spinner and keeps the label's width while it spins, so the
|
|
30
|
+
// button does not jump. It does not disable the button, which is why `disabled` still
|
|
31
|
+
// accounts for it.
|
|
32
|
+
loading: slotted ? undefined : loading, disabled: slotted ? undefined : disabled || loading, className: cn(fullWidth && 'w-full', className), ...props, children: href !== undefined ? _jsx("a", { href: href, children: children }) : children }));
|
|
33
|
+
return themed;
|
|
34
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Button as ThemedButton } from '@radix-ui/themes';
|
|
2
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Upfront's button intents.
|
|
5
|
+
*
|
|
6
|
+
* **`outline` is new, and it is how the disagreement was resolved.** The dashboard's
|
|
7
|
+
* `secondary` mapped to Themes' `soft` and the marketing site's mapped to `surface` — the same
|
|
8
|
+
* word meaning two things in two products, which is what `docs/ecosystem.md` named as the cost
|
|
9
|
+
* of three repositories with nothing shared. Making one of them win would have been a decision
|
|
10
|
+
* about the word rather than about the need: the site wants the bordered, translucent button
|
|
11
|
+
* because it sits over a dark hero, and the dashboard wants the tinted one because it sits on a
|
|
12
|
+
* panel. So `secondary` means exactly one thing everywhere and the other look has its own name.
|
|
13
|
+
*/
|
|
14
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
15
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
16
|
+
/**
|
|
17
|
+
* Derived from Themes' own props rather than from `ButtonHTMLAttributes`.
|
|
18
|
+
*
|
|
19
|
+
* The native attributes carry a legacy `color` — the HTML 4 one — which collides with the
|
|
20
|
+
* `color` Themes uses for its palette. Starting from the component's own props drops it,
|
|
21
|
+
* along with the other natives it does not accept.
|
|
22
|
+
*/
|
|
23
|
+
export interface ButtonProps extends Omit<ComponentPropsWithoutRef<typeof ThemedButton>, 'variant' | 'size' | 'color'> {
|
|
24
|
+
variant?: ButtonVariant;
|
|
25
|
+
size?: ButtonSize;
|
|
26
|
+
/** Renders a spinner and disables the button. */
|
|
27
|
+
loading?: boolean;
|
|
28
|
+
fullWidth?: boolean;
|
|
29
|
+
/** Renders the child element instead of a `button`, e.g. a router `Link`. */
|
|
30
|
+
asChild?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Renders an anchor, keeping Radix's focus ring and sizing.
|
|
33
|
+
*
|
|
34
|
+
* From the marketing site, where almost every button is a link. It is `asChild` underneath,
|
|
35
|
+
* so the anchor stays an anchor — middle-click, copy link, the lot — and it is here rather
|
|
36
|
+
* than left to each app because "a button that is really a link" is a house concern.
|
|
37
|
+
*/
|
|
38
|
+
href?: string;
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
export interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/** Lifts the card off the ground — for dialogs and anything that floats. */
|
|
4
|
+
raised?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Radix Themes' Card, with the padding taken off.
|
|
8
|
+
*
|
|
9
|
+
* Themes pads its card for you; Upfront's cards are built from a header and a body with a
|
|
10
|
+
* rule between them, so the padding belongs to those rather than to the card. `p-0` is the
|
|
11
|
+
* whole of the customisation.
|
|
12
|
+
*/
|
|
13
|
+
export declare function Card({ raised, className, ...props }: CardProps): import("react").JSX.Element;
|
|
14
|
+
export declare function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
|
|
15
|
+
export declare function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>): import("react").JSX.Element;
|
|
16
|
+
export declare function CardBody({ className, ...props }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Card as ThemedCard } from '@radix-ui/themes';
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
/**
|
|
5
|
+
* Radix Themes' Card, with the padding taken off.
|
|
6
|
+
*
|
|
7
|
+
* Themes pads its card for you; Upfront's cards are built from a header and a body with a
|
|
8
|
+
* rule between them, so the padding belongs to those rather than to the card. `p-0` is the
|
|
9
|
+
* whole of the customisation.
|
|
10
|
+
*/
|
|
11
|
+
export function Card({ raised = false, className, ...props }) {
|
|
12
|
+
return (_jsx(ThemedCard, { size: "2", variant: raised ? 'classic' : 'surface', className: cn('p-0', className), ...props }));
|
|
13
|
+
}
|
|
14
|
+
export function CardHeader({ className, ...props }) {
|
|
15
|
+
return _jsx("div", { className: cn('border-b border-hairline px-5 py-4', className), ...props });
|
|
16
|
+
}
|
|
17
|
+
export function CardTitle({ className, ...props }) {
|
|
18
|
+
return _jsx("h2", { className: cn('font-medium text-brand-900', className), ...props });
|
|
19
|
+
}
|
|
20
|
+
export function CardBody({ className, ...props }) {
|
|
21
|
+
return _jsx("div", { className: cn('px-5 py-4', className), ...props });
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Card';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Card';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Checkbox as ThemedCheckbox } from '@radix-ui/themes';
|
|
2
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
export type CheckboxProps = Omit<ComponentPropsWithoutRef<typeof ThemedCheckbox>, 'size'>;
|
|
4
|
+
/**
|
|
5
|
+
* A checkbox, for a list where several answers are true at once.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from `Switch`, which is for a single setting that takes effect immediately. This
|
|
8
|
+
* is for choosing from a set — which services somebody performs — where nothing happens
|
|
9
|
+
* until the form is saved.
|
|
10
|
+
*/
|
|
11
|
+
export declare const Checkbox: import("react").ForwardRefExoticComponent<CheckboxProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Checkbox as ThemedCheckbox } from '@radix-ui/themes';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
/**
|
|
6
|
+
* A checkbox, for a list where several answers are true at once.
|
|
7
|
+
*
|
|
8
|
+
* Distinct from `Switch`, which is for a single setting that takes effect immediately. This
|
|
9
|
+
* is for choosing from a set — which services somebody performs — where nothing happens
|
|
10
|
+
* until the form is saved.
|
|
11
|
+
*/
|
|
12
|
+
export const Checkbox = forwardRef(function Checkbox({ className, ...props }, ref) {
|
|
13
|
+
return _jsx(ThemedCheckbox, { ref: ref, size: "2", className: cn(className), ...props });
|
|
14
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Checkbox';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Checkbox';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export interface ContextMenuItem {
|
|
3
|
+
/** Stable across renders; also what `onSelect` is given back. */
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Red and separated from the rest, for the one that undoes something. */
|
|
7
|
+
destructive?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ContextMenuProps extends Omit<HTMLAttributes<HTMLElement>, 'onSelect'> {
|
|
11
|
+
items: ContextMenuItem[];
|
|
12
|
+
onSelect: (id: string) => void;
|
|
13
|
+
/** What right-clicking opens the menu on. */
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The menu a right-click opens, in the house vocabulary.
|
|
18
|
+
*
|
|
19
|
+
* Items are data rather than children, like `Select`'s options, because every caller
|
|
20
|
+
* builds them from something — a booking's status decides which of them make sense.
|
|
21
|
+
*
|
|
22
|
+
* **A shortcut, never the only way.** Right-click is invisible, mouse-only and absent on
|
|
23
|
+
* touch, so everything here also has a path through the form the block opens on click.
|
|
24
|
+
*
|
|
25
|
+
* Anything else it is given lands on the trigger, and so on the element inside it. Radix
|
|
26
|
+
* triggers compose by cloning their child, so a component in between that keeps its props
|
|
27
|
+
* to itself breaks the chain silently — the menu simply never opens. That is why the ref
|
|
28
|
+
* and the rest are passed down, and it is what lets a `Tooltip` wrap this and still reach
|
|
29
|
+
* the button.
|
|
30
|
+
*/
|
|
31
|
+
export declare const ContextMenu: import("react").ForwardRefExoticComponent<ContextMenuProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ContextMenu as ThemedContextMenu } from '@radix-ui/themes';
|
|
3
|
+
import { Fragment, forwardRef } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* The menu a right-click opens, in the house vocabulary.
|
|
6
|
+
*
|
|
7
|
+
* Items are data rather than children, like `Select`'s options, because every caller
|
|
8
|
+
* builds them from something — a booking's status decides which of them make sense.
|
|
9
|
+
*
|
|
10
|
+
* **A shortcut, never the only way.** Right-click is invisible, mouse-only and absent on
|
|
11
|
+
* touch, so everything here also has a path through the form the block opens on click.
|
|
12
|
+
*
|
|
13
|
+
* Anything else it is given lands on the trigger, and so on the element inside it. Radix
|
|
14
|
+
* triggers compose by cloning their child, so a component in between that keeps its props
|
|
15
|
+
* to itself breaks the chain silently — the menu simply never opens. That is why the ref
|
|
16
|
+
* and the rest are passed down, and it is what lets a `Tooltip` wrap this and still reach
|
|
17
|
+
* the button.
|
|
18
|
+
*/
|
|
19
|
+
export const ContextMenu = forwardRef(function ContextMenu({ items, onSelect, children, ...rest }, ref) {
|
|
20
|
+
return (_jsxs(ThemedContextMenu.Root, { children: [_jsx(ThemedContextMenu.Trigger, { ref: ref, ...rest, children: children }), _jsx(ThemedContextMenu.Content, { size: "1", children: items.map((item, index) => (_jsxs(Fragment, { children: [item.destructive && index > 0 && !items[index - 1].destructive ? (_jsx(ThemedContextMenu.Separator, {})) : null, _jsx(ThemedContextMenu.Item, { color: item.destructive ? 'red' : undefined, disabled: item.disabled,
|
|
21
|
+
// `onSelect` rather than `onClick`: Radix closes the menu and restores focus
|
|
22
|
+
// around it, which a bare click handler skips.
|
|
23
|
+
onSelect: () => onSelect(item.id), children: item.label })] }, item.id))) })] }));
|
|
24
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ContextMenu';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ContextMenu';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface DialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/** Action row pinned to the bottom of the panel. */
|
|
9
|
+
footer?: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Themes' dialog, laid out the way Upfront's are: a titled header, a body, and a rule above
|
|
14
|
+
* the actions.
|
|
15
|
+
*
|
|
16
|
+
* The padding is taken off the content so those three bands can own it — Themes pads the
|
|
17
|
+
* whole panel, which would put the rules inside the padding rather than across it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function Dialog({ open, onOpenChange, title, description, children, footer, className, }: DialogProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Dialog as ThemedDialog } from '@radix-ui/themes';
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
/**
|
|
5
|
+
* Themes' dialog, laid out the way Upfront's are: a titled header, a body, and a rule above
|
|
6
|
+
* the actions.
|
|
7
|
+
*
|
|
8
|
+
* The padding is taken off the content so those three bands can own it — Themes pads the
|
|
9
|
+
* whole panel, which would put the rules inside the padding rather than across it.
|
|
10
|
+
*/
|
|
11
|
+
export function Dialog({ open, onOpenChange, title, description, children, footer, className, }) {
|
|
12
|
+
return (_jsx(ThemedDialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(ThemedDialog.Content, { className: cn('max-w-lg p-0', className), children: [_jsxs("div", { className: "border-b border-hairline px-6 py-4", children: [_jsx(ThemedDialog.Title, { className: "mb-0 font-medium text-brand-900", children: title }), description ? (_jsx(ThemedDialog.Description, { className: "mb-0 mt-1 text-sm text-ink-muted", children: description })) : null] }), _jsx("div", { className: "max-h-[calc(100dvh-16rem)] overflow-y-auto px-6 py-5", children: children }), footer ? (_jsx("div", { className: "flex justify-end gap-2 border-t border-hairline px-6 py-4", children: footer })) : null] }) }));
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Dialog';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Dialog';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The handful of icons this app actually uses, drawn inline.
|
|
3
|
+
*
|
|
4
|
+
* No icon library: a dependency for twelve glyphs costs more in bundle and upgrade surface
|
|
5
|
+
* than it saves, and these are all one or two paths. They inherit `currentColor` and size
|
|
6
|
+
* with the text around them, which is what makes them work in a collapsed sidebar and a
|
|
7
|
+
* button alike.
|
|
8
|
+
*/
|
|
9
|
+
declare const PATHS: {
|
|
10
|
+
readonly overview: "M3 12l9-8 9 8M5 10v10h5v-6h4v6h5V10";
|
|
11
|
+
readonly services: "M4 7h16M4 12h16M4 17h10";
|
|
12
|
+
readonly bookings: "M7 3v3M17 3v3M4 8h16M5 6h14a1 1 0 011 1v12a1 1 0 01-1 1H5a1 1 0 01-1-1V7a1 1 0 011-1z";
|
|
13
|
+
readonly clients: "M16 20v-2a4 4 0 00-4-4H7a4 4 0 00-4 4v2M9.5 9.5a3 3 0 100-6 3 3 0 000 6zM21 20v-2a4 4 0 00-3-3.87";
|
|
14
|
+
readonly payments: "M3 7h18v12H3zM3 11h18M7 15h3";
|
|
15
|
+
readonly compliance: "M14 3H7a1 1 0 00-1 1v16a1 1 0 001 1h10a1 1 0 001-1V7zM14 3v4h4M9 13h6M9 17h4";
|
|
16
|
+
readonly settings: "M12 15.5a3.5 3.5 0 100-7 3.5 3.5 0 000 7zM19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 11-4 0v-.09A1.65 1.65 0 008 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 11-2.83-2.83l.06-.06A1.65 1.65 0 004.6 15a1.65 1.65 0 00-1.51-1H3a2 2 0 110-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 112.83-2.83l.06.06A1.65 1.65 0 009 4.6a1.65 1.65 0 001-1.51V3a2 2 0 114 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 112.83 2.83l-.06.06A1.65 1.65 0 0019.4 9c.14.35.4.65.73.85";
|
|
17
|
+
readonly collapse: "M15 6l-6 6 6 6";
|
|
18
|
+
readonly expand: "M9 6l6 6-6 6";
|
|
19
|
+
readonly logout: "M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9";
|
|
20
|
+
readonly upload: "M12 16V4M7 9l5-5 5 5M4 17v2a1 1 0 001 1h14a1 1 0 001-1v-2";
|
|
21
|
+
readonly trash: "M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3";
|
|
22
|
+
readonly menu: "M4 6h16M4 12h16M4 18h16";
|
|
23
|
+
readonly bell: "M18 8a6 6 0 10-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9M10.3 21a2 2 0 003.4 0";
|
|
24
|
+
readonly profile: "M12 12a4 4 0 100-8 4 4 0 000 8zM4 21v-1a6 6 0 016-6h4a6 6 0 016 6v1";
|
|
25
|
+
readonly download: "M12 4v12M7 11l5 5 5-5M4 19v1a1 1 0 001 1h14a1 1 0 001-1v-1";
|
|
26
|
+
readonly check: "M4 12.5l5 5L20 6.5";
|
|
27
|
+
readonly sun: "M12 4V2M12 22v-2M4 12H2M22 12h-2M5.6 5.6L4.2 4.2M19.8 19.8l-1.4-1.4M18.4 5.6l1.4-1.4M4.2 19.8l1.4-1.4M12 16.5a4.5 4.5 0 100-9 4.5 4.5 0 000 9z";
|
|
28
|
+
readonly moon: "M20 14.5A8.5 8.5 0 019.5 4a8.5 8.5 0 1010.5 10.5z";
|
|
29
|
+
readonly display: "M4 5h16a1 1 0 011 1v9a1 1 0 01-1 1H4a1 1 0 01-1-1V6a1 1 0 011-1zM9 20h6M12 16v4";
|
|
30
|
+
readonly close: "M6 6l12 12M18 6L6 18";
|
|
31
|
+
readonly 'close-circle': "M12 21a9 9 0 100-18 9 9 0 000 18zM9.5 9.5l5 5M14.5 9.5l-5 5";
|
|
32
|
+
readonly 'chevron-up': "M6 15l6-6 6 6";
|
|
33
|
+
readonly 'chevron-down': "M6 9l6 6 6-6";
|
|
34
|
+
};
|
|
35
|
+
export type IconName = keyof typeof PATHS;
|
|
36
|
+
export interface IconProps {
|
|
37
|
+
name: IconName;
|
|
38
|
+
className?: string;
|
|
39
|
+
/**
|
|
40
|
+
* A label makes it an image with a name; leaving it out makes it decoration.
|
|
41
|
+
*
|
|
42
|
+
* Decoration is the right answer whenever adjacent text already says the same thing —
|
|
43
|
+
* a screen reader announcing "Bookings, Bookings" is worse than saying it once.
|
|
44
|
+
*/
|
|
45
|
+
label?: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function Icon({ name, className, label }: IconProps): import("react").JSX.Element;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../utils/cn';
|
|
3
|
+
/**
|
|
4
|
+
* The handful of icons this app actually uses, drawn inline.
|
|
5
|
+
*
|
|
6
|
+
* No icon library: a dependency for twelve glyphs costs more in bundle and upgrade surface
|
|
7
|
+
* than it saves, and these are all one or two paths. They inherit `currentColor` and size
|
|
8
|
+
* with the text around them, which is what makes them work in a collapsed sidebar and a
|
|
9
|
+
* button alike.
|
|
10
|
+
*/
|
|
11
|
+
const PATHS = {
|
|
12
|
+
overview: 'M3 12l9-8 9 8M5 10v10h5v-6h4v6h5V10',
|
|
13
|
+
services: 'M4 7h16M4 12h16M4 17h10',
|
|
14
|
+
bookings: 'M7 3v3M17 3v3M4 8h16M5 6h14a1 1 0 011 1v12a1 1 0 01-1 1H5a1 1 0 01-1-1V7a1 1 0 011-1z',
|
|
15
|
+
clients: 'M16 20v-2a4 4 0 00-4-4H7a4 4 0 00-4 4v2M9.5 9.5a3 3 0 100-6 3 3 0 000 6zM21 20v-2a4 4 0 00-3-3.87',
|
|
16
|
+
payments: 'M3 7h18v12H3zM3 11h18M7 15h3',
|
|
17
|
+
compliance: 'M14 3H7a1 1 0 00-1 1v16a1 1 0 001 1h10a1 1 0 001-1V7zM14 3v4h4M9 13h6M9 17h4',
|
|
18
|
+
settings: 'M12 15.5a3.5 3.5 0 100-7 3.5 3.5 0 000 7zM19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 11-4 0v-.09A1.65 1.65 0 008 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 11-2.83-2.83l.06-.06A1.65 1.65 0 004.6 15a1.65 1.65 0 00-1.51-1H3a2 2 0 110-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 112.83-2.83l.06.06A1.65 1.65 0 009 4.6a1.65 1.65 0 001-1.51V3a2 2 0 114 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 112.83 2.83l-.06.06A1.65 1.65 0 0019.4 9c.14.35.4.65.73.85',
|
|
19
|
+
collapse: 'M15 6l-6 6 6 6',
|
|
20
|
+
expand: 'M9 6l6 6-6 6',
|
|
21
|
+
logout: 'M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9',
|
|
22
|
+
upload: 'M12 16V4M7 9l5-5 5 5M4 17v2a1 1 0 001 1h14a1 1 0 001-1v-2',
|
|
23
|
+
trash: 'M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3',
|
|
24
|
+
menu: 'M4 6h16M4 12h16M4 18h16',
|
|
25
|
+
bell: 'M18 8a6 6 0 10-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9M10.3 21a2 2 0 003.4 0',
|
|
26
|
+
profile: 'M12 12a4 4 0 100-8 4 4 0 000 8zM4 21v-1a6 6 0 016-6h4a6 6 0 016 6v1',
|
|
27
|
+
download: 'M12 4v12M7 11l5 5 5-5M4 19v1a1 1 0 001 1h14a1 1 0 001-1v-1',
|
|
28
|
+
check: 'M4 12.5l5 5L20 6.5',
|
|
29
|
+
sun: 'M12 4V2M12 22v-2M4 12H2M22 12h-2M5.6 5.6L4.2 4.2M19.8 19.8l-1.4-1.4M18.4 5.6l1.4-1.4M4.2 19.8l1.4-1.4M12 16.5a4.5 4.5 0 100-9 4.5 4.5 0 000 9z',
|
|
30
|
+
moon: 'M20 14.5A8.5 8.5 0 019.5 4a8.5 8.5 0 1010.5 10.5z',
|
|
31
|
+
display: 'M4 5h16a1 1 0 011 1v9a1 1 0 01-1 1H4a1 1 0 01-1-1V6a1 1 0 011-1zM9 20h6M12 16v4',
|
|
32
|
+
close: 'M6 6l12 12M18 6L6 18',
|
|
33
|
+
// The circle is what makes it read as a control rather than as decoration, which matters
|
|
34
|
+
// where it is the only thing in its column and has no label beside it.
|
|
35
|
+
'close-circle': 'M12 21a9 9 0 100-18 9 9 0 000 18zM9.5 9.5l5 5M14.5 9.5l-5 5',
|
|
36
|
+
// Up and down as well as left and right: reordering a list and opening an accordion
|
|
37
|
+
// section both point along the axis the content moves in.
|
|
38
|
+
'chevron-up': 'M6 15l6-6 6 6',
|
|
39
|
+
'chevron-down': 'M6 9l6 6 6-6',
|
|
40
|
+
};
|
|
41
|
+
export function Icon({ name, className, label }) {
|
|
42
|
+
return (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", className: cn('size-5 shrink-0', className), "aria-hidden": label ? undefined : true, role: label ? 'img' : undefined, "aria-label": label, children: _jsx("path", { d: PATHS[name] }) }));
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Icon';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Icon';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TextField } from '@radix-ui/themes';
|
|
2
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
export type InputSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
/**
|
|
5
|
+
* Built on Themes' own prop type rather than on `InputHTMLAttributes`.
|
|
6
|
+
*
|
|
7
|
+
* Themes narrows a few of the native props — `size` and `color` mean its own scales — so
|
|
8
|
+
* deriving from it is what keeps a caller from passing something the component will not
|
|
9
|
+
* accept and only finding out at runtime.
|
|
10
|
+
*/
|
|
11
|
+
export interface InputProps extends Omit<ComponentPropsWithoutRef<typeof TextField.Root>, 'size' | 'color'> {
|
|
12
|
+
size?: InputSize;
|
|
13
|
+
invalid?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare const Input: import("react").ForwardRefExoticComponent<InputProps & import("react").RefAttributes<HTMLInputElement>>;
|