@brightweblabs/ui 1.4.6 → 1.4.8
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/package.json +3 -1
- package/src/components/calendar.tsx +18 -0
- package/src/components/select.tsx +216 -0
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brightweblabs/ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.8",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"files": [
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"./popover": "./src/components/popover.tsx",
|
|
45
45
|
"./role-badge": "./src/components/role-badge.tsx",
|
|
46
46
|
"./search-field": "./src/components/search-field.tsx",
|
|
47
|
+
"./select": "./src/components/select.tsx",
|
|
47
48
|
"./section-heading": "./src/components/section-heading.tsx",
|
|
48
49
|
"./separator": "./src/components/separator.tsx",
|
|
49
50
|
"./sheet": "./src/components/sheet.tsx",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"@radix-ui/react-dropdown-menu": "^2.1.22",
|
|
67
68
|
"@radix-ui/react-label": "^2.1.13",
|
|
68
69
|
"@radix-ui/react-popover": "^1.1.21",
|
|
70
|
+
"@radix-ui/react-select": "^2.3.7",
|
|
69
71
|
"@radix-ui/react-separator": "^1.1.13",
|
|
70
72
|
"@radix-ui/react-slot": "^1.3.1",
|
|
71
73
|
"@radix-ui/react-tooltip": "^1.2.14",
|
|
@@ -16,6 +16,8 @@ import { cn } from "../lib/utils"
|
|
|
16
16
|
import { Button } from "./button"
|
|
17
17
|
import { buttonVariants, type ButtonVariantProps } from "./button-variants"
|
|
18
18
|
|
|
19
|
+
const DEFAULT_FUTURE_YEAR_RANGE = 25
|
|
20
|
+
|
|
19
21
|
function Calendar({
|
|
20
22
|
className,
|
|
21
23
|
classNames,
|
|
@@ -29,6 +31,21 @@ function Calendar({
|
|
|
29
31
|
buttonVariant?: ButtonVariantProps["variant"]
|
|
30
32
|
}) {
|
|
31
33
|
const defaultClassNames = getDefaultClassNames()
|
|
34
|
+
const hasYearDropdown =
|
|
35
|
+
captionLayout === "dropdown" || captionLayout === "dropdown-years"
|
|
36
|
+
const hasExplicitEndMonth =
|
|
37
|
+
props.endMonth !== undefined ||
|
|
38
|
+
props.toMonth !== undefined ||
|
|
39
|
+
props.toYear !== undefined
|
|
40
|
+
const defaultEndMonth =
|
|
41
|
+
hasYearDropdown && !hasExplicitEndMonth
|
|
42
|
+
? new Date(
|
|
43
|
+
(props.today ?? new Date()).getFullYear() +
|
|
44
|
+
DEFAULT_FUTURE_YEAR_RANGE,
|
|
45
|
+
11,
|
|
46
|
+
31
|
|
47
|
+
)
|
|
48
|
+
: undefined
|
|
32
49
|
|
|
33
50
|
return (
|
|
34
51
|
<DayPicker
|
|
@@ -176,6 +193,7 @@ function Calendar({
|
|
|
176
193
|
...components,
|
|
177
194
|
}}
|
|
178
195
|
{...props}
|
|
196
|
+
endMonth={props.endMonth ?? defaultEndMonth}
|
|
179
197
|
/>
|
|
180
198
|
)
|
|
181
199
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
5
|
+
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../lib/utils";
|
|
8
|
+
|
|
9
|
+
const Select = SelectPrimitive.Root;
|
|
10
|
+
const SelectGroup = SelectPrimitive.Group;
|
|
11
|
+
const SelectValue = SelectPrimitive.Value;
|
|
12
|
+
const EMPTY_SELECT_VALUE = "__brightweb_empty_select_value__";
|
|
13
|
+
|
|
14
|
+
export type SelectControlOption = {
|
|
15
|
+
value: string;
|
|
16
|
+
label: React.ReactNode;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type SelectControlProps = {
|
|
21
|
+
id?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
value: string;
|
|
24
|
+
options: readonly SelectControlOption[];
|
|
25
|
+
onValueChange: (value: string) => void;
|
|
26
|
+
placeholder?: string;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
contentClassName?: string;
|
|
31
|
+
"aria-label"?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type StyledSelectChangeEvent = {
|
|
35
|
+
target: { value: string };
|
|
36
|
+
currentTarget: { value: string };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type StyledSelectProps = Omit<SelectControlProps, "options" | "onValueChange" | "placeholder"> & {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
onChange?: (event: StyledSelectChangeEvent) => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function SelectTrigger({
|
|
45
|
+
className,
|
|
46
|
+
children,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger>) {
|
|
49
|
+
return (
|
|
50
|
+
<SelectPrimitive.Trigger
|
|
51
|
+
data-slot="select-trigger"
|
|
52
|
+
className={cn(
|
|
53
|
+
"flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-[color:var(--sheet-edit-control-border,var(--border))] bg-[color:var(--card)] px-2.5 text-left text-body text-foreground shadow-none outline-none transition-[border-color,box-shadow,background-color] placeholder:text-foreground/45 focus:border-[color:var(--accent)] focus:ring-[3px] focus:ring-[color:var(--project-ui-color-10,var(--ring))] disabled:cursor-not-allowed disabled:opacity-55 [&>span]:min-w-0 [&>span]:truncate",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
<SelectPrimitive.Icon asChild>
|
|
60
|
+
<ChevronDown className="size-4 shrink-0 text-foreground/55 transition-transform duration-150 data-[state=open]:rotate-180" />
|
|
61
|
+
</SelectPrimitive.Icon>
|
|
62
|
+
</SelectPrimitive.Trigger>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function SelectContent({
|
|
67
|
+
className,
|
|
68
|
+
children,
|
|
69
|
+
position = "popper",
|
|
70
|
+
sideOffset = 6,
|
|
71
|
+
...props
|
|
72
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
73
|
+
return (
|
|
74
|
+
<SelectPrimitive.Portal>
|
|
75
|
+
<SelectPrimitive.Content
|
|
76
|
+
data-slot="select-content"
|
|
77
|
+
position={position}
|
|
78
|
+
sideOffset={sideOffset}
|
|
79
|
+
className={cn(
|
|
80
|
+
"relative z-[1400] max-h-[min(20rem,var(--radix-select-content-available-height))] min-w-[var(--radix-select-trigger-width)] overflow-hidden rounded-xl border border-hairline bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
{...props}
|
|
84
|
+
>
|
|
85
|
+
<SelectPrimitive.ScrollUpButton className="flex h-7 cursor-default items-center justify-center bg-popover text-foreground/60">
|
|
86
|
+
<ChevronUp className="size-4" />
|
|
87
|
+
</SelectPrimitive.ScrollUpButton>
|
|
88
|
+
<SelectPrimitive.Viewport className="p-1.5">{children}</SelectPrimitive.Viewport>
|
|
89
|
+
<SelectPrimitive.ScrollDownButton className="flex h-7 cursor-default items-center justify-center bg-popover text-foreground/60">
|
|
90
|
+
<ChevronDown className="size-4" />
|
|
91
|
+
</SelectPrimitive.ScrollDownButton>
|
|
92
|
+
</SelectPrimitive.Content>
|
|
93
|
+
</SelectPrimitive.Portal>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function SelectLabel({
|
|
98
|
+
className,
|
|
99
|
+
...props
|
|
100
|
+
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
101
|
+
return (
|
|
102
|
+
<SelectPrimitive.Label
|
|
103
|
+
className={cn("px-2.5 py-1.5 text-label font-semibold text-muted-foreground", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function SelectItem({
|
|
110
|
+
className,
|
|
111
|
+
children,
|
|
112
|
+
...props
|
|
113
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
114
|
+
return (
|
|
115
|
+
<SelectPrimitive.Item
|
|
116
|
+
data-slot="select-item"
|
|
117
|
+
className={cn(
|
|
118
|
+
"relative flex min-h-9 w-full cursor-default select-none items-center rounded-lg py-2 pl-2.5 pr-9 text-body text-foreground outline-none transition-colors focus:bg-accent/12 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 data-[state=checked]:bg-accent/10 data-[state=checked]:font-semibold",
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
124
|
+
<span className="absolute right-2.5 flex size-4 items-center justify-center text-accent">
|
|
125
|
+
<SelectPrimitive.ItemIndicator>
|
|
126
|
+
<Check className="size-4 stroke-[2.5]" />
|
|
127
|
+
</SelectPrimitive.ItemIndicator>
|
|
128
|
+
</span>
|
|
129
|
+
</SelectPrimitive.Item>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function SelectSeparator({
|
|
134
|
+
className,
|
|
135
|
+
...props
|
|
136
|
+
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
137
|
+
return <SelectPrimitive.Separator className={cn("my-1 h-px bg-border", className)} {...props} />;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function SelectControl({
|
|
141
|
+
id,
|
|
142
|
+
name,
|
|
143
|
+
value,
|
|
144
|
+
options,
|
|
145
|
+
onValueChange,
|
|
146
|
+
placeholder,
|
|
147
|
+
disabled,
|
|
148
|
+
required,
|
|
149
|
+
className,
|
|
150
|
+
contentClassName,
|
|
151
|
+
"aria-label": ariaLabel,
|
|
152
|
+
}: SelectControlProps) {
|
|
153
|
+
const normalizedValue = value || EMPTY_SELECT_VALUE;
|
|
154
|
+
const normalizedOptions = options.some((option) => option.value === "")
|
|
155
|
+
? options
|
|
156
|
+
: placeholder
|
|
157
|
+
? [{ value: "", label: placeholder }, ...options]
|
|
158
|
+
: options;
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Select
|
|
162
|
+
name={name}
|
|
163
|
+
value={normalizedValue}
|
|
164
|
+
onValueChange={(nextValue) => onValueChange(nextValue === EMPTY_SELECT_VALUE ? "" : nextValue)}
|
|
165
|
+
disabled={disabled}
|
|
166
|
+
required={required}
|
|
167
|
+
>
|
|
168
|
+
<SelectTrigger id={id} aria-label={ariaLabel} className={className}>
|
|
169
|
+
<SelectValue />
|
|
170
|
+
</SelectTrigger>
|
|
171
|
+
<SelectContent align="start" className={contentClassName}>
|
|
172
|
+
{normalizedOptions.map((option) => (
|
|
173
|
+
<SelectItem
|
|
174
|
+
key={option.value || EMPTY_SELECT_VALUE}
|
|
175
|
+
value={option.value || EMPTY_SELECT_VALUE}
|
|
176
|
+
disabled={option.disabled}
|
|
177
|
+
>
|
|
178
|
+
{option.label}
|
|
179
|
+
</SelectItem>
|
|
180
|
+
))}
|
|
181
|
+
</SelectContent>
|
|
182
|
+
</Select>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function StyledSelect({ children, onChange, ...props }: StyledSelectProps) {
|
|
187
|
+
const options = React.Children.toArray(children).flatMap((child) => {
|
|
188
|
+
if (!React.isValidElement<{ value?: string | number; children?: React.ReactNode; disabled?: boolean }>(child) || child.type !== "option") return [];
|
|
189
|
+
return [{
|
|
190
|
+
value: child.props.value == null ? "" : String(child.props.value),
|
|
191
|
+
label: child.props.children,
|
|
192
|
+
disabled: child.props.disabled,
|
|
193
|
+
}];
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<SelectControl
|
|
198
|
+
{...props}
|
|
199
|
+
options={options}
|
|
200
|
+
onValueChange={(value) => onChange?.({ target: { value }, currentTarget: { value } })}
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export {
|
|
206
|
+
Select,
|
|
207
|
+
SelectControl,
|
|
208
|
+
SelectContent,
|
|
209
|
+
SelectGroup,
|
|
210
|
+
SelectItem,
|
|
211
|
+
SelectLabel,
|
|
212
|
+
SelectSeparator,
|
|
213
|
+
SelectTrigger,
|
|
214
|
+
SelectValue,
|
|
215
|
+
StyledSelect,
|
|
216
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export { PhoneInput } from "./components/phone-input";
|
|
|
26
26
|
export type { PhoneInputProps } from "./components/phone-input";
|
|
27
27
|
export { SearchField } from "./components/search-field";
|
|
28
28
|
export type { SearchFieldProps } from "./components/search-field";
|
|
29
|
+
export * from "./components/select";
|
|
29
30
|
export * from "./components/skeleton";
|
|
30
31
|
export { TableRowsSkeleton } from "./components/skeleton-table";
|
|
31
32
|
export { Separator } from "./components/separator";
|