diff --git a/site/src/@types/storybook.d.ts b/site/src/@types/storybook.d.ts index ccdecd690c9c8..599324a291ae4 100644 --- a/site/src/@types/storybook.d.ts +++ b/site/src/@types/storybook.d.ts @@ -8,6 +8,7 @@ import type { } from "api/typesGenerated"; import type { Permissions } from "modules/permissions"; import type { QueryKey } from "react-query"; +import type { ReactRouterAddonStoryParameters } from "storybook-addon-remix-react-router"; declare module "@storybook/react-vite" { type WebSocketEvent = @@ -24,5 +25,6 @@ declare module "@storybook/react-vite" { permissions?: Partial; deploymentValues?: DeploymentValues; deploymentOptions?: SerpentOption[]; + reactRouter?: ReactRouterAddonStoryParameters; } } diff --git a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx index e7c9fbcce3863..04b214995c814 100644 --- a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx +++ b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx @@ -1,11 +1,10 @@ -import type { Interpolation, Theme } from "@emotion/react"; import IconButton from "@mui/material/IconButton"; import Snackbar, { type SnackbarProps as MuiSnackbarProps, } from "@mui/material/Snackbar"; -import { type ClassName, useClassName } from "hooks/useClassName"; import { X as XIcon } from "lucide-react"; import type { FC } from "react"; +import { cn } from "utils/cn"; type EnterpriseSnackbarVariant = "error" | "info" | "success"; @@ -35,8 +34,6 @@ export const EnterpriseSnackbar: FC = ({ action, ...snackbarProps }) => { - const content = useClassName(classNames.content(variant), [variant]); - return ( = ({ horizontal: "right", }} action={ -
+
{action} - +
} ContentProps={{ ...ContentProps, - className: content, + className: cn( + "rounded-lg bg-surface-secondary text-content-primary shadow", + "py-2 pl-6 pr-4 items-[inherit] border-0 border-l-[4px]", + variantColor(variant), + ), }} onClose={onClose} {...snackbarProps} @@ -67,39 +67,13 @@ export const EnterpriseSnackbar: FC = ({ ); }; -const variantColor = (variant: EnterpriseSnackbarVariant, theme: Theme) => { +const variantColor = (variant: EnterpriseSnackbarVariant) => { switch (variant) { case "error": - return theme.palette.error.main; + return "border-border-destructive"; case "info": - return theme.palette.info.main; + return "border-highlight-sky"; case "success": - return theme.palette.success.main; + return "border-border-success"; } }; - -const classNames = { - content: - (variant: EnterpriseSnackbarVariant): ClassName => - (css, theme) => - css` - border: 1px solid ${theme.palette.divider}; - border-left: 4px solid ${variantColor(variant, theme)}; - border-radius: 8px; - padding: 8px 24px 8px 16px; - box-shadow: ${theme.shadows[6]}; - align-items: inherit; - background-color: ${theme.palette.background.paper}; - color: ${theme.palette.text.secondary}; - `, -}; - -const styles = { - actionWrapper: { - display: "flex", - alignItems: "center", - }, - closeIcon: (theme) => ({ - color: theme.palette.primary.contrastText, - }), -} satisfies Record>; diff --git a/site/src/components/Sidebar/Sidebar.stories.tsx b/site/src/components/Sidebar/Sidebar.stories.tsx index 083bffa423fe4..f352118f5f69e 100644 --- a/site/src/components/Sidebar/Sidebar.stories.tsx +++ b/site/src/components/Sidebar/Sidebar.stories.tsx @@ -7,6 +7,7 @@ import { LockIcon, UserIcon, } from "lucide-react"; +import { Outlet } from "react-router"; import { Sidebar, SidebarHeader, SidebarNavItem } from "./Sidebar"; const meta: Meta = { @@ -18,30 +19,73 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { - children: ( - - } - title="Jon" - subtitle="jon@coder.com" - /> - - Account - - - Schedule - - - Security - - - SSH Keys - - - Tokens - - - ), + decorators: [ + (Story) => { + return ( +
+ + +
+ ); + }, + ], + render: () => ( + + } + title="Jon" + subtitle="jon@coder.com" + /> + + Account + + + Schedule + + + Security + + + SSH Keys + + + Tokens + + + ), + parameters: { + reactRouter: { + location: { + path: "/account", + }, + routing: [ + { + path: "/", + useStoryElement: true, + children: [ + { + path: "account", + element: <>Account page, + }, + { + path: "schedule", + element: <>Schedule page, + }, + { + path: "security", + element: <>Security page, + }, + { + path: "ssh-keys", + element: <>SSH Keys, + }, + { + path: "tokens", + element: <>Tokens page, + }, + ], + }, + ], + }, }, }; diff --git a/site/src/components/Sidebar/Sidebar.tsx b/site/src/components/Sidebar/Sidebar.tsx index 813835baeb277..a09d9bfbaa517 100644 --- a/site/src/components/Sidebar/Sidebar.tsx +++ b/site/src/components/Sidebar/Sidebar.tsx @@ -1,7 +1,4 @@ -import { cx } from "@emotion/css"; -import type { CSSObject, Interpolation, Theme } from "@emotion/react"; import { Stack } from "components/Stack/Stack"; -import { type ClassName, useClassName } from "hooks/useClassName"; import type { ElementType, FC, ReactNode } from "react"; import { Link, NavLink } from "react-router"; import { cn } from "utils/cn"; @@ -21,6 +18,11 @@ interface SidebarHeaderProps { linkTo?: string; } +const titleStyles = { + normal: + "text-semibold overflow-hidden whitespace-nowrap text-content-primary", +}; + export const SidebarHeader: FC = ({ avatar, title, @@ -28,7 +30,7 @@ export const SidebarHeader: FC = ({ linkTo, }) => { return ( - + {avatar}
= ({ }} > {linkTo ? ( - + {title} ) : ( - {title} + {title} )} - {subtitle} + + {subtitle} +
); @@ -88,14 +92,18 @@ export const SidebarNavItem: FC = ({ href, icon: Icon, }) => { - const link = useClassName(classNames.link, []); - const activeLink = useClassName(classNames.activeLink, []); - return ( cx([link, isActive && activeLink])} + className={({ isActive }) => + cn( + "block relative text-sm text-inherit mb-px p-3 pl-4 rounded-sm", + "transition-colors no-underline hover:bg-surface-secondary", + isActive && + "bg-surface-secondary border-0 border-solid border-l-[3px] border-highlight-sky", + ) + } > @@ -104,60 +112,3 @@ export const SidebarNavItem: FC = ({ ); }; - -const styles = { - info: (theme) => ({ - ...(theme.typography.body2 as CSSObject), - marginBottom: 16, - }), - - title: (theme) => ({ - fontWeight: 600, - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap", - color: theme.palette.text.primary, - textDecoration: "none", - }), - subtitle: (theme) => ({ - color: theme.palette.text.secondary, - fontSize: 12, - overflow: "hidden", - textOverflow: "ellipsis", - }), -} satisfies Record>; - -const classNames = { - link: (css, theme) => css` - color: inherit; - display: block; - font-size: 14px; - text-decoration: none; - padding: 12px 12px 12px 16px; - border-radius: 4px; - transition: background-color 0.15s ease-in-out; - margin-bottom: 1px; - position: relative; - - &:hover { - background-color: ${theme.palette.action.hover}; - } - `, - - activeLink: (css, theme) => css` - background-color: ${theme.palette.action.hover}; - - &:before { - content: ""; - display: block; - width: 3px; - height: 100%; - position: absolute; - left: 0; - top: 0; - background-color: ${theme.palette.primary.main}; - border-top-left-radius: 8px; - border-bottom-left-radius: 8px; - } - `, -} satisfies Record; diff --git a/site/src/hooks/useClassName.ts b/site/src/hooks/useClassName.ts index 472e8681a028e..5155d1795a4a5 100644 --- a/site/src/hooks/useClassName.ts +++ b/site/src/hooks/useClassName.ts @@ -5,9 +5,9 @@ import { type DependencyList, useMemo } from "react"; export type ClassName = (cssFn: typeof css, theme: Theme) => string; /** - * An escape hatch for when you really need to manually pass around a - * `className`. Prefer using the `css` prop whenever possible. If you - * can't use that, then this might be helpful for you. + * @deprecated This hook was used as an escape hatch to generate class names + * using emotion when no other styling method would work. There is no valid new + * usage of this hook. Use Tailwind classes instead. */ export function useClassName(styles: ClassName, deps: DependencyList): string { const theme = useTheme(); diff --git a/site/src/pages/HealthPage/HealthLayout.tsx b/site/src/pages/HealthPage/HealthLayout.tsx index e1bdec0973b83..86e79aa9ec69e 100644 --- a/site/src/pages/HealthPage/HealthLayout.tsx +++ b/site/src/pages/HealthPage/HealthLayout.tsx @@ -1,4 +1,3 @@ -import { cx } from "@emotion/css"; import { useTheme } from "@emotion/react"; import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined"; import ReplayIcon from "@mui/icons-material/Replay"; @@ -9,17 +8,26 @@ import { health, refreshHealth } from "api/queries/debug"; import type { HealthSeverity } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; -import { type ClassName, useClassName } from "hooks/useClassName"; import kebabCase from "lodash/fp/kebabCase"; import { DashboardFullPage } from "modules/dashboard/DashboardLayout"; import { type FC, Suspense } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { NavLink, Outlet } from "react-router"; +import { cn } from "utils/cn"; import { createDayString } from "utils/createDayString"; import { pageTitle } from "utils/page"; import { HealthIcon } from "./Content"; +const linkStyles = { + normal: ` + text-content-secondary border-none text-sm w-full flex items-center gap-3 + text-left h-9 px-6 cursor-pointer no-underline transition-colors + hover:bg-surface-secondary hover:text-content-primary + `, + active: "bg-surface-secondary text-content-primary", +}; + export const HealthLayout: FC = () => { const theme = useTheme(); const queryClient = useQueryClient(); @@ -44,9 +52,6 @@ export const HealthLayout: FC = () => { } as const; const visibleSections = filterVisibleSections(sections); - const link = useClassName(classNames.link, []); - const activeLink = useClassName(classNames.activeLink, []); - if (isLoading) { return (
@@ -70,38 +75,11 @@ export const HealthLayout: FC = () => { -
-
-
+
+
+
-
+
@@ -116,20 +94,15 @@ export const HealthLayout: FC = () => { {isRefreshing ? ( ) : ( - + )}
-
+
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
-
+
{healthStatus.healthy ? Object.keys(visibleSections).some((key) => { const section = @@ -142,34 +115,28 @@ export const HealthLayout: FC = () => {
-
- Last check +
+ Last check {createDayString(healthStatus.time)}
-
- Version +
+ Version {healthStatus.coder_version}
-
-
+
}> @@ -229,35 +196,3 @@ const filterVisibleSections = (sections: T) => { return visible; }; - -const classNames = { - link: (css, theme) => - css({ - background: "none", - pointerEvents: "auto", - color: theme.palette.text.secondary, - border: "none", - fontSize: 14, - width: "100%", - display: "flex", - alignItems: "center", - gap: 12, - textAlign: "left", - height: 36, - padding: "0 24px", - cursor: "pointer", - textDecoration: "none", - - "&:hover": { - background: theme.palette.action.hover, - color: theme.palette.text.primary, - }, - }), - - activeLink: (css, theme) => - css({ - background: theme.palette.action.hover, - pointerEvents: "none", - color: theme.palette.text.primary, - }), -} satisfies Record; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy