@seekalfred/primitives 0.3.0 → 0.5.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 +25 -0
- package/dist/avatar/avatar.d.ts +1 -1
- package/dist/badge/badge.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/input/input.d.ts +18 -2
- package/dist/input/input.js +116 -13
- package/dist/input/input.js.map +1 -1
- package/dist/lib/cn.d.ts +55 -5
- package/dist/lib/cn.js +64 -6
- package/dist/lib/cn.js.map +1 -1
- package/dist/progress/progress.js +3 -3
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.js +2 -2
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/select/select.d.ts +14 -2
- package/dist/select/select.js +23 -6
- package/dist/select/select.js.map +1 -1
- package/dist/sheet/sheet.js +3 -2
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/slider/slider.js +2 -2
- package/dist/slider/slider.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table/table.js +14 -14
- package/dist/table/table.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -488,6 +488,31 @@ invalid styling on only one of them.
|
|
|
488
488
|
| `min-h-[80px]` | `min-h-20` | same 80px, from the spacing scale |
|
|
489
489
|
| `relative` + `absolute left-3` + `pl-9` | `startIcon` / `endIcon` | **delete the wrapper and the padding** — see below |
|
|
490
490
|
|
|
491
|
+
### Sizing a field
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
<Input size="sm" aria-label="Filter" />
|
|
495
|
+
<Select size="sm" label="Status">…</Select>
|
|
496
|
+
<Button size="sm">Apply</Button>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
`sm` | `md` | `lg`, default `md`, on the same `--density-control-*` tokens as
|
|
500
|
+
`Button` — so a field, a picker and a button on one rung are the same height.
|
|
501
|
+
In the legacy app 36 of 97 `Input` call sites and 14 of 32 `SelectTrigger` ones
|
|
502
|
+
hand-rolled a height, and a `size="sm"` Button (167 call sites) sat 10px off
|
|
503
|
+
the field it belonged to.
|
|
504
|
+
|
|
505
|
+
Two things worth knowing:
|
|
506
|
+
|
|
507
|
+
- **`size` replaces the native attribute.** React Aria inherits `size?: number`
|
|
508
|
+
(a character count) from `InputHTMLAttributes`; `InputProps` omits it. Set a
|
|
509
|
+
width with `className`. No legacy call site used it.
|
|
510
|
+
- **Type does not scale with the box.** The field stays at 16px on every rung.
|
|
511
|
+
Below that, iOS Safari zooms the viewport when the field takes focus.
|
|
512
|
+
|
|
513
|
+
`Textarea` has no `size` — it is `min-h` driven and no call site overrides its
|
|
514
|
+
height.
|
|
515
|
+
|
|
491
516
|
### Icons in a field
|
|
492
517
|
|
|
493
518
|
```tsx
|
package/dist/avatar/avatar.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
17
17
|
* class assembled at runtime is invisible to it and would never be emitted.
|
|
18
18
|
*/
|
|
19
19
|
export declare const avatarVariants: (props?: ({
|
|
20
|
-
size?: "
|
|
20
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | null | undefined;
|
|
21
21
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
22
22
|
/**
|
|
23
23
|
* A user avatar.
|
package/dist/badge/badge.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { ComponentPropsWithoutRef } from "react";
|
|
|
36
36
|
* across every shadcn control.
|
|
37
37
|
*/
|
|
38
38
|
export declare const badgeVariants: (props?: ({
|
|
39
|
-
tone?: "neutral" | "success" | "warning" | "danger" | "info" |
|
|
39
|
+
tone?: "primary" | "neutral" | "success" | "warning" | "danger" | "info" | null | undefined;
|
|
40
40
|
appearance?: "outline" | "solid" | "soft" | null | undefined;
|
|
41
41
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
42
42
|
shape?: "rounded" | "pill" | null | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Card, CardContent, CardContentProps, CardDescription, CardDescriptionPr
|
|
|
11
11
|
import { Checkbox, CheckboxProps, checkboxBoxVariants, checkboxVariants } from "./checkbox/checkbox.js";
|
|
12
12
|
import { Input, InputProps, inputVariants } from "./input/input.js";
|
|
13
13
|
import { Label, LabelProps, labelVariants } from "./label/label.js";
|
|
14
|
-
import { cn } from "./lib/cn.js";
|
|
14
|
+
import { cn, cnAll } from "./lib/cn.js";
|
|
15
15
|
import { Menu, MenuItem, MenuItemProps, MenuProps, MenuSeparator, MenuTrigger, menuItemVariants, menuPopoverVariants, menuSeparatorVariants, menuVariants } from "./menu/menu.js";
|
|
16
16
|
import { Popover, PopoverProps, PopoverTrigger, popoverVariants } from "./popover/popover.js";
|
|
17
17
|
import { Progress, ProgressProps, progressFillVariants, progressTrackVariants, progressVariants } from "./progress/progress.js";
|
|
@@ -29,4 +29,4 @@ import { Tab, TabList, TabListProps, TabPanel, TabPanelProps, TabProps, Tabs, Ta
|
|
|
29
29
|
import { Textarea, TextareaProps, textareaVariants } from "./textarea/textarea.js";
|
|
30
30
|
import { ToastContent, ToastRegion, ToastRegionProps, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants } from "./toast/toast.js";
|
|
31
31
|
import { Tooltip, TooltipProps, TooltipTrigger, TooltipTriggerProps, tooltipVariants } from "./tooltip/tooltip.js";
|
|
32
|
-
export { Accordion, AccordionItem, type AccordionItemProps, AccordionPanel, type AccordionPanelProps, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, type AlertDialogProps, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, BreadcrumbItem, type BreadcrumbItemProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, Calendar, type CalendarProps, CalendarRange, type CalendarRangeProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, Checkbox, type CheckboxProps, type Column, Dialog, DialogFooter, type DialogFooterProps, type DialogProps, DialogTrigger, Input, type InputProps, Label, type LabelProps, Menu, MenuItem, type MenuItemProps, type MenuProps, MenuSeparator, MenuTrigger, Popover, type PopoverProps, PopoverTrigger, Progress, type ProgressProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type RowKey, ScrollArea, type ScrollAreaProps, Select, SelectItem, type SelectItemProps, type SelectProps, Separator, type SeparatorProps, Sheet, type SheetProps, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, type TableProps, Tabs, type TabsProps, Textarea, type TextareaProps, type ToastContent, ToastRegion, type ToastRegionProps, Tooltip, type TooltipProps, TooltipTrigger, type TooltipTriggerProps, alertVariants, avatarVariants, badgeVariants, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants, buttonVariants, calendarCellVariants, calendarClearVariants, calendarGridVariants, calendarHeaderCellVariants, calendarHeaderVariants, calendarHeadingVariants, calendarLabelVariants, calendarMonthsVariants, calendarNavVariants, calendarVariants, cardContentVariants, cardDescriptionVariants, cardFooterVariants, cardHeaderVariants, cardTitleVariants, cardVariants, checkboxBoxVariants, checkboxVariants, cn, createColumns, dialogDescriptionVariants, dialogFooterVariants, dialogOverlayVariants, dialogPanelVariants, dialogTitleVariants, inputVariants, labelVariants, menuItemVariants, menuPopoverVariants, menuSeparatorVariants, menuVariants, popoverVariants, progressFillVariants, progressTrackVariants, progressVariants, radioGroupVariants, radioVariants, scrollAreaVariants, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants, separatorVariants, sheetOverlayVariants, sheetPanelVariants, skeletonVariants, sliderBarVariants, sliderFillVariants, sliderThumbVariants, sliderTrackVariants, sliderVariants, spinnerVariants, switchThumbVariants, switchTrackVariants, tabListVariants, tabPanelVariants, tabVariants, tableCardVariants, tableCellVariants, tableColumnVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableVariants, tabsVariants, textareaVariants, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants, tooltipVariants };
|
|
32
|
+
export { Accordion, AccordionItem, type AccordionItemProps, AccordionPanel, type AccordionPanelProps, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, type AlertDialogProps, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, BreadcrumbItem, type BreadcrumbItemProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, Calendar, type CalendarProps, CalendarRange, type CalendarRangeProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, Checkbox, type CheckboxProps, type Column, Dialog, DialogFooter, type DialogFooterProps, type DialogProps, DialogTrigger, Input, type InputProps, Label, type LabelProps, Menu, MenuItem, type MenuItemProps, type MenuProps, MenuSeparator, MenuTrigger, Popover, type PopoverProps, PopoverTrigger, Progress, type ProgressProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type RowKey, ScrollArea, type ScrollAreaProps, Select, SelectItem, type SelectItemProps, type SelectProps, Separator, type SeparatorProps, Sheet, type SheetProps, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, type TableProps, Tabs, type TabsProps, Textarea, type TextareaProps, type ToastContent, ToastRegion, type ToastRegionProps, Tooltip, type TooltipProps, TooltipTrigger, type TooltipTriggerProps, alertVariants, avatarVariants, badgeVariants, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants, buttonVariants, calendarCellVariants, calendarClearVariants, calendarGridVariants, calendarHeaderCellVariants, calendarHeaderVariants, calendarHeadingVariants, calendarLabelVariants, calendarMonthsVariants, calendarNavVariants, calendarVariants, cardContentVariants, cardDescriptionVariants, cardFooterVariants, cardHeaderVariants, cardTitleVariants, cardVariants, checkboxBoxVariants, checkboxVariants, cn, cnAll, createColumns, dialogDescriptionVariants, dialogFooterVariants, dialogOverlayVariants, dialogPanelVariants, dialogTitleVariants, inputVariants, labelVariants, menuItemVariants, menuPopoverVariants, menuSeparatorVariants, menuVariants, popoverVariants, progressFillVariants, progressTrackVariants, progressVariants, radioGroupVariants, radioVariants, scrollAreaVariants, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants, separatorVariants, sheetOverlayVariants, sheetPanelVariants, skeletonVariants, sliderBarVariants, sliderFillVariants, sliderThumbVariants, sliderTrackVariants, sliderVariants, spinnerVariants, switchThumbVariants, switchTrackVariants, tabListVariants, tabPanelVariants, tabVariants, tableCardVariants, tableCellVariants, tableColumnVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableVariants, tabsVariants, textareaVariants, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants, tooltipVariants };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cn } from "./lib/cn.js";
|
|
1
|
+
import { cn, cnAll } from "./lib/cn.js";
|
|
2
2
|
import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "./accordion/accordion.js";
|
|
3
3
|
import { Dialog, DialogFooter, DialogTrigger, dialogDescriptionVariants, dialogFooterVariants, dialogOverlayVariants, dialogPanelVariants, dialogTitleVariants } from "./dialog/dialog.js";
|
|
4
4
|
import { AlertDialog } from "./alert-dialog/alert-dialog.js";
|
|
@@ -29,4 +29,4 @@ import { Tab, TabList, TabPanel, Tabs, tabListVariants, tabPanelVariants, tabVar
|
|
|
29
29
|
import { Textarea, textareaVariants } from "./textarea/textarea.js";
|
|
30
30
|
import { ToastRegion, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants } from "./toast/toast.js";
|
|
31
31
|
import { Tooltip, TooltipTrigger, tooltipVariants } from "./tooltip/tooltip.js";
|
|
32
|
-
export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Alert, AlertDialog, Avatar, Badge, BreadcrumbItem, Breadcrumbs, Button, Calendar, CalendarRange, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Dialog, DialogFooter, DialogTrigger, Input, Label, Menu, MenuItem, MenuSeparator, MenuTrigger, Popover, PopoverTrigger, Progress, Radio, RadioGroup, ScrollArea, Select, SelectItem, Separator, Sheet, Skeleton, Slider, Spinner, Switch, Tab, TabList, TabPanel, Table, Tabs, Textarea, ToastRegion, Tooltip, TooltipTrigger, alertVariants, avatarVariants, badgeVariants, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants, buttonVariants, calendarCellVariants, calendarClearVariants, calendarGridVariants, calendarHeaderCellVariants, calendarHeaderVariants, calendarHeadingVariants, calendarLabelVariants, calendarMonthsVariants, calendarNavVariants, calendarVariants, cardContentVariants, cardDescriptionVariants, cardFooterVariants, cardHeaderVariants, cardTitleVariants, cardVariants, checkboxBoxVariants, checkboxVariants, cn, createColumns, dialogDescriptionVariants, dialogFooterVariants, dialogOverlayVariants, dialogPanelVariants, dialogTitleVariants, inputVariants, labelVariants, menuItemVariants, menuPopoverVariants, menuSeparatorVariants, menuVariants, popoverVariants, progressFillVariants, progressTrackVariants, progressVariants, radioGroupVariants, radioVariants, scrollAreaVariants, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants, separatorVariants, sheetOverlayVariants, sheetPanelVariants, skeletonVariants, sliderBarVariants, sliderFillVariants, sliderThumbVariants, sliderTrackVariants, sliderVariants, spinnerVariants, switchThumbVariants, switchTrackVariants, tabListVariants, tabPanelVariants, tabVariants, tableCardVariants, tableCellVariants, tableColumnVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableVariants, tabsVariants, textareaVariants, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants, tooltipVariants };
|
|
32
|
+
export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Alert, AlertDialog, Avatar, Badge, BreadcrumbItem, Breadcrumbs, Button, Calendar, CalendarRange, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Dialog, DialogFooter, DialogTrigger, Input, Label, Menu, MenuItem, MenuSeparator, MenuTrigger, Popover, PopoverTrigger, Progress, Radio, RadioGroup, ScrollArea, Select, SelectItem, Separator, Sheet, Skeleton, Slider, Spinner, Switch, Tab, TabList, TabPanel, Table, Tabs, Textarea, ToastRegion, Tooltip, TooltipTrigger, alertVariants, avatarVariants, badgeVariants, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants, buttonVariants, calendarCellVariants, calendarClearVariants, calendarGridVariants, calendarHeaderCellVariants, calendarHeaderVariants, calendarHeadingVariants, calendarLabelVariants, calendarMonthsVariants, calendarNavVariants, calendarVariants, cardContentVariants, cardDescriptionVariants, cardFooterVariants, cardHeaderVariants, cardTitleVariants, cardVariants, checkboxBoxVariants, checkboxVariants, cn, cnAll, createColumns, dialogDescriptionVariants, dialogFooterVariants, dialogOverlayVariants, dialogPanelVariants, dialogTitleVariants, inputVariants, labelVariants, menuItemVariants, menuPopoverVariants, menuSeparatorVariants, menuVariants, popoverVariants, progressFillVariants, progressTrackVariants, progressVariants, radioGroupVariants, radioVariants, scrollAreaVariants, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants, separatorVariants, sheetOverlayVariants, sheetPanelVariants, skeletonVariants, sliderBarVariants, sliderFillVariants, sliderThumbVariants, sliderTrackVariants, sliderVariants, spinnerVariants, switchThumbVariants, switchTrackVariants, tabListVariants, tabPanelVariants, tabVariants, tableCardVariants, tableCellVariants, tableColumnVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableVariants, tabsVariants, textareaVariants, toast, toastCloseVariants, toastQueue, toastRegionVariants, toastVariants, tooltipVariants };
|
package/dist/input/input.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { ReactNode } from "react";
|
|
|
24
24
|
* be an existing one, after `popover`, `accent` and the scrim.
|
|
25
25
|
*/
|
|
26
26
|
export declare const inputVariants: (props?: ({
|
|
27
|
+
size?: "sm" | "md" | "lg" | null | undefined;
|
|
27
28
|
hasStartIcon?: boolean | null | undefined;
|
|
28
29
|
hasEndIcon?: boolean | null | undefined;
|
|
29
30
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -34,9 +35,24 @@ export declare const inputVariants: (props?: ({
|
|
|
34
35
|
* <Input type="email" placeholder="you@example.com" value={v} onChange={e => set(e.target.value)} />
|
|
35
36
|
* <Input aria-label="Search" startIcon={<SearchIcon aria-hidden />} />
|
|
36
37
|
*/
|
|
37
|
-
interface InputProps$1 extends Omit<InputProps, "className" | "style"> {
|
|
38
|
+
interface InputProps$1 extends Omit<InputProps, "className" | "style" | "size"> {
|
|
38
39
|
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
39
40
|
className?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Control scale. Height and inline padding come from the same
|
|
43
|
+
* `--density-control-*` tokens as Button, so a field and a button on one
|
|
44
|
+
* rung are the same height.
|
|
45
|
+
*
|
|
46
|
+
* Type does not scale with it: the field stays at 16px everywhere, because
|
|
47
|
+
* iOS Safari zooms the viewport when a focused input's text is smaller.
|
|
48
|
+
*
|
|
49
|
+
* This REPLACES the native `size` attribute, which React Aria inherits from
|
|
50
|
+
* `InputHTMLAttributes` as a character count. No call site in the census
|
|
51
|
+
* used it; set a width with `className` instead.
|
|
52
|
+
*
|
|
53
|
+
* @default "md"
|
|
54
|
+
*/
|
|
55
|
+
size?: "sm" | "md" | "lg";
|
|
40
56
|
/**
|
|
41
57
|
* Decorative glyph at the leading edge of the field.
|
|
42
58
|
*
|
|
@@ -58,7 +74,7 @@ interface InputProps$1 extends Omit<InputProps, "className" | "style"> {
|
|
|
58
74
|
/** Decorative glyph at the trailing edge. Same rules as {@link InputProps.startIcon}. */
|
|
59
75
|
endIcon?: ReactNode;
|
|
60
76
|
}
|
|
61
|
-
declare
|
|
77
|
+
declare const Input$1: import("react").ForwardRefExoticComponent<InputProps$1 & import("react").RefAttributes<HTMLInputElement>>;
|
|
62
78
|
//#endregion
|
|
63
79
|
export { Input$1 as Input, InputProps$1 as InputProps };
|
|
64
80
|
//# sourceMappingURL=input.d.ts.map
|
package/dist/input/input.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { Input } from "react-aria-components";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { forwardRef } from "react";
|
|
7
8
|
//#region src/input/input.tsx
|
|
8
9
|
/**
|
|
9
10
|
* Input, ported from the legacy shadcn component onto React Aria's Input.
|
|
@@ -29,14 +30,66 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
29
30
|
*/
|
|
30
31
|
const inputVariants = cva([
|
|
31
32
|
fieldSurfaceVariants(),
|
|
32
|
-
"h-(--density-control-h-md) px-(--density-control-pad-md)",
|
|
33
33
|
"text-base",
|
|
34
34
|
"file:inline-flex file:border-0 file:bg-transparent file:text-sm file:font-medium",
|
|
35
35
|
"file:text-foreground"
|
|
36
|
-
], {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
], {
|
|
37
|
+
variants: {
|
|
38
|
+
/**
|
|
39
|
+
* Height and inline padding from the control scale, so `data-density`
|
|
40
|
+
* moves a whole form at once. Same three names and the same tokens as
|
|
41
|
+
* Button, because they sit in the same row: Button's `sm` is the app's
|
|
42
|
+
* second most common at 167 call sites, and while the field was locked
|
|
43
|
+
* to `md` the pair was 10px out of alignment with no supported fix.
|
|
44
|
+
*
|
|
45
|
+
* Type does NOT scale, which is the one place this axis deliberately
|
|
46
|
+
* departs from Button's. iOS Safari zooms the viewport when a focused
|
|
47
|
+
* field's text is under 16px, so `text-base` stays pinned in the base
|
|
48
|
+
* at every rung -- see the note there. Shrinking the box is a layout
|
|
49
|
+
* choice; shrinking the type is a bug on a phone.
|
|
50
|
+
*/
|
|
51
|
+
size: {
|
|
52
|
+
sm: "h-(--density-control-h-sm) px-(--density-control-pad-sm)",
|
|
53
|
+
md: "h-(--density-control-h-md) px-(--density-control-pad-md)",
|
|
54
|
+
lg: "h-(--density-control-h-lg) px-(--density-control-pad-lg)"
|
|
55
|
+
},
|
|
56
|
+
hasStartIcon: { true: "" },
|
|
57
|
+
hasEndIcon: { true: "" }
|
|
58
|
+
},
|
|
59
|
+
compoundVariants: [
|
|
60
|
+
{
|
|
61
|
+
size: "sm",
|
|
62
|
+
hasStartIcon: true,
|
|
63
|
+
class: "ps-[calc(var(--density-control-pad-sm)_+_var(--icon-sm)_+_var(--density-control-gap))]"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
size: "md",
|
|
67
|
+
hasStartIcon: true,
|
|
68
|
+
class: "ps-[calc(var(--density-control-pad-md)_+_var(--icon-sm)_+_var(--density-control-gap))]"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
size: "lg",
|
|
72
|
+
hasStartIcon: true,
|
|
73
|
+
class: "ps-[calc(var(--density-control-pad-lg)_+_var(--icon-sm)_+_var(--density-control-gap))]"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
size: "sm",
|
|
77
|
+
hasEndIcon: true,
|
|
78
|
+
class: "pe-[calc(var(--density-control-pad-sm)_+_var(--icon-sm)_+_var(--density-control-gap))]"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
size: "md",
|
|
82
|
+
hasEndIcon: true,
|
|
83
|
+
class: "pe-[calc(var(--density-control-pad-md)_+_var(--icon-sm)_+_var(--density-control-gap))]"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
size: "lg",
|
|
87
|
+
hasEndIcon: true,
|
|
88
|
+
class: "pe-[calc(var(--density-control-pad-lg)_+_var(--icon-sm)_+_var(--density-control-gap))]"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
defaultVariants: { size: "md" }
|
|
92
|
+
});
|
|
40
93
|
/**
|
|
41
94
|
* An affix slot.
|
|
42
95
|
*
|
|
@@ -56,15 +109,59 @@ const affixVariants = cva([
|
|
|
56
109
|
"pointer-events-none absolute inset-y-0 flex items-center",
|
|
57
110
|
"text-muted-foreground",
|
|
58
111
|
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-(--icon-sm)"
|
|
59
|
-
], {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
112
|
+
], {
|
|
113
|
+
variants: {
|
|
114
|
+
side: {
|
|
115
|
+
start: "",
|
|
116
|
+
end: ""
|
|
117
|
+
},
|
|
118
|
+
size: {
|
|
119
|
+
sm: "",
|
|
120
|
+
md: "",
|
|
121
|
+
lg: ""
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
compoundVariants: [
|
|
125
|
+
{
|
|
126
|
+
side: "start",
|
|
127
|
+
size: "sm",
|
|
128
|
+
class: "start-(--density-control-pad-sm)"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
side: "start",
|
|
132
|
+
size: "md",
|
|
133
|
+
class: "start-(--density-control-pad-md)"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
side: "start",
|
|
137
|
+
size: "lg",
|
|
138
|
+
class: "start-(--density-control-pad-lg)"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
side: "end",
|
|
142
|
+
size: "sm",
|
|
143
|
+
class: "end-(--density-control-pad-sm)"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
side: "end",
|
|
147
|
+
size: "md",
|
|
148
|
+
class: "end-(--density-control-pad-md)"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
side: "end",
|
|
152
|
+
size: "lg",
|
|
153
|
+
class: "end-(--density-control-pad-lg)"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
defaultVariants: { size: "md" }
|
|
157
|
+
});
|
|
158
|
+
const Input$1 = forwardRef(function Input$2({ className, size = "md", startIcon, endIcon, ...props }, ref) {
|
|
64
159
|
const input = /* @__PURE__ */ jsx(Input, {
|
|
160
|
+
ref,
|
|
65
161
|
...props,
|
|
66
162
|
"data-slot": "input",
|
|
67
163
|
className: cn(inputVariants({
|
|
164
|
+
size,
|
|
68
165
|
hasStartIcon: startIcon !== void 0,
|
|
69
166
|
hasEndIcon: endIcon !== void 0
|
|
70
167
|
}), className)
|
|
@@ -77,17 +174,23 @@ function Input$1({ className, startIcon, endIcon, ...props }) {
|
|
|
77
174
|
input,
|
|
78
175
|
startIcon === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
79
176
|
"data-slot": "input-start-icon",
|
|
80
|
-
className: affixVariants({
|
|
177
|
+
className: affixVariants({
|
|
178
|
+
side: "start",
|
|
179
|
+
size
|
|
180
|
+
}),
|
|
81
181
|
children: startIcon
|
|
82
182
|
}),
|
|
83
183
|
endIcon === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
84
184
|
"data-slot": "input-end-icon",
|
|
85
|
-
className: affixVariants({
|
|
185
|
+
className: affixVariants({
|
|
186
|
+
side: "end",
|
|
187
|
+
size
|
|
188
|
+
}),
|
|
86
189
|
children: endIcon
|
|
87
190
|
})
|
|
88
191
|
]
|
|
89
192
|
});
|
|
90
|
-
}
|
|
193
|
+
});
|
|
91
194
|
//#endregion
|
|
92
195
|
export { Input$1 as Input, inputVariants };
|
|
93
196
|
|
package/dist/input/input.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","names":["Input","AriaInput"],"sources":["../../src/input/input.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport { Input as AriaInput, type InputProps as AriaInputProps } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Input, ported from the legacy shadcn component onto React Aria's Input.\n *\n * Census: 25 files, 51 uses -- the busiest control in the app after Button.\n * Props are `onChange` 36, `value` 35, `placeholder` 33, `id` 23, `type` 21.\n *\n * EVERY ONE OF THOSE 51 CALL SITES MIGRATES UNCHANGED. React Aria's `Input`\n * takes NATIVE props -- `disabled`, `aria-invalid`, `onChange` with a real\n * event -- not the `isDisabled`/`onChange(value)` shape the rest of React\n * Aria uses. It reads `InputContext` when one is present, so the same\n * component is a bare styled input today and composes inside a `TextField`\n * the moment one is wrapped around it. There is no rename to schedule.\n *\n * That is also why this is not `TextField`. Wrapping 51 call sites in a\n * field they do not have, to get a `onChange(value: string)` they did not\n * ask for, would be the migration doing work on the app's behalf.\n *\n * `border-input` becomes `border-border`: in the legacy theme `--input` is\n * character-for-character `--border` on light (`oklch(0.922 0 0)`), and the\n * dark values differ only in alpha. A fourth token role that turns out to\n * be an existing one, after `popover`, `accent` and the scrim.\n */\nexport const inputVariants = cva(\n [\n fieldSurfaceVariants(),\n\n // Height from the density scale, not the legacy h-9. An input and a\n // Button sit next to each other constantly and must be the same height;\n // Button is already on this scale, so 36px would have made every form row\n // 10px out of alignment at the default density.\n \"h-(--density-control-h-md) px-(--density-control-pad-md)\",\n\n // text-base always, not the legacy `text-base md:text-sm`. 16px is the\n // threshold below which iOS Safari zooms the viewport on focus, and the\n // legacy dropped to 14px at exactly the widths where that stops being a\n // phone -- which reads as a deliberate fix but leaves the field a\n // different size on either side of a breakpoint for no visual reason.\n \"text-base\",\n\n // type=\"file\" has one call site, which is not zero. Enough to keep it\n // from rendering as an unstyled user-agent button.\n \"file:inline-flex file:border-0 file:bg-transparent file:text-sm file:font-medium\",\n \"file:text-foreground\",\n ],\n {\n variants: {\n // The affix slots float ABOVE the field rather than sitting beside it\n // in a flex row, so the only thing that keeps the value from running\n // underneath a glyph is this padding. It is derived from the same three\n // tokens that place the glyph -- inline padding, icon box, control gap\n // -- so `data-density` moves the text and the glyph together. That is\n // the part a hand-tuned `pl-9` at the call site cannot do, and the\n // product app has eighteen of those.\n hasStartIcon: {\n true: \"ps-[calc(var(--density-control-pad-md)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n hasEndIcon: {\n true: \"pe-[calc(var(--density-control-pad-md)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n },\n },\n);\n\n/**\n * An affix slot.\n *\n * `pointer-events-none` is load-bearing, not cosmetic: the slot is painted on\n * top of the field, so without it the glyph swallows the click that was meant\n * to focus the input. Which also fixes the slot's scope -- these are\n * DECORATIVE marks. An interactive trailing control (a password reveal, a\n * clear button) needs its own pointer events and its own accessible name, and\n * the census found no call site for one, so it is not built here.\n *\n * Logical properties throughout. The product app's eighteen hand-rolled\n * adornments are all `absolute left-*`, with `absolute start-*` at zero\n * occurrences, so every one of them puts the glyph on the wrong side under\n * RTL and lets the text run over it.\n */\nconst affixVariants = cva(\n [\n \"pointer-events-none absolute inset-y-0 flex items-center\",\n \"text-muted-foreground\",\n // Sized like Button's icon slots, and skipped when the caller sized the\n // glyph themselves. An svg with neither a width attribute nor a resolved\n // class renders at 0x0 and every position assertion still passes.\n \"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n ],\n {\n variants: {\n side: {\n start: \"start-(--density-control-pad-md)\",\n end: \"end-(--density-control-pad-md)\",\n },\n },\n },\n);\n\n/**\n * A single-line text field.\n *\n * @example\n * <Input type=\"email\" placeholder=\"you@example.com\" value={v} onChange={e => set(e.target.value)} />\n * <Input aria-label=\"Search\" startIcon={<SearchIcon aria-hidden />} />\n */\nexport interface InputProps extends Omit<AriaInputProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Decorative glyph at the leading edge of the field.\n *\n * Mark it `aria-hidden` -- the field's label carries the accessible name,\n * and a second name source only makes it announce twice. It is sized at\n * `--icon-sm` unless it carries a `size-*` class of its own, positioned\n * from `--density-control-pad-md`, and the field's leading padding grows to\n * match so the value cannot run underneath it.\n *\n * The slot is `pointer-events: none`, so a click on the glyph focuses the\n * field. That makes it decorative by construction: an interactive\n * adornment -- a password reveal, a clear button -- is NOT this prop.\n *\n * Two named slots rather than one `icon` plus a position prop, matching\n * Button: a search field can legitimately carry a leading glyph AND a\n * trailing unit, which one slot cannot express.\n */\n startIcon?: ReactNode;\n /** Decorative glyph at the trailing edge. Same rules as {@link InputProps.startIcon}. */\n endIcon?: ReactNode;\n}\n\nexport function Input({ className, startIcon, endIcon, ...props }: InputProps) {\n const input = (\n <AriaInput\n {...props}\n data-slot=\"input\"\n className={cn(\n inputVariants({\n hasStartIcon: startIcon !== undefined,\n hasEndIcon: endIcon !== undefined,\n }),\n className,\n )}\n />\n );\n\n // With neither slot used there is no wrapper AT ALL, so the DOM is\n // byte-identical to what shipped before this prop existed. That is what\n // keeps this additive for the 97 call sites that pass no glyph, and\n // NoIconsLeaveDomUnchanged is the assertion holding it true.\n if (startIcon === undefined && endIcon === undefined) return input;\n\n // An overlay, not a flex row. Moving the border onto this wrapper would\n // take the painted surface off [data-slot=\"input\"] -- the selector the\n // escape hatch is built on -- and would force the focus ring onto\n // :focus-within and the invalid border onto :has(), when React Aria\n // already puts data-focus-visible and data-invalid on the input itself.\n return (\n <span data-slot=\"input-wrapper\" className=\"relative block w-full\">\n {input}\n {startIcon === undefined ? null : (\n <span data-slot=\"input-start-icon\" className={affixVariants({ side: \"start\" })}>\n {startIcon}\n </span>\n )}\n {endIcon === undefined ? null : (\n <span data-slot=\"input-end-icon\" className={affixVariants({ side: \"end\" })}>\n {endIcon}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,gBAAgB,IAC3B;CACE,qBAAqB;CAMrB;CAOA;CAIA;CACA;AACF,GACA,EACE,UAAU;CAQR,cAAc,EACZ,MAAM,yFACR;CACA,YAAY,EACV,MAAM,yFACR;AACF,EACF,CACF;;;;;;;;;;;;;;;;AAiBA,MAAM,gBAAgB,IACpB;CACE;CACA;CAIA;AACF,GACA,EACE,UAAU,EACR,MAAM;CACJ,OAAO;CACP,KAAK;AACP,EACF,EACF,CACF;AAkCA,SAAgBA,QAAM,EAAE,WAAW,WAAW,SAAS,GAAG,SAAqB;CAC7E,MAAM,QACJ,oBAACC,OAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GACT,cAAc;GACZ,cAAc,cAAc,KAAA;GAC5B,YAAY,YAAY,KAAA;EAC1B,CAAC,GACD,SACF;CACD,CAAA;CAOH,IAAI,cAAc,KAAA,KAAa,YAAY,KAAA,GAAW,OAAO;CAO7D,OACE,qBAAC,QAAD;EAAM,aAAU;EAAgB,WAAU;EAA1C,UAAA;GACG;GACA,cAAc,KAAA,IAAY,OACzB,oBAAC,QAAD;IAAM,aAAU;IAAmB,WAAW,cAAc,EAAE,MAAM,QAAQ,CAAC;IAC1E,UAAA;GACG,CAAA;GAEP,YAAY,KAAA,IAAY,OACvB,oBAAC,QAAD;IAAM,aAAU;IAAiB,WAAW,cAAc,EAAE,MAAM,MAAM,CAAC;IACtE,UAAA;GACG,CAAA;EAEJ;;AAEV"}
|
|
1
|
+
{"version":3,"file":"input.js","names":["Input","AriaInput"],"sources":["../../src/input/input.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport { Input as AriaInput, type InputProps as AriaInputProps } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Input, ported from the legacy shadcn component onto React Aria's Input.\n *\n * Census: 25 files, 51 uses -- the busiest control in the app after Button.\n * Props are `onChange` 36, `value` 35, `placeholder` 33, `id` 23, `type` 21.\n *\n * EVERY ONE OF THOSE 51 CALL SITES MIGRATES UNCHANGED. React Aria's `Input`\n * takes NATIVE props -- `disabled`, `aria-invalid`, `onChange` with a real\n * event -- not the `isDisabled`/`onChange(value)` shape the rest of React\n * Aria uses. It reads `InputContext` when one is present, so the same\n * component is a bare styled input today and composes inside a `TextField`\n * the moment one is wrapped around it. There is no rename to schedule.\n *\n * That is also why this is not `TextField`. Wrapping 51 call sites in a\n * field they do not have, to get a `onChange(value: string)` they did not\n * ask for, would be the migration doing work on the app's behalf.\n *\n * `border-input` becomes `border-border`: in the legacy theme `--input` is\n * character-for-character `--border` on light (`oklch(0.922 0 0)`), and the\n * dark values differ only in alpha. A fourth token role that turns out to\n * be an existing one, after `popover`, `accent` and the scrim.\n */\nexport const inputVariants = cva(\n [\n fieldSurfaceVariants(),\n\n // text-base always, not the legacy `text-base md:text-sm`. 16px is the\n // threshold below which iOS Safari zooms the viewport on focus, and the\n // legacy dropped to 14px at exactly the widths where that stops being a\n // phone -- which reads as a deliberate fix but leaves the field a\n // different size on either side of a breakpoint for no visual reason.\n \"text-base\",\n\n // type=\"file\" has one call site, which is not zero. Enough to keep it\n // from rendering as an unstyled user-agent button.\n \"file:inline-flex file:border-0 file:bg-transparent file:text-sm file:font-medium\",\n \"file:text-foreground\",\n ],\n {\n variants: {\n /**\n * Height and inline padding from the control scale, so `data-density`\n * moves a whole form at once. Same three names and the same tokens as\n * Button, because they sit in the same row: Button's `sm` is the app's\n * second most common at 167 call sites, and while the field was locked\n * to `md` the pair was 10px out of alignment with no supported fix.\n *\n * Type does NOT scale, which is the one place this axis deliberately\n * departs from Button's. iOS Safari zooms the viewport when a focused\n * field's text is under 16px, so `text-base` stays pinned in the base\n * at every rung -- see the note there. Shrinking the box is a layout\n * choice; shrinking the type is a bug on a phone.\n */\n size: {\n sm: \"h-(--density-control-h-sm) px-(--density-control-pad-sm)\",\n md: \"h-(--density-control-h-md) px-(--density-control-pad-md)\",\n lg: \"h-(--density-control-h-lg) px-(--density-control-pad-lg)\",\n },\n // Declared empty and filled by the compound variants below: the affix\n // inset depends on BOTH whether a slot is used and which rung it is on.\n hasStartIcon: { true: \"\" },\n hasEndIcon: { true: \"\" },\n },\n compoundVariants: [\n // The affix slots float ABOVE the field rather than sitting beside it\n // in a flex row, so the only thing keeping the value from running\n // underneath a glyph is this padding. It is the same three tokens that\n // place the glyph -- inline padding, icon box, control gap -- so the\n // text and the glyph move together across density AND size. A\n // hand-tuned `pl-9` at the call site can do neither, and the product\n // app has eighteen of those.\n {\n size: \"sm\",\n hasStartIcon: true,\n class:\n \"ps-[calc(var(--density-control-pad-sm)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n {\n size: \"md\",\n hasStartIcon: true,\n class:\n \"ps-[calc(var(--density-control-pad-md)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n {\n size: \"lg\",\n hasStartIcon: true,\n class:\n \"ps-[calc(var(--density-control-pad-lg)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n {\n size: \"sm\",\n hasEndIcon: true,\n class:\n \"pe-[calc(var(--density-control-pad-sm)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n {\n size: \"md\",\n hasEndIcon: true,\n class:\n \"pe-[calc(var(--density-control-pad-md)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n {\n size: \"lg\",\n hasEndIcon: true,\n class:\n \"pe-[calc(var(--density-control-pad-lg)_+_var(--icon-sm)_+_var(--density-control-gap))]\",\n },\n ],\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * An affix slot.\n *\n * `pointer-events-none` is load-bearing, not cosmetic: the slot is painted on\n * top of the field, so without it the glyph swallows the click that was meant\n * to focus the input. Which also fixes the slot's scope -- these are\n * DECORATIVE marks. An interactive trailing control (a password reveal, a\n * clear button) needs its own pointer events and its own accessible name, and\n * the census found no call site for one, so it is not built here.\n *\n * Logical properties throughout. The product app's eighteen hand-rolled\n * adornments are all `absolute left-*`, with `absolute start-*` at zero\n * occurrences, so every one of them puts the glyph on the wrong side under\n * RTL and lets the text run over it.\n */\nconst affixVariants = cva(\n [\n \"pointer-events-none absolute inset-y-0 flex items-center\",\n \"text-muted-foreground\",\n // Sized like Button's icon slots, and skipped when the caller sized the\n // glyph themselves. An svg with neither a width attribute nor a resolved\n // class renders at 0x0 and every position assertion still passes.\n \"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n ],\n {\n variants: {\n side: { start: \"\", end: \"\" },\n size: { sm: \"\", md: \"\", lg: \"\" },\n },\n // The glyph's inset is the field's own inline padding, so it lines up with\n // where the value would have started. That makes it a pair of axes, not\n // one: a small field at the medium inset puts the glyph under the text.\n compoundVariants: [\n { side: \"start\", size: \"sm\", class: \"start-(--density-control-pad-sm)\" },\n { side: \"start\", size: \"md\", class: \"start-(--density-control-pad-md)\" },\n { side: \"start\", size: \"lg\", class: \"start-(--density-control-pad-lg)\" },\n { side: \"end\", size: \"sm\", class: \"end-(--density-control-pad-sm)\" },\n { side: \"end\", size: \"md\", class: \"end-(--density-control-pad-md)\" },\n { side: \"end\", size: \"lg\", class: \"end-(--density-control-pad-lg)\" },\n ],\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * A single-line text field.\n *\n * @example\n * <Input type=\"email\" placeholder=\"you@example.com\" value={v} onChange={e => set(e.target.value)} />\n * <Input aria-label=\"Search\" startIcon={<SearchIcon aria-hidden />} />\n */\nexport interface InputProps extends Omit<AriaInputProps, \"className\" | \"style\" | \"size\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Control scale. Height and inline padding come from the same\n * `--density-control-*` tokens as Button, so a field and a button on one\n * rung are the same height.\n *\n * Type does not scale with it: the field stays at 16px everywhere, because\n * iOS Safari zooms the viewport when a focused input's text is smaller.\n *\n * This REPLACES the native `size` attribute, which React Aria inherits from\n * `InputHTMLAttributes` as a character count. No call site in the census\n * used it; set a width with `className` instead.\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /**\n * Decorative glyph at the leading edge of the field.\n *\n * Mark it `aria-hidden` -- the field's label carries the accessible name,\n * and a second name source only makes it announce twice. It is sized at\n * `--icon-sm` unless it carries a `size-*` class of its own, positioned\n * from `--density-control-pad-md`, and the field's leading padding grows to\n * match so the value cannot run underneath it.\n *\n * The slot is `pointer-events: none`, so a click on the glyph focuses the\n * field. That makes it decorative by construction: an interactive\n * adornment -- a password reveal, a clear button -- is NOT this prop.\n *\n * Two named slots rather than one `icon` plus a position prop, matching\n * Button: a search field can legitimately carry a leading glyph AND a\n * trailing unit, which one slot cannot express.\n */\n startIcon?: ReactNode;\n /** Decorative glyph at the trailing edge. Same rules as {@link InputProps.startIcon}. */\n endIcon?: ReactNode;\n}\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(function Input(\n { className, size = \"md\", startIcon, endIcon, ...props },\n ref,\n) {\n const input = (\n <AriaInput\n // The ref goes on the INPUT, never on the wrapper span below. A ref\n // pointing at the wrapper is still non-null, so it satisfies the obvious\n // assertion while breaking `.focus()`, `.select()` and every\n // react-hook-form site -- but only at the call sites that pass an icon.\n // RefLandsOnTheInputNotTheWrapper is the guard.\n ref={ref}\n {...props}\n data-slot=\"input\"\n className={cn(\n inputVariants({\n size,\n hasStartIcon: startIcon !== undefined,\n hasEndIcon: endIcon !== undefined,\n }),\n className,\n )}\n />\n );\n\n // With neither slot used there is no wrapper AT ALL, so the DOM is\n // byte-identical to what shipped before this prop existed. That is what\n // keeps this additive for the 97 call sites that pass no glyph, and\n // NoIconsLeaveDomUnchanged is the assertion holding it true.\n if (startIcon === undefined && endIcon === undefined) return input;\n\n // An overlay, not a flex row. Moving the border onto this wrapper would\n // take the painted surface off [data-slot=\"input\"] -- the selector the\n // escape hatch is built on -- and would force the focus ring onto\n // :focus-within and the invalid border onto :has(), when React Aria\n // already puts data-focus-visible and data-invalid on the input itself.\n return (\n <span data-slot=\"input-wrapper\" className=\"relative block w-full\">\n {input}\n {startIcon === undefined ? null : (\n <span data-slot=\"input-start-icon\" className={affixVariants({ side: \"start\", size })}>\n {startIcon}\n </span>\n )}\n {endIcon === undefined ? null : (\n <span data-slot=\"input-end-icon\" className={affixVariants({ side: \"end\", size })}>\n {endIcon}\n </span>\n )}\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,gBAAgB,IAC3B;CACE,qBAAqB;CAOrB;CAIA;CACA;AACF,GACA;CACE,UAAU;;;;;;;;;;;;;;EAcR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EAGA,cAAc,EAAE,MAAM,GAAG;EACzB,YAAY,EAAE,MAAM,GAAG;CACzB;CACA,kBAAkB;EAQhB;GACE,MAAM;GACN,cAAc;GACd,OACE;EACJ;EACA;GACE,MAAM;GACN,cAAc;GACd,OACE;EACJ;EACA;GACE,MAAM;GACN,cAAc;GACd,OACE;EACJ;EACA;GACE,MAAM;GACN,YAAY;GACZ,OACE;EACJ;EACA;GACE,MAAM;GACN,YAAY;GACZ,OACE;EACJ;EACA;GACE,MAAM;GACN,YAAY;GACZ,OACE;EACJ;CACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;;;;;;;;;;;;;;;;AAiBA,MAAM,gBAAgB,IACpB;CACE;CACA;CAIA;AACF,GACA;CACE,UAAU;EACR,MAAM;GAAE,OAAO;GAAI,KAAK;EAAG;EAC3B,MAAM;GAAE,IAAI;GAAI,IAAI;GAAI,IAAI;EAAG;CACjC;CAIA,kBAAkB;EAChB;GAAE,MAAM;GAAS,MAAM;GAAM,OAAO;EAAmC;EACvE;GAAE,MAAM;GAAS,MAAM;GAAM,OAAO;EAAmC;EACvE;GAAE,MAAM;GAAS,MAAM;GAAM,OAAO;EAAmC;EACvE;GAAE,MAAM;GAAO,MAAM;GAAM,OAAO;EAAiC;EACnE;GAAE,MAAM;GAAO,MAAM;GAAM,OAAO;EAAiC;EACnE;GAAE,MAAM;GAAO,MAAM;GAAM,OAAO;EAAiC;CACrE;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAiDA,MAAaA,UAAQ,WAAyC,SAASA,QACrE,EAAE,WAAW,OAAO,MAAM,WAAW,SAAS,GAAG,SACjD,KACA;CACA,MAAM,QACJ,oBAACC,OAAD;EAMO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GACT,cAAc;GACZ;GACA,cAAc,cAAc,KAAA;GAC5B,YAAY,YAAY,KAAA;EAC1B,CAAC,GACD,SACF;CACD,CAAA;CAOH,IAAI,cAAc,KAAA,KAAa,YAAY,KAAA,GAAW,OAAO;CAO7D,OACE,qBAAC,QAAD;EAAM,aAAU;EAAgB,WAAU;EAA1C,UAAA;GACG;GACA,cAAc,KAAA,IAAY,OACzB,oBAAC,QAAD;IAAM,aAAU;IAAmB,WAAW,cAAc;KAAE,MAAM;KAAS;IAAK,CAAC;IAChF,UAAA;GACG,CAAA;GAEP,YAAY,KAAA,IAAY,OACvB,oBAAC,QAAD;IAAM,aAAU;IAAiB,WAAW,cAAc;KAAE,MAAM;KAAO;IAAK,CAAC;IAC5E,UAAA;GACG,CAAA;EAEJ;;AAEV,CAAC"}
|
package/dist/lib/cn.d.ts
CHANGED
|
@@ -1,12 +1,62 @@
|
|
|
1
1
|
import { ClassValue } from "clsx";
|
|
2
2
|
//#region src/lib/cn.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Merge
|
|
4
|
+
* Merge OUR classes, then append the consumer's UNTOUCHED.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The consumer's `className` deliberately does not go through tailwind-merge.
|
|
7
|
+
*
|
|
8
|
+
* WHY. tailwind-merge resolves conflicts by class GROUP, and several of its
|
|
9
|
+
* groups are matched by validators that accept any token -- `text-color` takes
|
|
10
|
+
* anything shaped `text-<word>-<number>`, which is also the shape of a
|
|
11
|
+
* consuming app's own utilities. It cannot tell `text-red-500` from a custom
|
|
12
|
+
* `text-semibold-14`, so it treats them as conflicting and deletes one. There
|
|
13
|
+
* is no error, no warning and no type error; the element simply renders without
|
|
14
|
+
* the class the author wrote.
|
|
15
|
+
*
|
|
16
|
+
* Measured in the consuming app before this changed: 38 call sites passed a
|
|
17
|
+
* custom type-scale class and 14 lost it. Two of those flipped at runtime
|
|
18
|
+
* depending on a data value, which is why it went unnoticed for so long. A
|
|
19
|
+
* second, unrelated utility (`bg-sidebar-bg`) was being eaten the same way, so
|
|
20
|
+
* the defect is structural rather than specific to typography.
|
|
21
|
+
*
|
|
22
|
+
* The loss happens INSIDE the consumer's own string, not between theirs and
|
|
23
|
+
* ours -- a lone consumer class survives, because it comes last and
|
|
24
|
+
* tailwind-merge keeps the last of a group. It is a pair like
|
|
25
|
+
* `"text-semibold-14 text-white"` that loses its first half. So no amount of
|
|
26
|
+
* ordering fixes it: their string has to stay out of the merge entirely.
|
|
27
|
+
*
|
|
28
|
+
* WHY THAT IS SAFE. "The consumer always wins" is delivered twice over. This
|
|
29
|
+
* package ships entirely inside `@layer alfred`, and unlayered CSS beats every
|
|
30
|
+
* layered rule regardless of specificity (ARCHITECTURE.md section 5;
|
|
31
|
+
* PHASE-0.md). `apps/smoke` asserts exactly that on every build. tailwind-merge
|
|
32
|
+
* was belt-and-braces for something the cascade already handles -- and the
|
|
33
|
+
* braces were cutting.
|
|
34
|
+
*
|
|
35
|
+
* WHAT THIS IS NOT. It is not "stop merging". A consumer class that
|
|
36
|
+
* tailwind-merge genuinely understands still wins outright -- `max-w-96` over
|
|
37
|
+
* our `max-w-128` collapses to one class, exactly as before. Only the tokens
|
|
38
|
+
* the merge DISCARDED from their string come back, which is precisely the set
|
|
39
|
+
* it had no business discarding.
|
|
40
|
+
*
|
|
41
|
+
* The one behaviour change to know about: if a consumer passes two conflicting
|
|
42
|
+
* classes of their own (`className="p-2 p-4"`), both now reach the element and
|
|
43
|
+
* CSS picks by stylesheet order rather than by token order. Their string, their
|
|
44
|
+
* conflict -- and the alternative is deleting custom utilities, which is worse.
|
|
45
|
+
*
|
|
46
|
+
* @param ours classes this library authored -- merged against each other
|
|
47
|
+
* @param theirs the consumer's `className` -- merged, then anything the merge
|
|
48
|
+
* discarded from it is restored
|
|
49
|
+
*/
|
|
50
|
+
export declare function cn(ours: ClassValue, theirs?: ClassValue): string;
|
|
51
|
+
/**
|
|
52
|
+
* Merge classes that are ALL ours.
|
|
53
|
+
*
|
|
54
|
+
* The same tailwind-merge behaviour `cn` used to have, for the call sites that
|
|
55
|
+
* combine several of this library's own class strings and never touch a
|
|
56
|
+
* consumer's. Keeping them on a separate function is what lets `cn` treat its
|
|
57
|
+
* second argument as foreign without guessing from argument position -- several
|
|
58
|
+
* internal call sites legitimately end with a library-owned conditional.
|
|
9
59
|
*/
|
|
10
|
-
export declare function
|
|
60
|
+
export declare function cnAll(...inputs: ClassValue[]): string;
|
|
11
61
|
//#endregion
|
|
12
62
|
//# sourceMappingURL=cn.d.ts.map
|
package/dist/lib/cn.js
CHANGED
|
@@ -2,16 +2,74 @@ import { clsx } from "clsx";
|
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
3
|
//#region src/lib/cn.ts
|
|
4
4
|
/**
|
|
5
|
-
* Merge
|
|
5
|
+
* Merge OUR classes, then append the consumer's UNTOUCHED.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* The consumer's `className` deliberately does not go through tailwind-merge.
|
|
8
|
+
*
|
|
9
|
+
* WHY. tailwind-merge resolves conflicts by class GROUP, and several of its
|
|
10
|
+
* groups are matched by validators that accept any token -- `text-color` takes
|
|
11
|
+
* anything shaped `text-<word>-<number>`, which is also the shape of a
|
|
12
|
+
* consuming app's own utilities. It cannot tell `text-red-500` from a custom
|
|
13
|
+
* `text-semibold-14`, so it treats them as conflicting and deletes one. There
|
|
14
|
+
* is no error, no warning and no type error; the element simply renders without
|
|
15
|
+
* the class the author wrote.
|
|
16
|
+
*
|
|
17
|
+
* Measured in the consuming app before this changed: 38 call sites passed a
|
|
18
|
+
* custom type-scale class and 14 lost it. Two of those flipped at runtime
|
|
19
|
+
* depending on a data value, which is why it went unnoticed for so long. A
|
|
20
|
+
* second, unrelated utility (`bg-sidebar-bg`) was being eaten the same way, so
|
|
21
|
+
* the defect is structural rather than specific to typography.
|
|
22
|
+
*
|
|
23
|
+
* The loss happens INSIDE the consumer's own string, not between theirs and
|
|
24
|
+
* ours -- a lone consumer class survives, because it comes last and
|
|
25
|
+
* tailwind-merge keeps the last of a group. It is a pair like
|
|
26
|
+
* `"text-semibold-14 text-white"` that loses its first half. So no amount of
|
|
27
|
+
* ordering fixes it: their string has to stay out of the merge entirely.
|
|
28
|
+
*
|
|
29
|
+
* WHY THAT IS SAFE. "The consumer always wins" is delivered twice over. This
|
|
30
|
+
* package ships entirely inside `@layer alfred`, and unlayered CSS beats every
|
|
31
|
+
* layered rule regardless of specificity (ARCHITECTURE.md section 5;
|
|
32
|
+
* PHASE-0.md). `apps/smoke` asserts exactly that on every build. tailwind-merge
|
|
33
|
+
* was belt-and-braces for something the cascade already handles -- and the
|
|
34
|
+
* braces were cutting.
|
|
35
|
+
*
|
|
36
|
+
* WHAT THIS IS NOT. It is not "stop merging". A consumer class that
|
|
37
|
+
* tailwind-merge genuinely understands still wins outright -- `max-w-96` over
|
|
38
|
+
* our `max-w-128` collapses to one class, exactly as before. Only the tokens
|
|
39
|
+
* the merge DISCARDED from their string come back, which is precisely the set
|
|
40
|
+
* it had no business discarding.
|
|
41
|
+
*
|
|
42
|
+
* The one behaviour change to know about: if a consumer passes two conflicting
|
|
43
|
+
* classes of their own (`className="p-2 p-4"`), both now reach the element and
|
|
44
|
+
* CSS picks by stylesheet order rather than by token order. Their string, their
|
|
45
|
+
* conflict -- and the alternative is deleting custom utilities, which is worse.
|
|
46
|
+
*
|
|
47
|
+
* @param ours classes this library authored -- merged against each other
|
|
48
|
+
* @param theirs the consumer's `className` -- merged, then anything the merge
|
|
49
|
+
* discarded from it is restored
|
|
50
|
+
*/
|
|
51
|
+
function cn(ours, theirs) {
|
|
52
|
+
const mine = clsx(ours);
|
|
53
|
+
const yours = clsx(theirs);
|
|
54
|
+
if (!yours) return twMerge(mine);
|
|
55
|
+
const merged = twMerge(`${mine} ${yours}`);
|
|
56
|
+
const kept = new Set(merged.split(" "));
|
|
57
|
+
const rescued = yours.split(" ").filter((token) => token && !kept.has(token));
|
|
58
|
+
return rescued.length === 0 ? merged : `${merged} ${rescued.join(" ")}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Merge classes that are ALL ours.
|
|
62
|
+
*
|
|
63
|
+
* The same tailwind-merge behaviour `cn` used to have, for the call sites that
|
|
64
|
+
* combine several of this library's own class strings and never touch a
|
|
65
|
+
* consumer's. Keeping them on a separate function is what lets `cn` treat its
|
|
66
|
+
* second argument as foreign without guessing from argument position -- several
|
|
67
|
+
* internal call sites legitimately end with a library-owned conditional.
|
|
10
68
|
*/
|
|
11
|
-
function
|
|
69
|
+
function cnAll(...inputs) {
|
|
12
70
|
return twMerge(clsx(inputs));
|
|
13
71
|
}
|
|
14
72
|
//#endregion
|
|
15
|
-
export { cn };
|
|
73
|
+
export { cn, cnAll };
|
|
16
74
|
|
|
17
75
|
//# sourceMappingURL=cn.js.map
|
package/dist/lib/cn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cn.js","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge
|
|
1
|
+
{"version":3,"file":"cn.js","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge OUR classes, then append the consumer's UNTOUCHED.\n *\n * The consumer's `className` deliberately does not go through tailwind-merge.\n *\n * WHY. tailwind-merge resolves conflicts by class GROUP, and several of its\n * groups are matched by validators that accept any token -- `text-color` takes\n * anything shaped `text-<word>-<number>`, which is also the shape of a\n * consuming app's own utilities. It cannot tell `text-red-500` from a custom\n * `text-semibold-14`, so it treats them as conflicting and deletes one. There\n * is no error, no warning and no type error; the element simply renders without\n * the class the author wrote.\n *\n * Measured in the consuming app before this changed: 38 call sites passed a\n * custom type-scale class and 14 lost it. Two of those flipped at runtime\n * depending on a data value, which is why it went unnoticed for so long. A\n * second, unrelated utility (`bg-sidebar-bg`) was being eaten the same way, so\n * the defect is structural rather than specific to typography.\n *\n * The loss happens INSIDE the consumer's own string, not between theirs and\n * ours -- a lone consumer class survives, because it comes last and\n * tailwind-merge keeps the last of a group. It is a pair like\n * `\"text-semibold-14 text-white\"` that loses its first half. So no amount of\n * ordering fixes it: their string has to stay out of the merge entirely.\n *\n * WHY THAT IS SAFE. \"The consumer always wins\" is delivered twice over. This\n * package ships entirely inside `@layer alfred`, and unlayered CSS beats every\n * layered rule regardless of specificity (ARCHITECTURE.md section 5;\n * PHASE-0.md). `apps/smoke` asserts exactly that on every build. tailwind-merge\n * was belt-and-braces for something the cascade already handles -- and the\n * braces were cutting.\n *\n * WHAT THIS IS NOT. It is not \"stop merging\". A consumer class that\n * tailwind-merge genuinely understands still wins outright -- `max-w-96` over\n * our `max-w-128` collapses to one class, exactly as before. Only the tokens\n * the merge DISCARDED from their string come back, which is precisely the set\n * it had no business discarding.\n *\n * The one behaviour change to know about: if a consumer passes two conflicting\n * classes of their own (`className=\"p-2 p-4\"`), both now reach the element and\n * CSS picks by stylesheet order rather than by token order. Their string, their\n * conflict -- and the alternative is deleting custom utilities, which is worse.\n *\n * @param ours classes this library authored -- merged against each other\n * @param theirs the consumer's `className` -- merged, then anything the merge\n * discarded from it is restored\n */\nexport function cn(ours: ClassValue, theirs?: ClassValue): string {\n const mine = clsx(ours);\n const yours = clsx(theirs);\n if (!yours) return twMerge(mine);\n\n // Merge normally first, so a consumer class that tailwind-merge DOES\n // understand still beats ours -- `max-w-96` over our `max-w-128` resolves to\n // one class, as it always did.\n const merged = twMerge(`${mine} ${yours}`);\n\n // Then put back anything of theirs the merge ate. Only their tokens are\n // restored, never ours, so this cannot resurrect a class the consumer\n // deliberately overrode.\n const kept = new Set(merged.split(\" \"));\n const rescued = yours.split(\" \").filter((token) => token && !kept.has(token));\n\n return rescued.length === 0 ? merged : `${merged} ${rescued.join(\" \")}`;\n}\n\n/**\n * Merge classes that are ALL ours.\n *\n * The same tailwind-merge behaviour `cn` used to have, for the call sites that\n * combine several of this library's own class strings and never touch a\n * consumer's. Keeping them on a separate function is what lets `cn` treat its\n * second argument as foreign without guessing from argument position -- several\n * internal call sites legitimately end with a library-owned conditional.\n */\nexport function cnAll(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAgB,GAAG,MAAkB,QAA6B;CAChE,MAAM,OAAO,KAAK,IAAI;CACtB,MAAM,QAAQ,KAAK,MAAM;CACzB,IAAI,CAAC,OAAO,OAAO,QAAQ,IAAI;CAK/B,MAAM,SAAS,QAAQ,GAAG,KAAK,GAAG,OAAO;CAKzC,MAAM,OAAO,IAAI,IAAI,OAAO,MAAM,GAAG,CAAC;CACtC,MAAM,UAAU,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ,UAAU,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC;CAE5E,OAAO,QAAQ,WAAW,IAAI,SAAS,GAAG,OAAO,GAAG,QAAQ,KAAK,GAAG;AACtE;;;;;;;;;;AAWA,SAAgB,MAAM,GAAG,QAA8B;CACrD,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { cn, cnAll } from "../lib/cn.js";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { Label, ProgressBar } from "react-aria-components";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -53,10 +53,10 @@ function Progress({ label, labelHidden, showValue, tone, trackClassName, fillCla
|
|
|
53
53
|
className: cn(progressVariants(), className),
|
|
54
54
|
children: ({ percentage, isIndeterminate, valueText }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
55
55
|
"data-slot": "progress-header",
|
|
56
|
-
className:
|
|
56
|
+
className: cnAll(showValue && "flex items-baseline justify-between gap-2"),
|
|
57
57
|
children: [/* @__PURE__ */ jsx(Label, {
|
|
58
58
|
"data-slot": "progress-label",
|
|
59
|
-
className:
|
|
59
|
+
className: cnAll("text-sm leading-(--leading-normal) text-foreground", labelHidden && "sr-only"),
|
|
60
60
|
children: label
|
|
61
61
|
}), showValue ? /* @__PURE__ */ jsx("span", {
|
|
62
62
|
"data-slot": "progress-value",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.js","names":["AriaProgressBar","AriaLabel"],"sources":["../../src/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n ProgressBar as AriaProgressBar,\n type ProgressBarProps as AriaProgressBarProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Progress, ported from the legacy shadcn/Radix component.\n *\n * Census: seven files import it and only TWO render anything --\n * `ResponseOption.tsx` and `LiveQA.tsx`. Both pass `value` and\n * `className` and nothing else.\n *\n * `label` IS REQUIRED, and that is the point. Neither live call site\n * passes `aria-label`, `aria-labelledby` or an `id` a label targets, and\n * `role=\"progressbar\"` does not take its name from content -- so both\n * rendered progress bars are announced as an unnamed progressbar. React\n * Aria supplies the plumbing for three naming routes but no name of its\n * own: `<ProgressBar value={60}/>` is just as nameless there. Making the\n * prop required is what turns it into something you cannot forget.\n *\n * The migration plan said this component \"adds the missing\n * aria-valuenow\". That is wrong -- Radix already supplies `aria-valuenow`,\n * `aria-valuemin`, `aria-valuemax` and `aria-valuetext`. The missing thing\n * was always the NAME.\n *\n * THE COLOURS ARE NOT THE LEGACY'S. Its track is `bg-primary/20`, an alpha\n * fill that composites against unknown page content and therefore has no\n * fixed contrast pair at all -- over white it is roughly 1.05:1, an\n * invisible boundary. Its indicator is `bg-primary`, 2.44:1 on the page.\n * Here the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`, which puts the fill against the track at 4.61:1 and\n * the track against the page at 5.25:1.\n *\n * Both are in the build-failing contrast contract.\n */\nexport const progressVariants = cva([\"flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\nexport const progressTrackVariants = cva([\n \"relative h-2 w-full overflow-hidden rounded-pill box-border\",\n // muted, NOT `border` as the Switch uses. A switch has no fill; a\n // progress bar does, and primary-strong on a `border` track is 1.01:1 --\n // the two halves are the same grey and the value disappears.\n \"bg-muted border-(length:--border-width) border-border\",\n]);\n\nexport const progressFillVariants = cva(\n [\n \"h-full rounded-pill\",\n \"motion-safe:transition-[width] motion-safe:duration-(--duration-base) motion-safe:ease-standard\",\n ],\n {\n variants: {\n /*\n * What the value MEANS, which for a progress bar is usually a score\n * rather than a completion. The product's bar carried exactly this axis\n * (`default`/`success`/`warning`/`error`/`info`) and reached for it to\n * colour performance metrics.\n *\n * `-strong` throughout: the fill sits on the `muted` track and has to\n * clear the 3:1 non-text floor of WCAG 1.4.11 against it, which is the\n * same reason the track is muted rather than `border` -- primary-strong\n * on a `border` track measures 1.01:1 and the value disappears.\n */\n tone: {\n neutral: \"bg-primary-strong\",\n success: \"bg-success-strong\",\n warning: \"bg-warning-strong\",\n danger: \"bg-destructive-strong\",\n info: \"bg-info-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * A determinate or indeterminate progress bar.\n *\n * @example\n * <Progress label=\"Upload\" value={62} />\n * <Progress label=\"Loading results\" />\n */\nexport interface ProgressProps\n extends Omit<AriaProgressBarProps, \"className\" | \"style\" | \"children\">,\n VariantProps<typeof progressFillVariants> {\n /**\n * What is progressing, and the bar's accessible name.\n *\n * **Required.** Both legacy call sites render an unnamed progressbar.\n * Pass `labelHidden` when the surrounding UI already says it.\n */\n label: ReactNode;\n /**\n * Render the label for screen readers only.\n *\n * Both legacy call sites sit beside visible text that was never\n * associated with the bar -- this is how that text becomes the name\n * without printing it twice.\n */\n labelHidden?: boolean;\n /**\n * Print the value beside the label.\n *\n * The text itself comes from React Aria, which already formats it from\n * `value`/`minValue`/`maxValue` and honours `formatOptions` and\n * `valueLabel` -- so a percentage, a currency or \"3 of 7\" are all reachable\n * without a second prop here. It was never RENDERED before: the bar\n * announced its value to assistive tech and showed a sighted reader nothing.\n */\n showValue?: boolean;\n /** Extra classes for the track. */\n trackClassName?: string;\n /** Extra classes for the fill -- a per-call-site colour the tone axis cannot express. */\n fillClassName?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Progress({\n label,\n labelHidden,\n showValue,\n tone,\n trackClassName,\n fillClassName,\n className,\n ...props\n}: ProgressProps) {\n return (\n <AriaProgressBar\n {...props}\n data-slot=\"progress\"\n className={cn(progressVariants(), className)}\n >\n {({ percentage, isIndeterminate, valueText }) => (\n <>\n {/*\n The row exists only when the value is printed. Wrapping the label\n in a flex row unconditionally would change the label's box for\n every existing consumer to no purpose.\n */}\n <div\n data-slot=\"progress-header\"\n className={
|
|
1
|
+
{"version":3,"file":"progress.js","names":["AriaProgressBar","AriaLabel"],"sources":["../../src/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n ProgressBar as AriaProgressBar,\n type ProgressBarProps as AriaProgressBarProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\n\n/**\n * Progress, ported from the legacy shadcn/Radix component.\n *\n * Census: seven files import it and only TWO render anything --\n * `ResponseOption.tsx` and `LiveQA.tsx`. Both pass `value` and\n * `className` and nothing else.\n *\n * `label` IS REQUIRED, and that is the point. Neither live call site\n * passes `aria-label`, `aria-labelledby` or an `id` a label targets, and\n * `role=\"progressbar\"` does not take its name from content -- so both\n * rendered progress bars are announced as an unnamed progressbar. React\n * Aria supplies the plumbing for three naming routes but no name of its\n * own: `<ProgressBar value={60}/>` is just as nameless there. Making the\n * prop required is what turns it into something you cannot forget.\n *\n * The migration plan said this component \"adds the missing\n * aria-valuenow\". That is wrong -- Radix already supplies `aria-valuenow`,\n * `aria-valuemin`, `aria-valuemax` and `aria-valuetext`. The missing thing\n * was always the NAME.\n *\n * THE COLOURS ARE NOT THE LEGACY'S. Its track is `bg-primary/20`, an alpha\n * fill that composites against unknown page content and therefore has no\n * fixed contrast pair at all -- over white it is roughly 1.05:1, an\n * invisible boundary. Its indicator is `bg-primary`, 2.44:1 on the page.\n * Here the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`, which puts the fill against the track at 4.61:1 and\n * the track against the page at 5.25:1.\n *\n * Both are in the build-failing contrast contract.\n */\nexport const progressVariants = cva([\"flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\nexport const progressTrackVariants = cva([\n \"relative h-2 w-full overflow-hidden rounded-pill box-border\",\n // muted, NOT `border` as the Switch uses. A switch has no fill; a\n // progress bar does, and primary-strong on a `border` track is 1.01:1 --\n // the two halves are the same grey and the value disappears.\n \"bg-muted border-(length:--border-width) border-border\",\n]);\n\nexport const progressFillVariants = cva(\n [\n \"h-full rounded-pill\",\n \"motion-safe:transition-[width] motion-safe:duration-(--duration-base) motion-safe:ease-standard\",\n ],\n {\n variants: {\n /*\n * What the value MEANS, which for a progress bar is usually a score\n * rather than a completion. The product's bar carried exactly this axis\n * (`default`/`success`/`warning`/`error`/`info`) and reached for it to\n * colour performance metrics.\n *\n * `-strong` throughout: the fill sits on the `muted` track and has to\n * clear the 3:1 non-text floor of WCAG 1.4.11 against it, which is the\n * same reason the track is muted rather than `border` -- primary-strong\n * on a `border` track measures 1.01:1 and the value disappears.\n */\n tone: {\n neutral: \"bg-primary-strong\",\n success: \"bg-success-strong\",\n warning: \"bg-warning-strong\",\n danger: \"bg-destructive-strong\",\n info: \"bg-info-strong\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * A determinate or indeterminate progress bar.\n *\n * @example\n * <Progress label=\"Upload\" value={62} />\n * <Progress label=\"Loading results\" />\n */\nexport interface ProgressProps\n extends Omit<AriaProgressBarProps, \"className\" | \"style\" | \"children\">,\n VariantProps<typeof progressFillVariants> {\n /**\n * What is progressing, and the bar's accessible name.\n *\n * **Required.** Both legacy call sites render an unnamed progressbar.\n * Pass `labelHidden` when the surrounding UI already says it.\n */\n label: ReactNode;\n /**\n * Render the label for screen readers only.\n *\n * Both legacy call sites sit beside visible text that was never\n * associated with the bar -- this is how that text becomes the name\n * without printing it twice.\n */\n labelHidden?: boolean;\n /**\n * Print the value beside the label.\n *\n * The text itself comes from React Aria, which already formats it from\n * `value`/`minValue`/`maxValue` and honours `formatOptions` and\n * `valueLabel` -- so a percentage, a currency or \"3 of 7\" are all reachable\n * without a second prop here. It was never RENDERED before: the bar\n * announced its value to assistive tech and showed a sighted reader nothing.\n */\n showValue?: boolean;\n /** Extra classes for the track. */\n trackClassName?: string;\n /** Extra classes for the fill -- a per-call-site colour the tone axis cannot express. */\n fillClassName?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Progress({\n label,\n labelHidden,\n showValue,\n tone,\n trackClassName,\n fillClassName,\n className,\n ...props\n}: ProgressProps) {\n return (\n <AriaProgressBar\n {...props}\n data-slot=\"progress\"\n className={cn(progressVariants(), className)}\n >\n {({ percentage, isIndeterminate, valueText }) => (\n <>\n {/*\n The row exists only when the value is printed. Wrapping the label\n in a flex row unconditionally would change the label's box for\n every existing consumer to no purpose.\n */}\n <div\n data-slot=\"progress-header\"\n className={cnAll(showValue && \"flex items-baseline justify-between gap-2\")}\n >\n <AriaLabel\n data-slot=\"progress-label\"\n className={cnAll(\n \"text-sm leading-(--leading-normal) text-foreground\",\n labelHidden && \"sr-only\",\n )}\n >\n {label}\n </AriaLabel>\n {showValue ? (\n <span\n data-slot=\"progress-value\"\n // aria-hidden: React Aria already announces the value on the\n // progressbar itself. Without this a screen reader reads it\n // twice, once as the widget's value and once as loose text.\n aria-hidden=\"true\"\n className=\"text-sm leading-(--leading-normal) text-muted-foreground tabular-nums\"\n >\n {valueText}\n </span>\n ) : null}\n </div>\n <div\n data-slot=\"progress-track\"\n className={cn(progressTrackVariants(), trackClassName)}\n >\n {/*\n width, not the legacy translateX(-(100 - value)%). That\n overflowed for value > 100 -- a positive translate slid the\n indicator off to the right, while the announced value stayed\n whatever was passed. React Aria clamps the value, so the two\n can no longer disagree.\n\n An indeterminate bar renders no fill rather than the\n legacy's silent 0%: `value={undefined}` became `(value || 0)`\n there, so a sighted user saw \"0% done\" while a screen reader\n said \"indeterminate\".\n */}\n <div\n data-slot=\"progress-fill\"\n data-indeterminate={isIndeterminate || undefined}\n className={cn(progressFillVariants({ tone }), fillClassName)}\n style={{ width: isIndeterminate ? \"0%\" : `${percentage ?? 0}%` }}\n />\n </div>\n </>\n )}\n </AriaProgressBar>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,mBAAmB,IAAI,CAAC,mDAAmD,CAAC;AAEzF,MAAa,wBAAwB,IAAI,CACvC,+DAIA,uDACF,CAAC;AAED,MAAa,uBAAuB,IAClC,CACE,uBACA,iGACF,GACA;CACE,UAAU,EAYR,MAAM;EACJ,SAAS;EACT,SAAS;EACT,SAAS;EACT,QAAQ;EACR,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CACF;AA6CA,SAAgB,SAAS,EACvB,OACA,aACA,WACA,MACA,gBACA,eACA,WACA,GAAG,SACa;CAChB,OACE,oBAACA,aAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;EAEzC,WAAA,EAAE,YAAY,iBAAiB,gBAC/B,qBAAA,UAAA,EAAA,UAAA,CAME,qBAAC,OAAD;GACE,aAAU;GACV,WAAW,MAAM,aAAa,2CAA2C;GAF3E,UAAA,CAIE,oBAACC,OAAD;IACE,aAAU;IACV,WAAW,MACT,sDACA,eAAe,SACjB;IAEC,UAAA;GACQ,CAAA,GACV,YACC,oBAAC,QAAD;IACE,aAAU;IAIV,eAAY;IACZ,WAAU;IAET,UAAA;GACG,CAAA,IACJ,IACD;EACL,CAAA,GAAA,oBAAC,OAAD;GACE,aAAU;GACV,WAAW,GAAG,sBAAsB,GAAG,cAAc;GAcrD,UAAA,oBAAC,OAAD;IACE,aAAU;IACV,sBAAoB,mBAAmB,KAAA;IACvC,WAAW,GAAG,qBAAqB,EAAE,KAAK,CAAC,GAAG,aAAa;IAC3D,OAAO,EAAE,OAAO,kBAAkB,OAAO,GAAG,cAAc,EAAE,GAAG;GAChE,CAAA;EACE,CAAA,CACL,EAAA,CAAA;CAEW,CAAA;AAErB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { cn, cnAll } from "../lib/cn.js";
|
|
3
3
|
import { selectionLabelVariants, selectionRingVariants } from "../lib/selection-control.js";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { Radio, RadioGroup } from "react-aria-components";
|
|
@@ -44,7 +44,7 @@ function Radio$1({ className, children, ...props }) {
|
|
|
44
44
|
return /* @__PURE__ */ jsx(Radio, {
|
|
45
45
|
...props,
|
|
46
46
|
"data-slot": "radio",
|
|
47
|
-
className: cn(selectionLabelVariants(), "justify-start", className),
|
|
47
|
+
className: cn(cnAll(selectionLabelVariants(), "justify-start"), className),
|
|
48
48
|
children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
49
49
|
"aria-hidden": "true",
|
|
50
50
|
"data-slot": "radio-dot",
|