-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(site): add UI primitives for the AI settings stack #25579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| import type { Meta, StoryObj } from "@storybook/react-vite"; | ||
| import { useFormik } from "formik"; | ||
| import type { FC } from "react"; | ||
| import { expect, within } from "storybook/test"; | ||
| import { FormField } from "./FormField"; | ||
|
|
||
| interface ExampleFormFieldProps { | ||
| id?: string; | ||
| label: string; | ||
| description?: string; | ||
| helperText?: string; | ||
| required?: boolean; | ||
| error?: string; | ||
| value?: string; | ||
| } | ||
|
|
||
| const ExampleFormField: FC<ExampleFormFieldProps> = ({ | ||
| id, | ||
| label, | ||
| description, | ||
| helperText, | ||
| required, | ||
| error, | ||
| value = "", | ||
| }) => { | ||
| const form = useFormik({ | ||
| initialValues: { value }, | ||
| onSubmit: () => {}, | ||
| }); | ||
|
|
||
| return ( | ||
| <FormField | ||
| id={id} | ||
| field={{ | ||
| name: "value", | ||
| id: "value", | ||
| value: form.values.value, | ||
| onChange: form.handleChange, | ||
| onBlur: form.handleBlur, | ||
| error: Boolean(error), | ||
| helperText: error ?? helperText, | ||
| }} | ||
| label={label} | ||
| description={description} | ||
| required={required} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| const meta: Meta<typeof ExampleFormField> = { | ||
| title: "components/FormField", | ||
| component: ExampleFormField, | ||
| args: { | ||
| id: "story-field", | ||
| label: "Provider name", | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof ExampleFormField>; | ||
|
|
||
| export const Default: Story = { | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const input = canvas.getByRole("textbox", { name: /Provider name/ }); | ||
| await expect(input).not.toHaveAttribute("aria-describedby"); | ||
| await expect(input).not.toHaveAttribute("aria-invalid", "true"); | ||
| await expect(canvas.queryByText("*")).not.toBeInTheDocument(); | ||
| }, | ||
| }; | ||
|
|
||
| export const Required: Story = { | ||
| args: { | ||
| required: true, | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| await expect(canvas.getByText("*")).toBeVisible(); | ||
| }, | ||
| }; | ||
|
|
||
| export const WithDescription: Story = { | ||
| args: { | ||
| description: "Shown to users when selecting this provider.", | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const input = canvas.getByRole("textbox", { name: /Provider name/ }); | ||
| await expect(input).toHaveAttribute( | ||
| "aria-describedby", | ||
| "story-field-description", | ||
| ); | ||
| const description = canvas.getByText( | ||
| "Shown to users when selecting this provider.", | ||
| ); | ||
| await expect(description).toHaveAttribute("id", "story-field-description"); | ||
| }, | ||
| }; | ||
|
|
||
| export const WithHelperText: Story = { | ||
| args: { | ||
| helperText: "Lowercase letters and dashes only.", | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const input = canvas.getByRole("textbox", { name: /Provider name/ }); | ||
| await expect(input).toHaveAttribute( | ||
| "aria-describedby", | ||
| "story-field-helper", | ||
| ); | ||
| }, | ||
| }; | ||
|
|
||
| export const WithError: Story = { | ||
| args: { | ||
| error: "Provider name is required.", | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const input = canvas.getByRole("textbox", { name: /Provider name/ }); | ||
| await expect(input).toHaveAttribute( | ||
| "aria-describedby", | ||
| "story-field-error", | ||
| ); | ||
| await expect(input).toHaveAttribute("aria-invalid", "true"); | ||
| await expect(canvas.getByText("Provider name is required.")).toBeVisible(); | ||
| }, | ||
| }; | ||
|
|
||
| export const WithDescriptionAndError: Story = { | ||
| args: { | ||
| description: "Shown to users when selecting this provider.", | ||
| error: "Provider name is required.", | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const input = canvas.getByRole("textbox", { name: /Provider name/ }); | ||
| await expect(input).toHaveAttribute( | ||
| "aria-describedby", | ||
| "story-field-description story-field-error", | ||
| ); | ||
| await expect(input).toHaveAttribute("aria-invalid", "true"); | ||
| }, | ||
| }; | ||
|
|
||
| export const RequiredWithDescription: Story = { | ||
| args: { | ||
| required: true, | ||
| description: "Shown to users when selecting this provider.", | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const input = canvas.getByRole("textbox", { name: /Provider name/ }); | ||
| await expect(canvas.getByText("*")).toBeVisible(); | ||
| await expect(input).toHaveAttribute( | ||
| "aria-describedby", | ||
| "story-field-description", | ||
| ); | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import type { FC, PropsWithChildren, ReactNode } from "react"; | ||
| import type React from "react"; | ||
| import type { FC, ReactNode } from "react"; | ||
| import { cn } from "#/utils/cn"; | ||
|
|
||
| interface PageHeaderProps { | ||
|
|
@@ -31,32 +32,61 @@ export const PageHeader: FC<PageHeaderProps> = ({ | |
| ); | ||
| }; | ||
|
|
||
| export const PageHeaderTitle: FC<PropsWithChildren> = ({ children }) => { | ||
| type PageHeaderTitleProps = React.ComponentPropsWithRef<"h1">; | ||
|
|
||
| export const PageHeaderTitle: FC<PageHeaderTitleProps> = ({ | ||
| children, | ||
| className, | ||
| ...props | ||
| }) => { | ||
| return ( | ||
| <h1 className="text-3xl font-semibold m-0 flex items-center leading-snug"> | ||
| <h1 | ||
| className={cn( | ||
| "text-3xl font-semibold m-0 flex items-center leading-snug", | ||
| className, | ||
| )} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </h1> | ||
| ); | ||
| }; | ||
|
|
||
| interface PageHeaderSubtitleProps { | ||
| children?: ReactNode; | ||
| condensed?: boolean; | ||
| } | ||
| type PageHeaderSubtitleProps = React.ComponentPropsWithRef<"h2">; | ||
|
|
||
| export const PageHeaderSubtitle: FC<PageHeaderSubtitleProps> = ({ | ||
| children, | ||
| className, | ||
| ...props | ||
| }) => { | ||
|
Comment on lines
57
to
61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in the amended commit (force-pushed). Reply from Coder Agents on behalf of Jake Howell. |
||
| return ( | ||
| <h2 className="text-sm text-content-secondary font-normal block m-0 leading-snug"> | ||
| <h2 | ||
| className={cn( | ||
| "text-sm text-content-secondary font-normal block m-0 leading-snug", | ||
| className, | ||
| )} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </h2> | ||
| ); | ||
| }; | ||
|
|
||
| export const PageHeaderCaption: FC<PropsWithChildren> = ({ children }) => { | ||
| type PageHeaderCaptionProps = React.ComponentPropsWithRef<"span">; | ||
|
|
||
| export const PageHeaderCaption: FC<PageHeaderCaptionProps> = ({ | ||
| children, | ||
| className, | ||
| ...props | ||
| }) => { | ||
| return ( | ||
| <span className="text-sm text-content-secondary font-medium uppercase tracking-widest"> | ||
| <span | ||
| className={cn( | ||
| "text-sm text-content-secondary font-medium uppercase tracking-widest", | ||
| className, | ||
| )} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </span> | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extreme nit but is this import necessary?