diff --git a/site/src/components/SettingsHeader/SettingsHeader.stories.tsx b/site/src/components/SettingsHeader/SettingsHeader.stories.tsx new file mode 100644 index 0000000000000..75381d419c4dc --- /dev/null +++ b/site/src/components/SettingsHeader/SettingsHeader.stories.tsx @@ -0,0 +1,83 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { docs } from "utils/docs"; +import { + SettingsHeader, + SettingsHeaderDescription, + SettingsHeaderDocsLink, + SettingsHeaderTitle, +} from "./SettingsHeader"; + +const meta: Meta = { + title: "components/SettingsHeader", + component: SettingsHeader, +}; + +export default meta; +type Story = StoryObj; + +export const PrimaryHeaderOnly: Story = { + args: { + children: This is a header, + }, +}; + +export const PrimaryHeaderWithDescription: Story = { + args: { + children: ( + <> + Another primary header + + This description can be any ReactNode. This provides more options for + composition. + + + ), + }, +}; + +export const PrimaryHeaderWithDescriptionAndDocsLink: Story = { + args: { + children: ( + <> + Another primary header + + This description can be any ReactNode. This provides more options for + composition. + + + ), + actions: , + }, +}; + +export const SecondaryHeaderWithDescription: Story = { + args: { + children: ( + <> + + This is a secondary header. + + + The header's styling is completely independent of its semantics. Both + can be adjusted independently to help avoid invalid HTML. + + + ), + }, +}; + +export const SecondaryHeaderWithDescriptionAndDocsLink: Story = { + args: { + children: ( + <> + + Another secondary header + + + Nothing to add, really. + + + ), + actions: , + }, +}; diff --git a/site/src/components/SettingsHeader/SettingsHeader.tsx b/site/src/components/SettingsHeader/SettingsHeader.tsx index edd06a6957815..b5128bcc28224 100644 --- a/site/src/components/SettingsHeader/SettingsHeader.tsx +++ b/site/src/components/SettingsHeader/SettingsHeader.tsx @@ -1,74 +1,107 @@ -import { useTheme } from "@emotion/react"; +import { type VariantProps, cva } from "class-variance-authority"; import { Button } from "components/Button/Button"; -import { Stack } from "components/Stack/Stack"; import { SquareArrowOutUpRightIcon } from "lucide-react"; -import type { FC, ReactNode } from "react"; +import type { FC, PropsWithChildren, ReactNode } from "react"; +import { cn } from "utils/cn"; -interface HeaderProps { - title: ReactNode; - description?: ReactNode; - secondary?: boolean; - docsHref?: string; - tooltip?: ReactNode; -} - -export const SettingsHeader: FC = ({ - title, - description, - docsHref, - secondary, - tooltip, +type SettingsHeaderProps = Readonly< + PropsWithChildren<{ + actions?: ReactNode; + className?: string; + }> +>; +export const SettingsHeader: FC = ({ + children, + actions, + className, }) => { - const theme = useTheme(); + return ( +
+ {/* + * The text-sm class is only meant to adjust the font size of + * SettingsDescription, but we need to apply it here. That way, + * text-sm combines with the max-w-prose class and makes sure + * we have a predictable max width for the header + description by + * default. + */} +
{children}
+ {actions} +
+ ); +}; +type SettingsHeaderDocsLinkProps = Readonly< + PropsWithChildren<{ href: string }> +>; +export const SettingsHeaderDocsLink: FC = ({ + href, + children = "Read the docs", +}) => { return ( - -
- -

- {title} -

- {tooltip} -
+ + ); +}; - {description && ( - - {description} - - )} -
+const titleVariants = cva("m-0 pb-1 flex items-center gap-2 leading-tight", { + variants: { + hierarchy: { + primary: "text-3xl font-bold", + secondary: "text-2xl font-medium", + }, + }, + defaultVariants: { + hierarchy: "primary", + }, +}); +type SettingsHeaderTitleProps = Readonly< + PropsWithChildren< + VariantProps & { + level?: `h${1 | 2 | 3 | 4 | 5 | 6}`; + tooltip?: ReactNode; + className?: string; + } + > +>; +export const SettingsHeaderTitle: FC = ({ + children, + tooltip, + className, + level = "h1", + hierarchy = "primary", +}) => { + // Explicitly not using Radix's Slot component, because we don't want to + // allow any arbitrary element to be composed into this. We specifically + // only want to allow the six HTML headers. Anything else will likely result + // in invalid markup + const Title = level; + return ( +
+ + {children} + + {tooltip} +
+ ); +}; - {docsHref && ( - - )} -
+type SettingsHeaderDescriptionProps = Readonly< + PropsWithChildren<{ + className?: string; + }> +>; +export const SettingsHeaderDescription: FC = ({ + children, + className, +}) => { + return ( +

+ {children} +

); }; diff --git a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx index 6509153694f6d..4f72c67d02fb3 100644 --- a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx @@ -8,7 +8,11 @@ import { } from "components/Badges/Badges"; import { Button } from "components/Button/Button"; import { PopoverPaywall } from "components/Paywall/PopoverPaywall"; -import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; +import { + SettingsHeader, + SettingsHeaderDescription, + SettingsHeaderTitle, +} from "components/SettingsHeader/SettingsHeader"; import { Popover, PopoverContent, @@ -54,10 +58,12 @@ export const AppearanceSettingsPageView: FC< return ( <> - + + Appearance + + Customize the look and feel of your Coder deployment. + + diff --git a/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx index e64986c5788fc..d2a916823f56e 100644 --- a/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx @@ -8,7 +8,12 @@ import TableRow from "@mui/material/TableRow"; import type { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated"; import { Alert } from "components/Alert/Alert"; import { PremiumBadge } from "components/Badges/Badges"; -import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; +import { + SettingsHeader, + SettingsHeaderDescription, + SettingsHeaderDocsLink, + SettingsHeaderTitle, +} from "components/SettingsHeader/SettingsHeader"; import type { FC } from "react"; import { docs } from "utils/docs"; @@ -22,10 +27,14 @@ export const ExternalAuthSettingsPageView: FC< return ( <> + actions={} + > + External Authentication + + Coder integrates with GitHub, GitLab, BitBucket, Azure Repos, and + OpenID Connect to authenticate developers with external services. + +