panelui-web 0.4.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/dist/components/button/index.d.ts +111 -0
- package/dist/components/button/index.js +45 -0
- package/dist/components/input/index.d.ts +55 -0
- package/dist/components/input/index.js +89 -0
- package/dist/components/menu/index.d.ts +202 -0
- package/dist/components/menu/index.js +188 -0
- package/dist/components/planet/index.js +4 -1
- package/dist/components/prompt/index.d.ts +414 -0
- package/dist/components/prompt/index.js +779 -0
- package/dist/components/select/index.d.ts +134 -0
- package/dist/components/select/index.js +164 -0
- package/dist/components/sheet/index.js +46 -5
- package/dist/components/textarea/index.d.ts +43 -0
- package/dist/components/textarea/index.js +101 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select — a button that opens a list of options, one of which is chosen.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* <Select items={plans} defaultValue="team">
|
|
6
|
+
* <Select.Trigger>
|
|
7
|
+
* <Select.Value placeholder="Pick a plan" />
|
|
8
|
+
* </Select.Trigger>
|
|
9
|
+
* <Select.Content>
|
|
10
|
+
* {plans.map((plan) => (
|
|
11
|
+
* <Select.Item key={plan.value} value={plan.value} description={plan.price}>
|
|
12
|
+
* {plan.label}
|
|
13
|
+
* </Select.Item>
|
|
14
|
+
* ))}
|
|
15
|
+
* </Select.Content>
|
|
16
|
+
* </Select>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* ## The list opens over the trigger
|
|
20
|
+
*
|
|
21
|
+
* Opened with a pointer, the list is placed so the chosen option sits exactly
|
|
22
|
+
* over the trigger, and the eye does not have to go looking for it. Where
|
|
23
|
+
* there is no room for that, or `alignItemWithTrigger` is false, it opens
|
|
24
|
+
* below. Opened from the keyboard, the arrow keys move through the options,
|
|
25
|
+
* typing jumps to one by its first letters, and Enter chooses.
|
|
26
|
+
*
|
|
27
|
+
* ## Labels
|
|
28
|
+
*
|
|
29
|
+
* Pass `items` to the root, as `{ value, label }` pairs, and `Select.Value`
|
|
30
|
+
* shows the chosen option's label rather than its value. Without `items`,
|
|
31
|
+
* give `Select.Value` a function that turns a value into what to show.
|
|
32
|
+
*
|
|
33
|
+
* ## Three triggers
|
|
34
|
+
*
|
|
35
|
+
* `default` is a field, for a form. `ghost` has no box until it is hovered,
|
|
36
|
+
* for a choice set into a toolbar or a line of text. `pill` is a rounded chip
|
|
37
|
+
* with an edge, for a choice that sits among other chips, such as the model
|
|
38
|
+
* in a prompt.
|
|
39
|
+
*/
|
|
40
|
+
import { Select as BaseSelect } from '@base-ui/react/select';
|
|
41
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
42
|
+
export type SelectProps<Value = string, Multiple extends boolean | undefined = false> = BaseSelect.Root.Props<Value, Multiple>;
|
|
43
|
+
/** Holds the chosen value. Takes `value`/`onValueChange`, `defaultValue`, `items`, `name`, `disabled` and `multiple`. */
|
|
44
|
+
declare function SelectRoot<Value = string, Multiple extends boolean | undefined = false>(props: SelectProps<Value, Multiple>): import("react").JSX.Element;
|
|
45
|
+
declare namespace SelectRoot {
|
|
46
|
+
var displayName: string;
|
|
47
|
+
}
|
|
48
|
+
export interface SelectTriggerProps extends BaseSelect.Trigger.Props {
|
|
49
|
+
/** `default` is a field, `ghost` has no box until hovered, `pill` is a rounded chip. Default `default`. */
|
|
50
|
+
variant?: 'default' | 'ghost' | 'pill';
|
|
51
|
+
/** Its height and text size. Default `md`. */
|
|
52
|
+
size?: 'sm' | 'md' | 'lg';
|
|
53
|
+
/** Replaces the chevron at the end. Pass `null` for none. */
|
|
54
|
+
icon?: ReactNode;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
/** The button that opens the list. Put a `Select.Value` in it. */
|
|
58
|
+
declare function SelectTrigger({ variant, size, icon, className, children, ...props }: SelectTriggerProps): import("react").JSX.Element;
|
|
59
|
+
declare namespace SelectTrigger {
|
|
60
|
+
var displayName: string;
|
|
61
|
+
}
|
|
62
|
+
export interface SelectValueProps extends BaseSelect.Value.Props {
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
/** The chosen option's label, or `placeholder` while there is none. */
|
|
66
|
+
declare function SelectValue({ className, ...props }: SelectValueProps): import("react").JSX.Element;
|
|
67
|
+
declare namespace SelectValue {
|
|
68
|
+
var displayName: string;
|
|
69
|
+
}
|
|
70
|
+
export interface SelectContentProps extends BaseSelect.Popup.Props {
|
|
71
|
+
/** Which side of the trigger it opens on, when it is not placed over it. Default `bottom`. */
|
|
72
|
+
side?: BaseSelect.Positioner.Props['side'];
|
|
73
|
+
/** How it lines up with the trigger along that side. Default `start`. */
|
|
74
|
+
align?: BaseSelect.Positioner.Props['align'];
|
|
75
|
+
/** The gap between the trigger and the list, in pixels. Default `6`. */
|
|
76
|
+
sideOffset?: number;
|
|
77
|
+
/** Whether a pointer opens it with the chosen option over the trigger. Default `true`. */
|
|
78
|
+
alignItemWithTrigger?: boolean;
|
|
79
|
+
/** Where in the document the list is put. Default `document.body`. */
|
|
80
|
+
container?: BaseSelect.Portal.Props['container'];
|
|
81
|
+
className?: string;
|
|
82
|
+
}
|
|
83
|
+
/** The list of options, opened in a layer over the page. */
|
|
84
|
+
declare function SelectContent({ side, align, sideOffset, alignItemWithTrigger, container, className, children, ...props }: SelectContentProps): import("react").JSX.Element;
|
|
85
|
+
declare namespace SelectContent {
|
|
86
|
+
var displayName: string;
|
|
87
|
+
}
|
|
88
|
+
export interface SelectItemProps extends Omit<BaseSelect.Item.Props, 'children'> {
|
|
89
|
+
/** The option's label: what the trigger shows once it is chosen. */
|
|
90
|
+
children?: ReactNode;
|
|
91
|
+
/** A second, quieter line under the label. */
|
|
92
|
+
description?: ReactNode;
|
|
93
|
+
/** An icon before the label. */
|
|
94
|
+
icon?: ReactNode;
|
|
95
|
+
className?: string;
|
|
96
|
+
}
|
|
97
|
+
/** One option. A check marks the chosen one. */
|
|
98
|
+
declare function SelectItem({ description, icon, className, children, ...props }: SelectItemProps): import("react").JSX.Element;
|
|
99
|
+
declare namespace SelectItem {
|
|
100
|
+
var displayName: string;
|
|
101
|
+
}
|
|
102
|
+
export interface SelectGroupProps extends BaseSelect.Group.Props {
|
|
103
|
+
className?: string;
|
|
104
|
+
}
|
|
105
|
+
/** Options that belong together, under a `Select.GroupLabel`. */
|
|
106
|
+
declare function SelectGroup({ className, ...props }: SelectGroupProps): import("react").JSX.Element;
|
|
107
|
+
declare namespace SelectGroup {
|
|
108
|
+
var displayName: string;
|
|
109
|
+
}
|
|
110
|
+
export interface SelectGroupLabelProps extends BaseSelect.GroupLabel.Props {
|
|
111
|
+
className?: string;
|
|
112
|
+
}
|
|
113
|
+
/** The heading of a group. Screen readers announce the group by it. */
|
|
114
|
+
declare function SelectGroupLabel({ className, ...props }: SelectGroupLabelProps): import("react").JSX.Element;
|
|
115
|
+
declare namespace SelectGroupLabel {
|
|
116
|
+
var displayName: string;
|
|
117
|
+
}
|
|
118
|
+
export interface SelectSeparatorProps extends ComponentProps<'div'> {
|
|
119
|
+
}
|
|
120
|
+
/** A rule between groups of options. */
|
|
121
|
+
declare function SelectSeparator({ className, ...props }: SelectSeparatorProps): import("react").JSX.Element;
|
|
122
|
+
declare namespace SelectSeparator {
|
|
123
|
+
var displayName: string;
|
|
124
|
+
}
|
|
125
|
+
export declare const Select: typeof SelectRoot & {
|
|
126
|
+
Trigger: typeof SelectTrigger;
|
|
127
|
+
Value: typeof SelectValue;
|
|
128
|
+
Content: typeof SelectContent;
|
|
129
|
+
Item: typeof SelectItem;
|
|
130
|
+
Group: typeof SelectGroup;
|
|
131
|
+
GroupLabel: typeof SelectGroupLabel;
|
|
132
|
+
Separator: typeof SelectSeparator;
|
|
133
|
+
};
|
|
134
|
+
export { SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectGroupLabel, SelectSeparator };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Select — a button that opens a list of options, one of which is chosen.
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* <Select items={plans} defaultValue="team">
|
|
7
|
+
* <Select.Trigger>
|
|
8
|
+
* <Select.Value placeholder="Pick a plan" />
|
|
9
|
+
* </Select.Trigger>
|
|
10
|
+
* <Select.Content>
|
|
11
|
+
* {plans.map((plan) => (
|
|
12
|
+
* <Select.Item key={plan.value} value={plan.value} description={plan.price}>
|
|
13
|
+
* {plan.label}
|
|
14
|
+
* </Select.Item>
|
|
15
|
+
* ))}
|
|
16
|
+
* </Select.Content>
|
|
17
|
+
* </Select>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* ## The list opens over the trigger
|
|
21
|
+
*
|
|
22
|
+
* Opened with a pointer, the list is placed so the chosen option sits exactly
|
|
23
|
+
* over the trigger, and the eye does not have to go looking for it. Where
|
|
24
|
+
* there is no room for that, or `alignItemWithTrigger` is false, it opens
|
|
25
|
+
* below. Opened from the keyboard, the arrow keys move through the options,
|
|
26
|
+
* typing jumps to one by its first letters, and Enter chooses.
|
|
27
|
+
*
|
|
28
|
+
* ## Labels
|
|
29
|
+
*
|
|
30
|
+
* Pass `items` to the root, as `{ value, label }` pairs, and `Select.Value`
|
|
31
|
+
* shows the chosen option's label rather than its value. Without `items`,
|
|
32
|
+
* give `Select.Value` a function that turns a value into what to show.
|
|
33
|
+
*
|
|
34
|
+
* ## Three triggers
|
|
35
|
+
*
|
|
36
|
+
* `default` is a field, for a form. `ghost` has no box until it is hovered,
|
|
37
|
+
* for a choice set into a toolbar or a line of text. `pill` is a rounded chip
|
|
38
|
+
* with an edge, for a choice that sits among other chips, such as the model
|
|
39
|
+
* in a prompt.
|
|
40
|
+
*/
|
|
41
|
+
import { Select as BaseSelect } from '@base-ui/react/select';
|
|
42
|
+
import { tv } from 'tailwind-variants';
|
|
43
|
+
const selectVariants = tv({
|
|
44
|
+
slots: {
|
|
45
|
+
trigger: [
|
|
46
|
+
'group/select-trigger inline-flex min-w-0 cursor-pointer select-none items-center justify-between gap-2 whitespace-nowrap text-foreground outline-none',
|
|
47
|
+
'transition-[background-color,border-color,box-shadow,color] duration-150',
|
|
48
|
+
'focus-visible:ring-[3px] focus-visible:ring-ring/30',
|
|
49
|
+
'data-disabled:cursor-not-allowed data-disabled:opacity-50',
|
|
50
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
|
|
51
|
+
],
|
|
52
|
+
icon: 'flex text-muted-foreground transition-transform duration-200 group-data-popup-open/select-trigger:rotate-180 motion-reduce:transition-none',
|
|
53
|
+
value: 'flex min-w-0 flex-1 items-center gap-2 truncate text-start data-placeholder:text-muted-foreground/80',
|
|
54
|
+
positioner: 'z-50 outline-none',
|
|
55
|
+
popup: [
|
|
56
|
+
'min-w-(--anchor-width) origin-(--transform-origin) overflow-hidden rounded-xl border bg-popover text-popover-foreground shadow-lg outline-none',
|
|
57
|
+
'transition-[opacity,scale] duration-150 ease-out',
|
|
58
|
+
'data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0',
|
|
59
|
+
// Placed over the trigger, it only fades: growing from a point inside
|
|
60
|
+
// the list would look like it came from nowhere.
|
|
61
|
+
'data-[side=none]:data-starting-style:scale-100 data-[side=none]:data-ending-style:scale-100',
|
|
62
|
+
'motion-reduce:transition-none',
|
|
63
|
+
],
|
|
64
|
+
list: 'max-h-(--available-height) scroll-py-1 overflow-y-auto p-1 [scrollbar-color:var(--border)_transparent] [scrollbar-width:thin]',
|
|
65
|
+
item: [
|
|
66
|
+
'flex cursor-default items-center gap-2.5 rounded-lg px-2 py-1.5 text-sm outline-none select-none',
|
|
67
|
+
'data-highlighted:bg-accent data-highlighted:text-accent-foreground',
|
|
68
|
+
'data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
69
|
+
'in-data-[side=none]:min-w-[calc(var(--anchor-width)+0.5rem)]',
|
|
70
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
|
|
71
|
+
],
|
|
72
|
+
itemIcon: 'flex size-4 shrink-0 items-center justify-center text-muted-foreground',
|
|
73
|
+
itemBody: 'flex min-w-0 flex-1 flex-col',
|
|
74
|
+
itemText: 'truncate',
|
|
75
|
+
itemDescription: 'truncate text-xs text-muted-foreground',
|
|
76
|
+
// The box is always there, so an option does not change width as it is chosen.
|
|
77
|
+
indicator: 'flex size-4 shrink-0 items-center justify-center text-foreground',
|
|
78
|
+
group: 'flex flex-col',
|
|
79
|
+
groupLabel: 'px-2 pb-1 pt-2 text-xs font-medium text-muted-foreground',
|
|
80
|
+
separator: 'mx-2 my-1 h-px bg-border',
|
|
81
|
+
},
|
|
82
|
+
variants: {
|
|
83
|
+
variant: {
|
|
84
|
+
default: {
|
|
85
|
+
trigger: 'rounded-lg border border-input bg-background shadow-xs hover:bg-accent/40 data-popup-open:border-ring dark:bg-input/30',
|
|
86
|
+
},
|
|
87
|
+
ghost: {
|
|
88
|
+
trigger: 'rounded-lg text-muted-foreground hover:bg-accent hover:text-foreground data-popup-open:bg-accent data-popup-open:text-foreground',
|
|
89
|
+
},
|
|
90
|
+
pill: {
|
|
91
|
+
trigger: 'rounded-full border bg-background shadow-xs hover:bg-accent data-popup-open:bg-accent dark:bg-input/30',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
size: {
|
|
95
|
+
sm: { trigger: 'h-8 px-2.5 text-[0.8125rem]' },
|
|
96
|
+
md: { trigger: 'h-9 px-3 text-sm' },
|
|
97
|
+
lg: { trigger: 'h-11 px-3.5 text-[0.9375rem]' },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
compoundVariants: [
|
|
101
|
+
// A field is as wide as its column; a ghost or a pill is as wide as its label.
|
|
102
|
+
{ variant: 'default', className: { trigger: 'w-full' } },
|
|
103
|
+
{ variant: 'ghost', size: 'sm', className: { trigger: 'px-2' } },
|
|
104
|
+
],
|
|
105
|
+
defaultVariants: { variant: 'default', size: 'md' },
|
|
106
|
+
});
|
|
107
|
+
/** Holds the chosen value. Takes `value`/`onValueChange`, `defaultValue`, `items`, `name`, `disabled` and `multiple`. */
|
|
108
|
+
function SelectRoot(props) {
|
|
109
|
+
return _jsx(BaseSelect.Root, { ...props });
|
|
110
|
+
}
|
|
111
|
+
SelectRoot.displayName = 'Select';
|
|
112
|
+
function Chevron({ variant }) {
|
|
113
|
+
// A field shows both directions, since its list can open either way over it.
|
|
114
|
+
// A chip shows one, and turns over while its list is open.
|
|
115
|
+
return (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: variant === 'default' || variant === undefined ? _jsx("path", { d: "m7 15 5 5 5-5M7 9l5-5 5 5" }) : _jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
116
|
+
}
|
|
117
|
+
/** The button that opens the list. Put a `Select.Value` in it. */
|
|
118
|
+
function SelectTrigger({ variant, size, icon, className, children, ...props }) {
|
|
119
|
+
const slots = selectVariants({ variant, size });
|
|
120
|
+
return (_jsxs(BaseSelect.Trigger, { "data-slot": "select-trigger", className: slots.trigger({ className }), ...props, children: [children, icon === null ? null : (_jsx(BaseSelect.Icon, { "data-slot": "select-icon", className: slots.icon({ className: variant === 'default' || variant === undefined ? 'rotate-0!' : undefined }), children: icon ?? _jsx(Chevron, { variant: variant }) }))] }));
|
|
121
|
+
}
|
|
122
|
+
SelectTrigger.displayName = 'Select.Trigger';
|
|
123
|
+
/** The chosen option's label, or `placeholder` while there is none. */
|
|
124
|
+
function SelectValue({ className, ...props }) {
|
|
125
|
+
return _jsx(BaseSelect.Value, { "data-slot": "select-value", className: selectVariants().value({ className }), ...props });
|
|
126
|
+
}
|
|
127
|
+
SelectValue.displayName = 'Select.Value';
|
|
128
|
+
/** The list of options, opened in a layer over the page. */
|
|
129
|
+
function SelectContent({ side = 'bottom', align = 'start', sideOffset = 6, alignItemWithTrigger = true, container, className, children, ...props }) {
|
|
130
|
+
const slots = selectVariants();
|
|
131
|
+
return (_jsx(BaseSelect.Portal, { container: container, children: _jsx(BaseSelect.Positioner, { side: side, align: align, sideOffset: sideOffset, alignItemWithTrigger: alignItemWithTrigger, className: slots.positioner(), children: _jsx(BaseSelect.Popup, { "data-slot": "select-content", className: slots.popup({ className }), ...props, children: _jsx(BaseSelect.List, { className: slots.list(), children: children }) }) }) }));
|
|
132
|
+
}
|
|
133
|
+
SelectContent.displayName = 'Select.Content';
|
|
134
|
+
/** One option. A check marks the chosen one. */
|
|
135
|
+
function SelectItem({ description, icon, className, children, ...props }) {
|
|
136
|
+
const slots = selectVariants();
|
|
137
|
+
return (_jsxs(BaseSelect.Item, { "data-slot": "select-item", className: slots.item({ className }), ...props, children: [icon ? (_jsx("span", { "data-slot": "select-item-icon", "aria-hidden": "true", className: slots.itemIcon(), children: icon })) : null, _jsxs("span", { className: slots.itemBody(), children: [_jsx(BaseSelect.ItemText, { className: slots.itemText(), children: children }), description ? _jsx("span", { className: slots.itemDescription(), children: description }) : null] }), _jsx("span", { className: slots.indicator(), children: _jsx(BaseSelect.ItemIndicator, { className: "flex", children: _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.25", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }) }) })] }));
|
|
138
|
+
}
|
|
139
|
+
SelectItem.displayName = 'Select.Item';
|
|
140
|
+
/** Options that belong together, under a `Select.GroupLabel`. */
|
|
141
|
+
function SelectGroup({ className, ...props }) {
|
|
142
|
+
return _jsx(BaseSelect.Group, { "data-slot": "select-group", className: selectVariants().group({ className }), ...props });
|
|
143
|
+
}
|
|
144
|
+
SelectGroup.displayName = 'Select.Group';
|
|
145
|
+
/** The heading of a group. Screen readers announce the group by it. */
|
|
146
|
+
function SelectGroupLabel({ className, ...props }) {
|
|
147
|
+
return (_jsx(BaseSelect.GroupLabel, { "data-slot": "select-group-label", className: selectVariants().groupLabel({ className }), ...props }));
|
|
148
|
+
}
|
|
149
|
+
SelectGroupLabel.displayName = 'Select.GroupLabel';
|
|
150
|
+
/** A rule between groups of options. */
|
|
151
|
+
function SelectSeparator({ className, ...props }) {
|
|
152
|
+
return _jsx("div", { role: "separator", "data-slot": "select-separator", className: selectVariants().separator({ className }), ...props });
|
|
153
|
+
}
|
|
154
|
+
SelectSeparator.displayName = 'Select.Separator';
|
|
155
|
+
export const Select = Object.assign(SelectRoot, {
|
|
156
|
+
Trigger: SelectTrigger,
|
|
157
|
+
Value: SelectValue,
|
|
158
|
+
Content: SelectContent,
|
|
159
|
+
Item: SelectItem,
|
|
160
|
+
Group: SelectGroup,
|
|
161
|
+
GroupLabel: SelectGroupLabel,
|
|
162
|
+
Separator: SelectSeparator,
|
|
163
|
+
});
|
|
164
|
+
export { SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectGroupLabel, SelectSeparator };
|
|
@@ -41,7 +41,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
41
41
|
*/
|
|
42
42
|
import { Drawer } from '@base-ui/react/drawer';
|
|
43
43
|
import { ScrollArea } from '@base-ui/react/scroll-area';
|
|
44
|
-
import { createContext, useContext, useMemo, } from 'react';
|
|
44
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
45
45
|
import { tv } from 'tailwind-variants';
|
|
46
46
|
/*
|
|
47
47
|
* One curve for every movement: the sheet arriving, the one behind it stepping
|
|
@@ -216,8 +216,44 @@ function useSheet(part) {
|
|
|
216
216
|
function SheetRoot({ children, open, onOpenChange, defaultOpen = false, side = 'bottom', dismissible = true, }) {
|
|
217
217
|
// A sheet inside another sheet's content finds that sheet's context.
|
|
218
218
|
const parent = useContext(SheetContext);
|
|
219
|
-
const
|
|
220
|
-
|
|
219
|
+
const nested = parent !== null && !parent.inline;
|
|
220
|
+
// Held here as well as by the primitive, so a sheet opened from this one
|
|
221
|
+
// can close it.
|
|
222
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
223
|
+
const isOpen = open ?? internalOpen;
|
|
224
|
+
const setOpen = useCallback((next) => {
|
|
225
|
+
if (open === undefined)
|
|
226
|
+
setInternalOpen(next);
|
|
227
|
+
onOpenChange?.(next);
|
|
228
|
+
}, [open, onOpenChange]);
|
|
229
|
+
// The sheets opened from this one, so a press on the page behind them all
|
|
230
|
+
// can close the whole stack. The primitive gives that press to no one
|
|
231
|
+
// while a second sheet is open.
|
|
232
|
+
const opened = useRef(new Set());
|
|
233
|
+
const live = useRef({ isOpen, setOpen });
|
|
234
|
+
live.current = { isOpen, setOpen };
|
|
235
|
+
const handle = useMemo(() => ({
|
|
236
|
+
isOpen: () => live.current.isOpen,
|
|
237
|
+
closeTree: () => {
|
|
238
|
+
opened.current.forEach((child) => child.closeTree());
|
|
239
|
+
if (live.current.isOpen)
|
|
240
|
+
live.current.setOpen(false);
|
|
241
|
+
},
|
|
242
|
+
}), []);
|
|
243
|
+
useEffect(() => (nested && parent ? parent.register(handle) : undefined), [nested, parent, handle]);
|
|
244
|
+
const context = useMemo(() => ({
|
|
245
|
+
side,
|
|
246
|
+
nested,
|
|
247
|
+
inline: false,
|
|
248
|
+
showClose: true,
|
|
249
|
+
register: (child) => {
|
|
250
|
+
opened.current.add(child);
|
|
251
|
+
return () => opened.current.delete(child);
|
|
252
|
+
},
|
|
253
|
+
closeTree: handle.closeTree,
|
|
254
|
+
hasOpenChild: () => [...opened.current].some((child) => child.isOpen()),
|
|
255
|
+
}), [side, nested, handle]);
|
|
256
|
+
return (_jsx(SheetContext.Provider, { value: context, children: _jsx(Drawer.Root, { open: isOpen, swipeDirection: SWIPE[side], disablePointerDismissal: !dismissible, onOpenChange: (next, details) => {
|
|
221
257
|
// The primitive has a switch for a press outside and none for
|
|
222
258
|
// Escape or a swipe, so those are refused here.
|
|
223
259
|
if (!next &&
|
|
@@ -226,7 +262,7 @@ function SheetRoot({ children, open, onOpenChange, defaultOpen = false, side = '
|
|
|
226
262
|
details.reason !== 'imperative-action') {
|
|
227
263
|
return;
|
|
228
264
|
}
|
|
229
|
-
|
|
265
|
+
setOpen(next);
|
|
230
266
|
}, children: children }) }));
|
|
231
267
|
}
|
|
232
268
|
SheetRoot.displayName = 'Sheet';
|
|
@@ -250,7 +286,12 @@ function SheetContent({ children, detached = true, size = 'auto', showGrabber, s
|
|
|
250
286
|
if (inline) {
|
|
251
287
|
return (_jsx(SheetContext.Provider, { value: context, children: _jsx("div", { className: slots.popup({ className: ['[transform:none]', className] }), ...props, children: inside }) }));
|
|
252
288
|
}
|
|
253
|
-
return (_jsxs(Drawer.Portal, { children: [sheet.nested ? null : _jsx(Drawer.Backdrop, { className: slots.backdrop()
|
|
289
|
+
return (_jsxs(Drawer.Portal, { children: [sheet.nested ? null : (_jsx(Drawer.Backdrop, { className: slots.backdrop(),
|
|
290
|
+
// With a second sheet open, a press here is outside both, and closes both.
|
|
291
|
+
onClick: () => {
|
|
292
|
+
if (sheet.hasOpenChild())
|
|
293
|
+
sheet.closeTree();
|
|
294
|
+
} })), _jsx(Drawer.Viewport, { className: slots.viewport(), children: _jsx(Drawer.Popup, { className: slots.popup({ className }), ...props, children: _jsx(SheetContext.Provider, { value: context, children: inside }) }) })] }));
|
|
254
295
|
}
|
|
255
296
|
SheetContent.displayName = 'Sheet.Content';
|
|
256
297
|
/**
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Textarea — a field for more than one line, that grows as it is written in.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* <Textarea placeholder="What happened?" />
|
|
6
|
+
* <Textarea minRows={1} maxRows={6} variant="ghost" />
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* ## Growing
|
|
10
|
+
*
|
|
11
|
+
* It starts `minRows` lines tall and grows a line at a time as text is
|
|
12
|
+
* written, up to `maxRows`. Past that it scrolls, with a thin scrollbar of its
|
|
13
|
+
* own rather than the browser's. Pass `autoResize={false}` for a field that
|
|
14
|
+
* keeps the size `rows` gives it and can be resized by hand.
|
|
15
|
+
*
|
|
16
|
+
* The height is measured from the text, so it is right on the first render
|
|
17
|
+
* for a value that was already there, and for a value set from outside.
|
|
18
|
+
*
|
|
19
|
+
* ## Ghost
|
|
20
|
+
*
|
|
21
|
+
* `variant="ghost"` drops the border, background and ring, for a field set
|
|
22
|
+
* inside something that already draws them, such as a card or a composer.
|
|
23
|
+
*/
|
|
24
|
+
import { type ComponentProps } from 'react';
|
|
25
|
+
export interface TextareaProps extends Omit<ComponentProps<'textarea'>, 'size'> {
|
|
26
|
+
/** `ghost` draws no box, for a field inside something that draws one. Default `default`. */
|
|
27
|
+
variant?: 'default' | 'ghost';
|
|
28
|
+
/** Its text size and line height. Default `md`. */
|
|
29
|
+
size?: 'sm' | 'md' | 'lg';
|
|
30
|
+
/** Whether it grows with what is written in it. Default `true`. */
|
|
31
|
+
autoResize?: boolean;
|
|
32
|
+
/** How many lines tall it starts, while growing. Default `2`. */
|
|
33
|
+
minRows?: number;
|
|
34
|
+
/** How many lines tall it can grow before it scrolls. Default `8`. */
|
|
35
|
+
maxRows?: number;
|
|
36
|
+
}
|
|
37
|
+
/** A multi-line field. Every attribute of `<textarea>` passes through. */
|
|
38
|
+
declare function TextareaRoot({ variant, size, autoResize, minRows, maxRows, rows, value, onInput, className, style, ref, ...props }: TextareaProps): import("react").JSX.Element;
|
|
39
|
+
declare namespace TextareaRoot {
|
|
40
|
+
var displayName: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const Textarea: typeof TextareaRoot;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Textarea — a field for more than one line, that grows as it is written in.
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <Textarea placeholder="What happened?" />
|
|
8
|
+
* <Textarea minRows={1} maxRows={6} variant="ghost" />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* ## Growing
|
|
12
|
+
*
|
|
13
|
+
* It starts `minRows` lines tall and grows a line at a time as text is
|
|
14
|
+
* written, up to `maxRows`. Past that it scrolls, with a thin scrollbar of its
|
|
15
|
+
* own rather than the browser's. Pass `autoResize={false}` for a field that
|
|
16
|
+
* keeps the size `rows` gives it and can be resized by hand.
|
|
17
|
+
*
|
|
18
|
+
* The height is measured from the text, so it is right on the first render
|
|
19
|
+
* for a value that was already there, and for a value set from outside.
|
|
20
|
+
*
|
|
21
|
+
* ## Ghost
|
|
22
|
+
*
|
|
23
|
+
* `variant="ghost"` drops the border, background and ring, for a field set
|
|
24
|
+
* inside something that already draws them, such as a card or a composer.
|
|
25
|
+
*/
|
|
26
|
+
import { useCallback, useLayoutEffect, useRef } from 'react';
|
|
27
|
+
import { tv } from 'tailwind-variants';
|
|
28
|
+
const textareaVariants = tv({
|
|
29
|
+
base: [
|
|
30
|
+
'block w-full min-w-0 bg-transparent text-foreground outline-none',
|
|
31
|
+
'placeholder:text-muted-foreground/80 selection:bg-primary selection:text-primary-foreground',
|
|
32
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
33
|
+
// A thin bar in the theme's border colour, never the platform's wide one.
|
|
34
|
+
'[scrollbar-color:var(--border)_transparent] [scrollbar-width:thin]',
|
|
35
|
+
],
|
|
36
|
+
variants: {
|
|
37
|
+
variant: {
|
|
38
|
+
default: [
|
|
39
|
+
'rounded-lg border border-input bg-background shadow-xs transition-[border-color,box-shadow] dark:bg-input/30',
|
|
40
|
+
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/30',
|
|
41
|
+
'aria-invalid:border-destructive aria-invalid:ring-destructive/20',
|
|
42
|
+
],
|
|
43
|
+
ghost: 'rounded-none border-0 p-0 shadow-none',
|
|
44
|
+
},
|
|
45
|
+
size: {
|
|
46
|
+
sm: 'text-[0.8125rem] leading-5',
|
|
47
|
+
md: 'text-sm leading-6',
|
|
48
|
+
lg: 'text-base leading-7',
|
|
49
|
+
},
|
|
50
|
+
autoResize: {
|
|
51
|
+
true: 'resize-none',
|
|
52
|
+
false: 'resize-y',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
compoundVariants: [
|
|
56
|
+
{ variant: 'default', size: 'sm', className: 'px-2.5 py-1.5' },
|
|
57
|
+
{ variant: 'default', size: 'md', className: 'px-3 py-2' },
|
|
58
|
+
{ variant: 'default', size: 'lg', className: 'px-3.5 py-2.5' },
|
|
59
|
+
],
|
|
60
|
+
defaultVariants: { variant: 'default', size: 'md', autoResize: true },
|
|
61
|
+
});
|
|
62
|
+
function setRef(ref, value) {
|
|
63
|
+
if (typeof ref === 'function')
|
|
64
|
+
ref(value);
|
|
65
|
+
else if (ref)
|
|
66
|
+
ref.current = value;
|
|
67
|
+
}
|
|
68
|
+
/** A multi-line field. Every attribute of `<textarea>` passes through. */
|
|
69
|
+
function TextareaRoot({ variant, size, autoResize = true, minRows = 2, maxRows = 8, rows, value, onInput, className, style, ref, ...props }) {
|
|
70
|
+
const field = useRef(null);
|
|
71
|
+
// Set to one row, read how tall the text wants to be, and clamp that to the
|
|
72
|
+
// rows allowed. The padding and border are part of both ends of the clamp.
|
|
73
|
+
const fit = useCallback(() => {
|
|
74
|
+
const node = field.current;
|
|
75
|
+
if (!node || !autoResize)
|
|
76
|
+
return;
|
|
77
|
+
const computed = getComputedStyle(node);
|
|
78
|
+
const line = parseFloat(computed.lineHeight) || 20;
|
|
79
|
+
const frame = parseFloat(computed.paddingTop) +
|
|
80
|
+
parseFloat(computed.paddingBottom) +
|
|
81
|
+
parseFloat(computed.borderTopWidth) +
|
|
82
|
+
parseFloat(computed.borderBottomWidth);
|
|
83
|
+
node.style.height = 'auto';
|
|
84
|
+
const wanted = node.scrollHeight + parseFloat(computed.borderTopWidth) + parseFloat(computed.borderBottomWidth);
|
|
85
|
+
const min = line * minRows + frame;
|
|
86
|
+
const max = line * Math.max(maxRows, minRows) + frame;
|
|
87
|
+
node.style.height = `${Math.min(Math.max(wanted, min), max)}px`;
|
|
88
|
+
node.style.overflowY = wanted > max ? 'auto' : 'hidden';
|
|
89
|
+
}, [autoResize, minRows, maxRows]);
|
|
90
|
+
// Before paint, so a value that was already there never shows at the wrong height.
|
|
91
|
+
useLayoutEffect(fit, [fit, value]);
|
|
92
|
+
return (_jsx("textarea", { ref: (node) => {
|
|
93
|
+
field.current = node;
|
|
94
|
+
setRef(ref, node);
|
|
95
|
+
}, "data-slot": "textarea", rows: autoResize ? minRows : rows, value: value, onInput: (event) => {
|
|
96
|
+
onInput?.(event);
|
|
97
|
+
fit();
|
|
98
|
+
}, className: textareaVariants({ variant, size, autoResize, className }), style: style, ...props }));
|
|
99
|
+
}
|
|
100
|
+
TextareaRoot.displayName = 'Textarea';
|
|
101
|
+
export const Textarea = TextareaRoot;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,3 +15,9 @@ export { TiltCard, TiltCardLayer, TiltCardTitle, TiltCardDescription, type TiltC
|
|
|
15
15
|
export { Stat, StatContent, StatLabel, StatDescription, StatValue, StatUnit, StatBadge, type StatProps, type StatContentProps, type StatLabelProps, type StatDescriptionProps, type StatValueProps, type StatUnitProps, type StatBadgeProps, } from './components/stat/index.js';
|
|
16
16
|
export { CommandDock, CommandDockTrigger, CommandDockPanel, CommandDockInput, CommandDockResults, CommandDockRow, CommandDockRowTitle, CommandDockRowMeta, CommandDockStatus, type CommandDockProps, type CommandDockTriggerProps, type CommandDockPanelProps, type CommandDockInputProps, type CommandDockResultsProps, type CommandDockRowProps, type CommandDockRowTitleProps, type CommandDockRowMetaProps, type CommandDockStatusProps, } from './components/command-dock/index.js';
|
|
17
17
|
export { NotFound, NotFoundCode, NotFoundTitle, NotFoundDescription, NotFoundActions, type NotFoundProps, type NotFoundCodeProps, type NotFoundTitleProps, type NotFoundDescriptionProps, type NotFoundActionsProps, type NotFoundEffect, type NotFoundColor, } from './components/not-found/index.js';
|
|
18
|
+
export { Button, buttonVariants, type ButtonProps, type ButtonVariant, type ButtonSize } from './components/button/index.js';
|
|
19
|
+
export { Input, InputGroup, InputAddon, type InputProps, type InputGroupProps, type InputAddonProps, type InputSize } from './components/input/index.js';
|
|
20
|
+
export { Textarea, type TextareaProps } from './components/textarea/index.js';
|
|
21
|
+
export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectGroupLabel, SelectSeparator, type SelectProps, type SelectTriggerProps, type SelectValueProps, type SelectContentProps, type SelectItemProps, type SelectGroupProps, type SelectGroupLabelProps, type SelectSeparatorProps, } from './components/select/index.js';
|
|
22
|
+
export { Prompt, PromptBanner, PromptBannerAction, PromptField, PromptAttachments, PromptTextarea, PromptFooter, PromptTools, PromptTool, PromptAttach, PromptMenu, PromptAttachItem, PromptModel, PromptSubmit, PromptStatus, PromptTasks, PromptTask, PromptCommands, PromptRecorder, PromptContext, PromptContextItem, PromptHint, type PromptProps, type PromptState, type PromptMessage, type PromptAttachment, type PromptRejectReason, type PromptBannerProps, type PromptBannerActionProps, type PromptFieldProps, type PromptAttachmentsProps, type PromptTextareaProps, type PromptFooterProps, type PromptToolsProps, type PromptToolProps, type PromptAttachProps, type PromptMenuProps, type PromptAttachItemProps, type PromptModelProps, type PromptModelItem, type PromptSubmitProps, type PromptStatusProps, type PromptTasksProps, type PromptTaskProps, type PromptTaskStatus, type PromptCommandsProps, type PromptCommandItem, type PromptRecorderProps, type PromptContextProps, type PromptContextItemProps, type PromptHintProps, } from './components/prompt/index.js';
|
|
23
|
+
export { Menu, MenuTrigger, MenuContent, MenuItem, MenuCheckboxItem, MenuRadioGroup, MenuRadioItem, MenuSub, MenuSubTrigger, MenuSubContent, MenuGroup, MenuGroupLabel, MenuSeparator, MenuDescription, MenuShortcut, MenuBadge, type MenuProps, type MenuTriggerProps, type MenuContentProps, type MenuItemProps, type MenuCheckboxItemProps, type MenuRadioGroupProps, type MenuRadioItemProps, type MenuSubProps, type MenuSubTriggerProps, type MenuSubContentProps, type MenuGroupProps, type MenuGroupLabelProps, type MenuSeparatorProps, type MenuDescriptionProps, type MenuShortcutProps, type MenuBadgeProps, } from './components/menu/index.js';
|
package/dist/index.js
CHANGED
|
@@ -15,3 +15,9 @@ export { TiltCard, TiltCardLayer, TiltCardTitle, TiltCardDescription, } from './
|
|
|
15
15
|
export { Stat, StatContent, StatLabel, StatDescription, StatValue, StatUnit, StatBadge, } from './components/stat/index.js';
|
|
16
16
|
export { CommandDock, CommandDockTrigger, CommandDockPanel, CommandDockInput, CommandDockResults, CommandDockRow, CommandDockRowTitle, CommandDockRowMeta, CommandDockStatus, } from './components/command-dock/index.js';
|
|
17
17
|
export { NotFound, NotFoundCode, NotFoundTitle, NotFoundDescription, NotFoundActions, } from './components/not-found/index.js';
|
|
18
|
+
export { Button, buttonVariants } from './components/button/index.js';
|
|
19
|
+
export { Input, InputGroup, InputAddon } from './components/input/index.js';
|
|
20
|
+
export { Textarea } from './components/textarea/index.js';
|
|
21
|
+
export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectGroupLabel, SelectSeparator, } from './components/select/index.js';
|
|
22
|
+
export { Prompt, PromptBanner, PromptBannerAction, PromptField, PromptAttachments, PromptTextarea, PromptFooter, PromptTools, PromptTool, PromptAttach, PromptMenu, PromptAttachItem, PromptModel, PromptSubmit, PromptStatus, PromptTasks, PromptTask, PromptCommands, PromptRecorder, PromptContext, PromptContextItem, PromptHint, } from './components/prompt/index.js';
|
|
23
|
+
export { Menu, MenuTrigger, MenuContent, MenuItem, MenuCheckboxItem, MenuRadioGroup, MenuRadioItem, MenuSub, MenuSubTrigger, MenuSubContent, MenuGroup, MenuGroupLabel, MenuSeparator, MenuDescription, MenuShortcut, MenuBadge, } from './components/menu/index.js';
|