Skip to content

Commit afd1150

Browse files
committed
packages/ui: Add storybook, Add Badge, Input (#7923)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR introduces several updates focused on enhancing the UI components, improving Storybook integration, and updating dependencies. It also refactors some components for better modularity and performance. ### Detailed summary - Added `class-variance-authority` and updated `tailwindcss-animate` in `knip.json`. - Updated `tailwind.config.ts` to include `.storybook` directory in content. - Refactored imports for `Button` and `Input` components. - Created `Badge` component and its variants in `badge.tsx`. - Updated Storybook configuration in `.storybook/main.ts` and `preview.tsx`. - Refactored `Input` components in multiple locations to use a shared implementation. - Added `Toaster` setup in Storybook preview for better notifications. - Updated dependencies in `package.json` and `pnpm-lock.yaml`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Shared Badge and Input components added to the UI package and made available to apps. - Themed Storybook for the UI library with mobile viewports, notifications, data-fetching context, and an in-app theme toggle. - **Refactor** - Apps now re-export Badge/Input from the shared UI package; story utilities and preview/layout centralized. - **Chores** - Storybook script and dependencies added, Tailwind content updated, and import paths standardized. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent f79672e commit afd1150

File tree

17 files changed

+436
-269
lines changed

17 files changed

+436
-269
lines changed

apps/dashboard/.storybook/preview.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import type { Preview } from "@storybook/nextjs";
2-
import "../src/global.css";
2+
import "@workspace/ui/global.css";
33
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4+
import { Button } from "@workspace/ui/components/button";
45
import { MoonIcon, SunIcon } from "lucide-react";
5-
import { ThemeProvider, useTheme } from "next-themes";
66
import { Inter as interFont } from "next/font/google";
7-
// biome-ignore lint/style/useImportType: <explanation>
8-
import React from "react";
9-
import { useEffect } from "react";
7+
import { ThemeProvider, useTheme } from "next-themes";
8+
// biome-ignore lint/style/useImportType: ok
9+
import React, { useEffect } from "react";
1010
import { Toaster } from "sonner";
11-
import { Button } from "../src/@/components/ui/button";
1211

1312
const queryClient = new QueryClient();
1413

@@ -69,9 +68,7 @@ const preview: Preview = {
6968

7069
export default preview;
7170

72-
function StoryLayout(props: {
73-
children: React.ReactNode;
74-
}) {
71+
function StoryLayout(props: { children: React.ReactNode }) {
7572
const { setTheme, theme } = useTheme();
7673

7774
useEffect(() => {
Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,2 @@
1-
import { cva, type VariantProps } from "class-variance-authority";
2-
import type * as React from "react";
3-
4-
import { cn } from "@/lib/utils";
5-
6-
const badgeVariants = cva(
7-
"inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 leading-4",
8-
{
9-
defaultVariants: {
10-
variant: "default",
11-
},
12-
variants: {
13-
variant: {
14-
default: "border-transparent bg-primary/20 text-primary",
15-
destructive:
16-
"border-transparent dark:bg-red-950 dark:text-red-400 bg-red-500/20 text-red-800",
17-
outline: "text-foreground",
18-
secondary:
19-
"border-transparent bg-accent text-accent-foreground hover:bg-accent/80",
20-
success:
21-
"border-transparent dark:bg-green-950/50 dark:text-green-400 bg-green-200 text-green-950",
22-
warning:
23-
"border-transparent dark:bg-yellow-600/20 dark:text-yellow-500 bg-yellow-500/20 text-yellow-900",
24-
},
25-
},
26-
},
27-
);
28-
29-
export interface BadgeProps
30-
extends React.HTMLAttributes<HTMLDivElement>,
31-
VariantProps<typeof badgeVariants> {}
32-
33-
function Badge({ className, variant, ...props }: BadgeProps) {
34-
return (
35-
<div className={cn(badgeVariants({ variant }), className)} {...props} />
36-
);
37-
}
38-
39-
export { Badge, badgeVariants };
1+
export type { BadgeProps } from "@workspace/ui/components/badge";
2+
export { Badge, badgeVariants } from "@workspace/ui/components/badge";
Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1 @@
1-
import * as React from "react";
2-
3-
import { cn } from "@/lib/utils";
4-
5-
export interface InputProps
6-
extends React.InputHTMLAttributes<HTMLInputElement> {}
7-
8-
const Input = React.forwardRef<HTMLInputElement, InputProps>(
9-
({ className, type, ...props }, ref) => {
10-
return (
11-
<input
12-
className={cn(
13-
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background selection:bg-inverted selection:text-inverted-foreground file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground placeholder:text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
14-
className,
15-
)}
16-
ref={ref}
17-
type={type}
18-
{...props}
19-
/>
20-
);
21-
},
22-
);
23-
Input.displayName = "Input";
24-
25-
export { Input };
1+
export { Input } from "@workspace/ui/components/input";
Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,2 @@
1-
import { cva, type VariantProps } from "class-variance-authority";
2-
import type * as React from "react";
3-
4-
import { cn } from "@/lib/utils";
5-
6-
const badgeVariants = cva(
7-
"inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 leading-4",
8-
{
9-
defaultVariants: {
10-
variant: "default",
11-
},
12-
variants: {
13-
variant: {
14-
default: "border-transparent bg-primary/20 text-primary",
15-
destructive:
16-
"border-transparent dark:bg-red-950 dark:text-red-400 bg-red-500/20 text-red-800",
17-
outline: "text-foreground",
18-
secondary:
19-
"border-transparent bg-accent text-accent-foreground hover:bg-accent/80",
20-
success:
21-
"border-transparent dark:bg-green-950/50 dark:text-green-400 bg-green-200 text-green-950",
22-
warning:
23-
"border-transparent dark:bg-yellow-600/20 dark:text-yellow-500 bg-yellow-500/20 text-yellow-900",
24-
},
25-
},
26-
},
27-
);
28-
29-
export interface BadgeProps
30-
extends React.HTMLAttributes<HTMLDivElement>,
31-
VariantProps<typeof badgeVariants> {}
32-
33-
function Badge({ className, variant, ...props }: BadgeProps) {
34-
return (
35-
<div className={cn(badgeVariants({ variant }), className)} {...props} />
36-
);
37-
}
38-
39-
export { Badge, badgeVariants };
1+
export type { BadgeProps } from "@workspace/ui/components/badge";
2+
export { Badge, badgeVariants } from "@workspace/ui/components/badge";
Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1 @@
1-
import * as React from "react";
2-
3-
import { cn } from "@/lib/utils";
4-
5-
export interface InputProps
6-
extends React.InputHTMLAttributes<HTMLInputElement> {}
7-
8-
const Input = React.forwardRef<HTMLInputElement, InputProps>(
9-
({ className, type, ...props }, ref) => {
10-
return (
11-
<input
12-
className={cn(
13-
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
14-
className,
15-
)}
16-
ref={ref}
17-
type={type}
18-
{...props}
19-
/>
20-
);
21-
},
22-
);
23-
Input.displayName = "Input";
24-
25-
export { Input };
1+
export { Input } from "@workspace/ui/components/input";

apps/portal/knip.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"@workspace/ui",
2121
"@types/flexsearch",
2222
"@radix-ui/react-slot",
23-
"tailwindcss-animate"
23+
"tailwindcss-animate",
24+
"class-variance-authority"
2425
],
2526
"next": true,
2627
"project": ["src/**"]
Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,2 @@
1-
import { cva, type VariantProps } from "class-variance-authority";
2-
import type * as React from "react";
3-
4-
import { cn } from "@/lib/utils";
5-
6-
const badgeVariants = cva(
7-
"inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 leading-4",
8-
{
9-
defaultVariants: {
10-
variant: "default",
11-
},
12-
variants: {
13-
variant: {
14-
default: "border-transparent bg-primary/20 text-primary",
15-
destructive:
16-
"border-transparent dark:bg-red-950 dark:text-red-400 bg-red-500/20 text-red-800",
17-
outline: "text-foreground",
18-
secondary:
19-
"border-transparent bg-accent text-accent-foreground hover:bg-accent/80",
20-
success:
21-
"border-transparent dark:bg-green-950/50 dark:text-green-400 bg-green-200 text-green-950",
22-
warning:
23-
"border-transparent dark:bg-yellow-600/20 dark:text-yellow-500 bg-yellow-500/20 text-yellow-900",
24-
},
25-
},
26-
},
27-
);
28-
29-
export interface BadgeProps
30-
extends React.HTMLAttributes<HTMLDivElement>,
31-
VariantProps<typeof badgeVariants> {}
32-
33-
function Badge({ className, variant, ...props }: BadgeProps) {
34-
return (
35-
<div className={cn(badgeVariants({ variant }), className)} {...props} />
36-
);
37-
}
38-
39-
export { Badge, badgeVariants };
1+
export type { BadgeProps } from "@workspace/ui/components/badge";
2+
export { Badge, badgeVariants } from "@workspace/ui/components/badge";
Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1 @@
1-
import * as React from "react";
2-
3-
import { cn } from "@/lib/utils";
4-
5-
export interface InputProps
6-
extends React.InputHTMLAttributes<HTMLInputElement> {}
7-
8-
const Input = React.forwardRef<HTMLInputElement, InputProps>(
9-
({ className, type, ...props }, ref) => {
10-
return (
11-
<input
12-
className={cn(
13-
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background selection:bg-foreground selection:text-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
14-
className,
15-
)}
16-
ref={ref}
17-
type={type}
18-
{...props}
19-
/>
20-
);
21-
},
22-
);
23-
Input.displayName = "Input";
24-
25-
export { Input };
1+
export { Input } from "@workspace/ui/components/input";

packages/ui/.storybook/main.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { dirname, join } from "node:path";
2+
import type { StorybookConfig } from "@storybook/nextjs";
3+
4+
/**
5+
* This function is used to resolve the absolute path of a package.
6+
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
7+
*/
8+
function getAbsolutePath(value: string): string {
9+
return dirname(require.resolve(join(value, "package.json")));
10+
}
11+
12+
const config: StorybookConfig = {
13+
stories: ["../src/**/*.stories.tsx"],
14+
addons: [
15+
getAbsolutePath("@storybook/addon-onboarding"),
16+
getAbsolutePath("@storybook/addon-links"),
17+
getAbsolutePath("@chromatic-com/storybook"),
18+
getAbsolutePath("@storybook/addon-docs"),
19+
],
20+
framework: {
21+
name: getAbsolutePath("@storybook/nextjs"),
22+
options: {},
23+
},
24+
features: {
25+
experimentalRSC: true,
26+
},
27+
};
28+
export default config;

packages/ui/.storybook/preview.tsx

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type { Preview } from "@storybook/nextjs";
2+
import "../src/global.css";
3+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4+
import { MoonIcon, SunIcon } from "lucide-react";
5+
import { Inter as interFont } from "next/font/google";
6+
import { ThemeProvider, useTheme } from "next-themes";
7+
// biome-ignore lint/style/useImportType: ok
8+
import React, { useEffect } from "react";
9+
import { Toaster } from "sonner";
10+
import { Button } from "../src/components/button";
11+
12+
const queryClient = new QueryClient();
13+
14+
const fontSans = interFont({
15+
display: "swap",
16+
subsets: ["latin"],
17+
variable: "--font-sans",
18+
});
19+
20+
const customViewports = {
21+
xs: {
22+
// Regular sized phones (iphone 15 / 15 pro)
23+
name: "iPhone",
24+
styles: {
25+
width: "390px",
26+
height: "844px",
27+
},
28+
},
29+
sm: {
30+
// Larger phones (iphone 15 plus / 15 pro max)
31+
name: "iPhone Plus",
32+
styles: {
33+
width: "430px",
34+
height: "932px",
35+
},
36+
},
37+
};
38+
39+
const preview: Preview = {
40+
parameters: {
41+
viewport: {
42+
viewports: customViewports,
43+
},
44+
controls: {
45+
matchers: {
46+
color: /(background|color)$/i,
47+
date: /Date$/i,
48+
},
49+
},
50+
},
51+
decorators: [
52+
(Story) => {
53+
return (
54+
<ThemeProvider
55+
attribute="class"
56+
disableTransitionOnChange
57+
enableSystem={false}
58+
defaultTheme="dark"
59+
>
60+
<StoryLayout>
61+
<Story />
62+
</StoryLayout>
63+
</ThemeProvider>
64+
);
65+
},
66+
],
67+
};
68+
69+
export default preview;
70+
71+
function StoryLayout(props: { children: React.ReactNode }) {
72+
const { setTheme, theme } = useTheme();
73+
74+
useEffect(() => {
75+
document.body.className = `font-sans antialiased ${fontSans.variable}`;
76+
}, []);
77+
78+
return (
79+
<QueryClientProvider client={queryClient}>
80+
<div className="flex min-h-dvh min-w-0 flex-col bg-background text-foreground">
81+
<div className="fixed right-0 bottom-0 z-50 flex justify-end gap-2 p-4">
82+
<Button
83+
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
84+
size="sm"
85+
variant="outline"
86+
className="h-auto w-auto shrink-0 rounded-full p-2"
87+
>
88+
{theme === "dark" ? (
89+
<MoonIcon className="size-4" />
90+
) : (
91+
<SunIcon className="size-4" />
92+
)}
93+
</Button>
94+
</div>
95+
96+
<div className="flex min-w-0 grow flex-col">{props.children}</div>
97+
<ToasterSetup />
98+
</div>
99+
</QueryClientProvider>
100+
);
101+
}
102+
103+
function ToasterSetup() {
104+
const { theme } = useTheme();
105+
return <Toaster richColors theme={theme === "light" ? "light" : "dark"} />;
106+
}

0 commit comments

Comments
 (0)