Shadcn Form and Label Components (#1958)

The first step in setting up the connmanager. Adds shadcn components for
form and label in addition to shadcn utils.
This commit is contained in:
Sylvie Crowe 2025-02-13 15:07:08 -08:00 committed by GitHub
parent ef30221e0b
commit 075d3b1302
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 312 additions and 1 deletions

View File

@ -1,9 +1,16 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
//
// This file is based on components from shadcn/ui, which is licensed under the MIT License.
// Original source: https://github.com/shadcn/ui
// Modifications made by Command Line Inc.
"use client";
import * as React from "react";
import * as RechartsPrimitive from "recharts";
import cn from "clsx";
import { cn } from "@/shadcn/lib/utils";
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const;

View File

@ -0,0 +1,151 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
//
// This file is based on components from shadcn/ui, which is licensed under the MIT License.
// Original source: https://github.com/shadcn/ui
// Modifications made by Command Line Inc.
"use client";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form";
import { Label } from "@/shadcn/label";
import * as React from "react";
import { cn } from "@/shadcn/lib/utils";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
}
);
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return <Label ref={ref} className={cn(error && "text-destructive", className)} htmlFor={formItemId} {...props} />;
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
);
}
);
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props}
/>
);
}
);
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
);
}
);
FormMessage.displayName = "FormMessage";
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };

View File

@ -0,0 +1,26 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
//
// This file is based on components from shadcn/ui, which is licensed under the MIT License.
// Original source: https://github.com/shadcn/ui
// Modifications made by Command Line Inc.
"use client";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import cn from "clsx";
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };

View File

@ -0,0 +1,26 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
//
// This file is based on components from shadcn/ui, which is licensed under the MIT License.
// Original source: https://github.com/shadcn/ui
// Modifications made by Command Line Inc.
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function formatDate(input: string | number): string {
const date = new Date(input);
return date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
});
}
export function absoluteUrl(path: string) {
return `${process.env.NEXT_PUBLIC_APP_URL}${path}`;
}

View File

@ -95,6 +95,8 @@
"@monaco-editor/loader": "^1.4.0",
"@monaco-editor/react": "^4.6.0",
"@observablehq/plot": "^0.6.16",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@react-hook/resize-observer": "^2.0.2",
"@table-nav/core": "^0.0.7",
"@table-nav/react": "^0.0.7",
@ -106,6 +108,7 @@
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"base64-js": "^1.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"color": "^4.2.3",
"colord": "^2.9.3",
@ -133,6 +136,7 @@
"react-dom": "^18.3.1",
"react-frame-component": "^5.2.7",
"react-gauge-chart": "^0.5.1",
"react-hook-form": "^7.54.2",
"react-markdown": "^9.0.3",
"react-zoom-pan-pinch": "^3.7.0",
"recharts": "^2.15.1",
@ -147,6 +151,7 @@
"sharp": "^0.33.5",
"shell-quote": "^1.8.2",
"sprintf-js": "^1.1.3",
"tailwind-merge": "^3.0.1",
"throttle-debounce": "^5.0.2",
"tinycolor2": "^1.6.0",
"use-device-pixel-ratio": "^1.1.2",

View File

@ -3911,6 +3911,72 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-compose-refs@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-compose-refs@npm:1.1.1"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/3e84580024e66e3cc5b9ae79355e787815c1d2a3c7d46e7f47900a29c33751ca24cf4ac8903314957ab1f7788aebe1687e2258641c188cf94653f7ddf8f70627
languageName: node
linkType: hard
"@radix-ui/react-label@npm:^2.1.2":
version: 2.1.2
resolution: "@radix-ui/react-label@npm:2.1.2"
dependencies:
"@radix-ui/react-primitive": "npm:2.0.2"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10c0/c425ea25a67f60142645e6dd7669aa90bd9017e8d99c347736c9c19c44cea52e33224e4d086fd7e4945a7e9baa49335d42a5801d3bead884305515023e3ab31c
languageName: node
linkType: hard
"@radix-ui/react-primitive@npm:2.0.2":
version: 2.0.2
resolution: "@radix-ui/react-primitive@npm:2.0.2"
dependencies:
"@radix-ui/react-slot": "npm:1.1.2"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10c0/1af7a33a86f8bd2467f2300b1bb6ca9af67cae3950953ba543d2a625c17f341dff05d19056ece7b03e5ced8b9f8de99c74f806710ce0da6b9a000f2af063fffe
languageName: node
linkType: hard
"@radix-ui/react-slot@npm:1.1.2, @radix-ui/react-slot@npm:^1.1.2":
version: 1.1.2
resolution: "@radix-ui/react-slot@npm:1.1.2"
dependencies:
"@radix-ui/react-compose-refs": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/81d45091806c52b507cec80b4477e4f31189d76ffcd7845b382eb3a034e6cf1faef71b881612028d5893f7580bf9ab59daa18fbf2792042dccd755c99a18df67
languageName: node
linkType: hard
"@react-dnd/asap@npm:^5.0.1":
version: 5.0.2
resolution: "@react-dnd/asap@npm:5.0.2"
@ -8163,6 +8229,15 @@ __metadata:
languageName: node
linkType: hard
"class-variance-authority@npm:^0.7.1":
version: 0.7.1
resolution: "class-variance-authority@npm:0.7.1"
dependencies:
clsx: "npm:^2.1.1"
checksum: 10c0/0f438cea22131808b99272de0fa933c2532d5659773bfec0c583de7b3f038378996d3350683426b8e9c74a6286699382106d71fbec52f0dd5fbb191792cccb5b
languageName: node
linkType: hard
"clean-css@npm:^5.2.2, clean-css@npm:^5.3.2, clean-css@npm:~5.3.2":
version: 5.3.3
resolution: "clean-css@npm:5.3.3"
@ -17400,6 +17475,15 @@ __metadata:
languageName: node
linkType: hard
"react-hook-form@npm:^7.54.2":
version: 7.54.2
resolution: "react-hook-form@npm:7.54.2"
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
checksum: 10c0/6eebead2900e3d369a989e7a20429f390dc75b3897142aa3107f1f6dabb9ae64fed201ea98cdcd8676e40466c97748aeb0c0d83264f5bd3a84dbc0b8e4863415
languageName: node
linkType: hard
"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@ -20096,6 +20180,13 @@ __metadata:
languageName: node
linkType: hard
"tailwind-merge@npm:^3.0.1":
version: 3.0.1
resolution: "tailwind-merge@npm:3.0.1"
checksum: 10c0/973d754687ad82d25cb55bf16958153999d080cd68dd0a6c1c74909bd92d05bcca01c24d9642e9e6ec95b3a207b15cd4b32bca49d586f2e743d311358ecb584b
languageName: node
linkType: hard
"tailwindcss-animate@npm:^1.0.7":
version: 1.0.7
resolution: "tailwindcss-animate@npm:1.0.7"
@ -21616,6 +21707,8 @@ __metadata:
"@monaco-editor/loader": "npm:^1.4.0"
"@monaco-editor/react": "npm:^4.6.0"
"@observablehq/plot": "npm:^0.6.16"
"@radix-ui/react-label": "npm:^2.1.2"
"@radix-ui/react-slot": "npm:^1.1.2"
"@react-hook/resize-observer": "npm:^2.0.2"
"@rollup/plugin-node-resolve": "npm:^16.0.0"
"@storybook/addon-essentials": "npm:^8.5.4"
@ -21657,6 +21750,7 @@ __metadata:
"@xterm/addon-webgl": "npm:^0.18.0"
"@xterm/xterm": "npm:^5.5.0"
base64-js: "npm:^1.5.1"
class-variance-authority: "npm:^0.7.1"
clsx: "npm:^2.1.1"
color: "npm:^4.2.3"
colord: "npm:^2.9.3"
@ -21693,6 +21787,7 @@ __metadata:
react-dom: "npm:^18.3.1"
react-frame-component: "npm:^5.2.7"
react-gauge-chart: "npm:^0.5.1"
react-hook-form: "npm:^7.54.2"
react-markdown: "npm:^9.0.3"
react-zoom-pan-pinch: "npm:^3.7.0"
recharts: "npm:^2.15.1"
@ -21712,6 +21807,7 @@ __metadata:
sprintf-js: "npm:^1.1.3"
storybook: "npm:^8.5.4"
storybook-dark-mode: "npm:^4.0.2"
tailwind-merge: "npm:^3.0.1"
tailwindcss: "npm:^4.0.6"
tailwindcss-animate: "npm:^1.0.7"
throttle-debounce: "npm:^5.0.2"