Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions apps/web/src/components/ui/autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,6 @@ function AutocompleteEmpty({ className, ...props }: AutocompletePrimitive.Empty.
);
}

function AutocompleteRow({ className, ...props }: AutocompletePrimitive.Row.Props) {
return (
<AutocompletePrimitive.Row className={className} data-slot="autocomplete-row" {...props} />
);
}

function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) {
return <AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} />;
}

function AutocompleteList({ className, ...props }: AutocompletePrimitive.List.Props) {
return (
<ScrollArea scrollbarGutter scrollFade>
Expand Down Expand Up @@ -216,19 +206,6 @@ function AutocompleteClear({ className, ...props }: AutocompletePrimitive.Clear.
);
}

function AutocompleteStatus({ className, ...props }: AutocompletePrimitive.Status.Props) {
return (
<AutocompletePrimitive.Status
className={cn(
"px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0",
className,
)}
data-slot="autocomplete-status"
{...props}
/>
);
}

function AutocompleteCollection({ ...props }: AutocompletePrimitive.Collection.Props) {
return <AutocompletePrimitive.Collection data-slot="autocomplete-collection" {...props} />;
}
Expand All @@ -249,23 +226,15 @@ function AutocompleteTrigger({
);
}

const useAutocompleteFilter = AutocompletePrimitive.useFilter;

export {
Autocomplete,
AutocompleteInput,
AutocompleteTrigger,
AutocompletePopup,
AutocompleteItem,
AutocompleteSeparator,
AutocompleteGroup,
AutocompleteGroupLabel,
AutocompleteEmpty,
AutocompleteValue,
AutocompleteList,
AutocompleteClear,
AutocompleteStatus,
AutocompleteRow,
AutocompleteCollection,
useAutocompleteFilter,
};
141 changes: 1 addition & 140 deletions apps/web/src/components/ui/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,22 @@ import { ScrollArea } from "~/components/ui/scroll-area";

const ComboboxContext = React.createContext<{
chipsRef: React.RefObject<Element | null> | null;
multiple: boolean;
}>({
chipsRef: null,
multiple: false,
});

function Combobox<Value, Multiple extends boolean | undefined = false>(
props: ComboboxPrimitive.Root.Props<Value, Multiple>,
) {
const chipsRef = React.useRef<Element | null>(null);
const value = React.useMemo(() => ({ chipsRef, multiple: !!props.multiple }), [props.multiple]);
const value = React.useMemo(() => ({ chipsRef }), []);
return (
<ComboboxContext value={value}>
<ComboboxPrimitive.Root {...props} />
</ComboboxContext>
);
}

function ComboboxChipsInput({
className,
size,
...props
}: Omit<ComboboxPrimitive.Input.Props, "size"> & {
size?: "sm" | "default" | "lg" | number;
ref?: React.Ref<HTMLInputElement>;
}) {
const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number;

return (
<ComboboxPrimitive.Input
className={cn(
"min-w-12 flex-1 text-base outline-none sm:text-sm [[data-slot=combobox-chip]+&]:ps-0.5",
sizeValue === "sm" ? "ps-1.5" : "ps-2",
className,
)}
data-size={typeof sizeValue === "string" ? sizeValue : undefined}
data-slot="combobox-chips-input"
size={typeof sizeValue === "number" ? sizeValue : undefined}
{...props}
/>
);
}

function ComboboxInput({
className,
inputClassName,
Expand Down Expand Up @@ -219,36 +192,6 @@ function ComboboxItem({
);
}

function ComboboxSeparator({ className, ...props }: ComboboxPrimitive.Separator.Props) {
return (
<ComboboxPrimitive.Separator
className={cn("mx-2 my-1 h-px bg-border last:hidden", className)}
data-slot="combobox-separator"
{...props}
/>
);
}

function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
return (
<ComboboxPrimitive.Group
className={cn("[[role=group]+&]:mt-1.5", className)}
data-slot="combobox-group"
{...props}
/>
);
}

function ComboboxGroupLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) {
return (
<ComboboxPrimitive.GroupLabel
className={cn("px-2 py-1.5 font-medium text-muted-foreground text-xs", className)}
data-slot="combobox-group-label"
{...props}
/>
);
}

function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
return (
<ComboboxPrimitive.Empty
Expand All @@ -262,14 +205,6 @@ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
);
}

function ComboboxRow({ className, ...props }: ComboboxPrimitive.Row.Props) {
return <ComboboxPrimitive.Row className={className} data-slot="combobox-row" {...props} />;
}

function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;
}

function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
return (
<ScrollArea scrollbarGutter scrollFade>
Expand Down Expand Up @@ -313,91 +248,17 @@ function ComboboxStatus({ className, ...props }: ComboboxPrimitive.Status.Props)
);
}

function ComboboxCollection(props: ComboboxPrimitive.Collection.Props) {
return <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />;
}

function ComboboxChips({
className,
children,
startAddon,
...props
}: ComboboxPrimitive.Chips.Props & {
startAddon?: React.ReactNode;
}) {
const { chipsRef } = React.use(ComboboxContext);

return (
<ComboboxPrimitive.Chips
className={cn(
"relative inline-flex min-h-9 w-full flex-wrap gap-1 rounded-lg border border-input bg-background not-dark:bg-clip-padding p-[calc(--spacing(1)-1px)] text-base shadow-xs/5 outline-none ring-ring/24 transition-shadow *:min-h-7 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-within:border-ring focus-within:ring-[3px] has-disabled:pointer-events-none has-data-[size=lg]:min-h-10 has-data-[size=sm]:min-h-8 has-aria-invalid:border-destructive/36 has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-within,[aria-invalid]]:shadow-none focus-within:has-aria-invalid:border-destructive/64 focus-within:has-aria-invalid:ring-destructive/16 has-data-[size=lg]:*:min-h-8 has-data-[size=sm]:*:min-h-6 sm:min-h-8 sm:text-sm sm:has-data-[size=lg]:min-h-9 sm:has-data-[size=sm]:min-h-7 sm:*:min-h-6 sm:has-data-[size=lg]:*:min-h-7 sm:has-data-[size=sm]:*:min-h-5 dark:not-has-disabled:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
className,
)}
data-slot="combobox-chips"
ref={chipsRef as React.Ref<HTMLDivElement> | null}
{...props}
>
{startAddon && (
<div
aria-hidden="true"
className="[&_svg]:-ms-0.5 [&_svg]:-me-1.5 flex shrink-0 items-center ps-2 opacity-80 has-[~[data-size=sm]]:has-[+[data-slot=combobox-chip]]:pe-1.5 has-[~[data-size=sm]]:ps-1.5 has-[+[data-slot=combobox-chip]]:pe-2 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none"
data-slot="combobox-start-addon"
>
{startAddon}
</div>
)}
{children}
</ComboboxPrimitive.Chips>
);
}

function ComboboxChip({ children, ...props }: ComboboxPrimitive.Chip.Props) {
return (
<ComboboxPrimitive.Chip
className="flex items-center rounded-[calc(var(--radius-md)-1px)] bg-accent ps-2 font-medium text-accent-foreground text-sm outline-none sm:text-xs/(--text-xs--line-height) [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
data-slot="combobox-chip"
{...props}
>
{children}
<ComboboxChipRemove />
</ComboboxPrimitive.Chip>
);
}

function ComboboxChipRemove(props: ComboboxPrimitive.ChipRemove.Props) {
return (
<ComboboxPrimitive.ChipRemove
aria-label="Remove"
className="h-full shrink-0 cursor-pointer px-1.5 opacity-80 hover:opacity-100 [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
data-slot="combobox-chip-remove"
{...props}
>
<XIcon />
</ComboboxPrimitive.ChipRemove>
);
}

const useComboboxFilter = ComboboxPrimitive.useFilter;

export {
Combobox,
ComboboxChipsInput,
ComboboxInput,
ComboboxTrigger,
ComboboxPopup,
ComboboxItem,
ComboboxSeparator,
ComboboxGroup,
ComboboxGroupLabel,
ComboboxEmpty,
ComboboxValue,
ComboboxList,
ComboboxListVirtualized,
ComboboxClear,
ComboboxStatus,
ComboboxRow,
ComboboxCollection,
ComboboxChips,
ComboboxChip,
useComboboxFilter,
};
19 changes: 1 addition & 18 deletions apps/web/src/components/ui/input-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type * as React from "react";

import { cn } from "~/lib/utils";
import { Input, type InputProps } from "~/components/ui/input";
import { Textarea, type TextareaProps } from "~/components/ui/textarea";

const inputGroupVariants = cva(
"relative inline-flex w-full min-w-0 items-center rounded-[var(--control-radius)] border text-base text-foreground ring-ring/24 transition-shadow has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-autofill:bg-foreground/4 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:has-autofill:bg-foreground/8 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--control-radius)-1px)]",
Expand Down Expand Up @@ -90,24 +89,8 @@ function InputGroupAddon({
);
}

function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"[&_svg]:-mx-0.5 line-clamp-1 flex items-center gap-2 text-muted-foreground leading-none in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
className,
)}
{...props}
/>
);
}

function InputGroupInput({ className, ...props }: InputProps) {
return <Input className={className} unstyled {...props} />;
}

function InputGroupTextarea({ className, ...props }: TextareaProps) {
return <Textarea className={className} unstyled {...props} />;
}

export { InputGroup, InputGroupAddon, InputGroupText, InputGroupInput, InputGroupTextarea };
export { InputGroup, InputGroupAddon, InputGroupInput };
74 changes: 7 additions & 67 deletions apps/web/src/components/ui/number-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ import { NumberField as NumberFieldPrimitive } from "@base-ui/react/number-field
import { MinusIcon, PlusIcon } from "lucide-react";
import * as React from "react";
import { cn } from "~/lib/utils";
import { Label } from "~/components/ui/label";

export const NumberFieldContext: React.Context<{
fieldId: string;
} | null> = React.createContext<{
fieldId: string;
} | null>(null);

export function NumberField({
id,
Expand All @@ -22,18 +15,15 @@ export function NumberField({
}): React.ReactElement {
const generatedId = React.useId();
const fieldId = id ?? generatedId;
const contextValue = React.useMemo(() => ({ fieldId }), [fieldId]);

return (
<NumberFieldContext value={contextValue}>
<NumberFieldPrimitive.Root
className={cn("flex w-full flex-col items-start gap-2", className)}
data-size={size}
data-slot="number-field"
id={fieldId}
{...props}
/>
</NumberFieldContext>
<NumberFieldPrimitive.Root
className={cn("flex w-full flex-col items-start gap-2", className)}
data-size={size}
data-slot="number-field"
id={fieldId}
{...props}
/>
);
}

Expand Down Expand Up @@ -104,53 +94,3 @@ export function NumberFieldInput({
/>
);
}

export function NumberFieldScrubArea({
className,
label,
...props
}: NumberFieldPrimitive.ScrubArea.Props & {
label: string;
}): React.ReactElement {
const context = React.use(NumberFieldContext);

if (!context) {
throw new Error(
"NumberFieldScrubArea must be used within a NumberField component for accessibility.",
);
}

return (
<NumberFieldPrimitive.ScrubArea
className={cn("flex cursor-ew-resize", className)}
data-slot="number-field-scrub-area"
{...props}
>
<Label className="cursor-ew-resize" htmlFor={context.fieldId}>
{label}
</Label>
<NumberFieldPrimitive.ScrubAreaCursor className="drop-shadow-[0_1px_1px_#0008] filter">
<CursorGrowIcon />
</NumberFieldPrimitive.ScrubAreaCursor>
</NumberFieldPrimitive.ScrubArea>
);
}

export function CursorGrowIcon(props: React.ComponentProps<"svg">): React.ReactElement {
return (
<svg
aria-hidden="true"
fill="black"
height="14"
stroke="white"
viewBox="0 0 24 14"
width="26"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path d="M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z" />
</svg>
);
}

export { NumberFieldPrimitive };
Loading
Loading