@fabrica_communications/design-system 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +54 -0
  2. package/package.json +91 -0
  3. package/src/app/components/page.tsx +19 -0
  4. package/src/app/favicon.ico +0 -0
  5. package/src/app/foundations/colors/page.tsx +11 -0
  6. package/src/app/foundations/page.tsx +11 -0
  7. package/src/app/foundations/typography/page.tsx +11 -0
  8. package/src/app/getting-started/page.tsx +11 -0
  9. package/src/app/globals.css +3 -0
  10. package/src/app/layout.tsx +61 -0
  11. package/src/app/page.tsx +994 -0
  12. package/src/components/shared/gap.tsx +103 -0
  13. package/src/components/shared/page-header.tsx +11 -0
  14. package/src/components/shared/page-top-button.tsx +27 -0
  15. package/src/components/ui/accordion.tsx +66 -0
  16. package/src/components/ui/alert-dialog.tsx +157 -0
  17. package/src/components/ui/aspect-ratio.tsx +11 -0
  18. package/src/components/ui/badge.tsx +46 -0
  19. package/src/components/ui/breadcrumb.tsx +109 -0
  20. package/src/components/ui/button.mdx +39 -0
  21. package/src/components/ui/button.stories.tsx +88 -0
  22. package/src/components/ui/button.tsx +53 -0
  23. package/src/components/ui/card.tsx +92 -0
  24. package/src/components/ui/carousel.tsx +241 -0
  25. package/src/components/ui/checkbox.tsx +32 -0
  26. package/src/components/ui/dialog.tsx +143 -0
  27. package/src/components/ui/field.tsx +248 -0
  28. package/src/components/ui/heading.tsx +51 -0
  29. package/src/components/ui/icon-definitions.ts +532 -0
  30. package/src/components/ui/icon.tsx +115 -0
  31. package/src/components/ui/input-group.tsx +170 -0
  32. package/src/components/ui/input.tsx +21 -0
  33. package/src/components/ui/label.tsx +24 -0
  34. package/src/components/ui/native-select.tsx +48 -0
  35. package/src/components/ui/pagination.tsx +127 -0
  36. package/src/components/ui/radio-group.tsx +45 -0
  37. package/src/components/ui/scroll-area.tsx +58 -0
  38. package/src/components/ui/section-container.tsx +24 -0
  39. package/src/components/ui/select.tsx +187 -0
  40. package/src/components/ui/separator.tsx +28 -0
  41. package/src/components/ui/skeleton.tsx +13 -0
  42. package/src/components/ui/sonner.tsx +40 -0
  43. package/src/components/ui/switch.mdx +20 -0
  44. package/src/components/ui/switch.stories.tsx +54 -0
  45. package/src/components/ui/switch.tsx +66 -0
  46. package/src/components/ui/table.tsx +119 -0
  47. package/src/components/ui/tabs.tsx +66 -0
  48. package/src/components/ui/text-link.tsx +72 -0
  49. package/src/components/ui/text.tsx +54 -0
  50. package/src/components/ui/textarea.tsx +18 -0
  51. package/src/components/ui/toggle.tsx +47 -0
  52. package/src/components/ui/tooltip.tsx +61 -0
  53. package/src/index.ts +37 -0
  54. package/src/lib/utils.ts +6 -0
  55. package/src/styles/globals.css +162 -0
  56. package/src/styles/variables.css +261 -0
  57. package/tokens/design-tokens.tokens.json +1652 -0
  58. package/tokens/design-tokens.tokens/344/270/213/345/261/244.json +1659 -0
  59. package/tokens//345/205/250/343/203/227/343/203/255/343/202/270/343/202/247/343/202/257/343/203/210/345/205/261/351/200/232_design-tokens.tokens.json +572 -0
@@ -0,0 +1,187 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ function Select({
10
+ ...props
11
+ }: React.ComponentProps<typeof SelectPrimitive.Root>) {
12
+ return <SelectPrimitive.Root data-slot="select" {...props} />
13
+ }
14
+
15
+ function SelectGroup({
16
+ ...props
17
+ }: React.ComponentProps<typeof SelectPrimitive.Group>) {
18
+ return <SelectPrimitive.Group data-slot="select-group" {...props} />
19
+ }
20
+
21
+ function SelectValue({
22
+ ...props
23
+ }: React.ComponentProps<typeof SelectPrimitive.Value>) {
24
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />
25
+ }
26
+
27
+ function SelectTrigger({
28
+ className,
29
+ size = "default",
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
33
+ size?: "sm" | "default"
34
+ }) {
35
+ return (
36
+ <SelectPrimitive.Trigger
37
+ data-slot="select-trigger"
38
+ data-size={size}
39
+ className={cn(
40
+ "border-input data-placeholder:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ {children}
46
+ <SelectPrimitive.Icon asChild>
47
+ <ChevronDownIcon className="size-4 opacity-50" />
48
+ </SelectPrimitive.Icon>
49
+ </SelectPrimitive.Trigger>
50
+ )
51
+ }
52
+
53
+ function SelectContent({
54
+ className,
55
+ children,
56
+ position = "popper",
57
+ align = "center",
58
+ ...props
59
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
60
+ return (
61
+ <SelectPrimitive.Portal>
62
+ <SelectPrimitive.Content
63
+ data-slot="select-content"
64
+ className={cn(
65
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
66
+ position === "popper" &&
67
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
68
+ className
69
+ )}
70
+ position={position}
71
+ align={align}
72
+ {...props}
73
+ >
74
+ <SelectScrollUpButton />
75
+ <SelectPrimitive.Viewport
76
+ className={cn(
77
+ "p-1",
78
+ position === "popper" &&
79
+ "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1"
80
+ )}
81
+ >
82
+ {children}
83
+ </SelectPrimitive.Viewport>
84
+ <SelectScrollDownButton />
85
+ </SelectPrimitive.Content>
86
+ </SelectPrimitive.Portal>
87
+ )
88
+ }
89
+
90
+ function SelectLabel({
91
+ className,
92
+ ...props
93
+ }: React.ComponentProps<typeof SelectPrimitive.Label>) {
94
+ return (
95
+ <SelectPrimitive.Label
96
+ data-slot="select-label"
97
+ className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
98
+ {...props}
99
+ />
100
+ )
101
+ }
102
+
103
+ function SelectItem({
104
+ className,
105
+ children,
106
+ ...props
107
+ }: React.ComponentProps<typeof SelectPrimitive.Item>) {
108
+ return (
109
+ <SelectPrimitive.Item
110
+ data-slot="select-item"
111
+ className={cn(
112
+ "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
113
+ className
114
+ )}
115
+ {...props}
116
+ >
117
+ <span className="absolute right-2 flex size-3.5 items-center justify-center">
118
+ <SelectPrimitive.ItemIndicator>
119
+ <CheckIcon className="size-4" />
120
+ </SelectPrimitive.ItemIndicator>
121
+ </span>
122
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
123
+ </SelectPrimitive.Item>
124
+ )
125
+ }
126
+
127
+ function SelectSeparator({
128
+ className,
129
+ ...props
130
+ }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
131
+ return (
132
+ <SelectPrimitive.Separator
133
+ data-slot="select-separator"
134
+ className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
135
+ {...props}
136
+ />
137
+ )
138
+ }
139
+
140
+ function SelectScrollUpButton({
141
+ className,
142
+ ...props
143
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
144
+ return (
145
+ <SelectPrimitive.ScrollUpButton
146
+ data-slot="select-scroll-up-button"
147
+ className={cn(
148
+ "flex cursor-default items-center justify-center py-1",
149
+ className
150
+ )}
151
+ {...props}
152
+ >
153
+ <ChevronUpIcon className="size-4" />
154
+ </SelectPrimitive.ScrollUpButton>
155
+ )
156
+ }
157
+
158
+ function SelectScrollDownButton({
159
+ className,
160
+ ...props
161
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
162
+ return (
163
+ <SelectPrimitive.ScrollDownButton
164
+ data-slot="select-scroll-down-button"
165
+ className={cn(
166
+ "flex cursor-default items-center justify-center py-1",
167
+ className
168
+ )}
169
+ {...props}
170
+ >
171
+ <ChevronDownIcon className="size-4" />
172
+ </SelectPrimitive.ScrollDownButton>
173
+ )
174
+ }
175
+
176
+ export {
177
+ Select,
178
+ SelectContent,
179
+ SelectGroup,
180
+ SelectItem,
181
+ SelectLabel,
182
+ SelectScrollDownButton,
183
+ SelectScrollUpButton,
184
+ SelectSeparator,
185
+ SelectTrigger,
186
+ SelectValue,
187
+ }
@@ -0,0 +1,28 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ function Separator({
9
+ className,
10
+ orientation = "horizontal",
11
+ decorative = true,
12
+ ...props
13
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
14
+ return (
15
+ <SeparatorPrimitive.Root
16
+ data-slot="separator"
17
+ decorative={decorative}
18
+ orientation={orientation}
19
+ className={cn(
20
+ "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ export { Separator }
@@ -0,0 +1,13 @@
1
+ import { cn } from "../../lib/utils"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("bg-accent animate-pulse rounded-md", className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
@@ -0,0 +1,40 @@
1
+ "use client"
2
+
3
+ import {
4
+ CircleCheckIcon,
5
+ InfoIcon,
6
+ Loader2Icon,
7
+ OctagonXIcon,
8
+ TriangleAlertIcon,
9
+ } from "lucide-react"
10
+ import { useTheme } from "next-themes"
11
+ import { Toaster as Sonner, type ToasterProps } from "sonner"
12
+
13
+ const Toaster = ({ ...props }: ToasterProps) => {
14
+ const { theme = "system" } = useTheme()
15
+
16
+ return (
17
+ <Sonner
18
+ theme={theme as ToasterProps["theme"]}
19
+ className="toaster group"
20
+ icons={{
21
+ success: <CircleCheckIcon className="size-4" />,
22
+ info: <InfoIcon className="size-4" />,
23
+ warning: <TriangleAlertIcon className="size-4" />,
24
+ error: <OctagonXIcon className="size-4" />,
25
+ loading: <Loader2Icon className="size-4 animate-spin" />,
26
+ }}
27
+ style={
28
+ {
29
+ "--normal-bg": "var(--popover)",
30
+ "--normal-text": "var(--popover-foreground)",
31
+ "--normal-border": "var(--border)",
32
+ "--border-radius": "var(--radius)",
33
+ } as React.CSSProperties
34
+ }
35
+ {...props}
36
+ />
37
+ )
38
+ }
39
+
40
+ export { Toaster }
@@ -0,0 +1,20 @@
1
+ import { Meta, Canvas, Controls } from "@storybook/addon-docs/blocks";
2
+ import * as SwitchStories from "./switch.stories";
3
+
4
+ <Meta of={SwitchStories} />
5
+
6
+ # Switch
7
+
8
+ オン/オフを切り替えるトグルスイッチです。`label` を渡すと任意のテキストを表示でき、既定では左側に配置されます。
9
+
10
+ ## Examples
11
+
12
+ ### Default
13
+ <Canvas of={SwitchStories.Default} />
14
+ <Controls of={SwitchStories.Default} />
15
+
16
+ ### Checked
17
+ <Canvas of={SwitchStories.Checked} />
18
+
19
+ ### Disabled
20
+ <Canvas of={SwitchStories.Disabled} />
@@ -0,0 +1,54 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite"
2
+
3
+ import { Switch } from "./switch"
4
+
5
+ type SwitchProps = React.ComponentProps<typeof Switch>
6
+
7
+ const meta = {
8
+ title: "UI/Switch",
9
+ component: Switch,
10
+ parameters: {
11
+ layout: "centered",
12
+ },
13
+ argTypes: {
14
+ disabled: {
15
+ control: "boolean",
16
+ },
17
+ defaultChecked: {
18
+ control: "boolean",
19
+ },
20
+ label: {
21
+ control: "text",
22
+ },
23
+ labelPosition: {
24
+ control: "select",
25
+ options: ["start", "end"],
26
+ },
27
+ },
28
+ } satisfies Meta<SwitchProps>
29
+
30
+ export default meta
31
+ type Story = StoryObj<typeof meta>
32
+
33
+ export const Default: Story = {
34
+ args: {
35
+ label: "通知を受け取る",
36
+ labelPosition: "start",
37
+ },
38
+ }
39
+
40
+ export const Checked: Story = {
41
+ args: {
42
+ label: "通知を受け取る",
43
+ labelPosition: "start",
44
+ defaultChecked: true,
45
+ },
46
+ }
47
+
48
+ export const Disabled: Story = {
49
+ args: {
50
+ label: "通知を受け取る",
51
+ labelPosition: "start",
52
+ disabled: true,
53
+ },
54
+ }
@@ -0,0 +1,66 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SwitchPrimitive from "@radix-ui/react-switch"
5
+
6
+ import { Label } from "./label"
7
+ import { cn } from "../../lib/utils"
8
+
9
+ type SwitchProps = React.ComponentProps<typeof SwitchPrimitive.Root> & {
10
+ label?: React.ReactNode
11
+ labelProps?: Omit<React.ComponentProps<typeof Label>, "htmlFor" | "children">
12
+ labelPosition?: "start" | "end"
13
+ }
14
+
15
+ function Switch({
16
+ className,
17
+ id: idProp,
18
+ label,
19
+ labelProps,
20
+ labelPosition = "start",
21
+ ...props
22
+ }: SwitchProps) {
23
+ const autoId = React.useId()
24
+ const id = idProp ?? (label ? autoId : undefined)
25
+
26
+ const switchElement = (
27
+ <SwitchPrimitive.Root
28
+ data-slot="switch"
29
+ id={id}
30
+ className={cn(
31
+ "peer inline-flex h-[20px] w-[36px] shrink-0 items-center rounded-full border-0 transition-all outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-(--usage-active-border) disabled:cursor-not-allowed disabled:opacity-50",
32
+ "data-[state=checked]:bg-(--usage-button-primary) data-[state=unchecked]:bg-(--primitive-neutral-400)",
33
+ className
34
+ )}
35
+ {...props}
36
+ >
37
+ <SwitchPrimitive.Thumb
38
+ data-slot="switch-thumb"
39
+ className={cn(
40
+ "bg-white pointer-events-none block size-[20px] rounded-full ring-0 transition-transform",
41
+ "data-[state=checked]:translate-x-[16px] data-[state=unchecked]:translate-x-0"
42
+ )}
43
+ />
44
+ </SwitchPrimitive.Root>
45
+ )
46
+
47
+ if (!label) {
48
+ return switchElement
49
+ }
50
+
51
+ return (
52
+ <div
53
+ className={cn(
54
+ "flex items-center cursor-pointer gap-(--parts-gap-column-sm)",
55
+ labelPosition === "start" && "flex-row-reverse"
56
+ )}
57
+ >
58
+ {switchElement}
59
+ <Label htmlFor={id} {...labelProps} className="cursor-pointer">
60
+ {label}
61
+ </Label>
62
+ </div>
63
+ )
64
+ }
65
+
66
+ export { Switch }
@@ -0,0 +1,119 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
8
+ return (
9
+ <div
10
+ data-slot="table-container"
11
+ className="relative w-full overflow-x-auto rounded-[var(--usage-table)] border border-(--usage-border) bg-white"
12
+ >
13
+ <table
14
+ data-slot="table"
15
+ className={cn("w-full caption-bottom", className)}
16
+ {...props}
17
+ />
18
+ </div>
19
+ )
20
+ }
21
+
22
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
23
+ return (
24
+ <thead
25
+ data-slot="table-header"
26
+ className={cn(
27
+ "bg-(--usage-table-bg) border-b border-(--usage-border)",
28
+ className
29
+ )}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
36
+ return (
37
+ <tbody
38
+ data-slot="table-body"
39
+ className={cn("[&_tr:last-child]:border-0", className)}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+
45
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
46
+ return (
47
+ <tfoot
48
+ data-slot="table-footer"
49
+ className={cn(
50
+ "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
59
+ return (
60
+ <tr
61
+ data-slot="table-row"
62
+ className={cn(
63
+ "h-[57px] border-b border-(--usage-border) transition-colors hover:bg-(--usage-table-hover) data-[state=selected]:bg-(--usage-table-hover)",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ )
69
+ }
70
+
71
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
72
+ return (
73
+ <th
74
+ data-slot="table-head"
75
+ className={cn(
76
+ "h-[50px] bg-(--usage-table-bg) px-[var(--primitive-spacing-spacing-16)] py-[var(--primitive-spacing-spacing-16)] text-left align-middle font-bold text-[length:var(--font-size-usage-parts-md)] leading-none text-(--body-text) whitespace-nowrap border-r border-(--primitive-neutral-500) [&:last-child]:border-r-0 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
85
+ return (
86
+ <td
87
+ data-slot="table-cell"
88
+ className={cn(
89
+ "min-h-[40px] px-[var(--primitive-spacing-spacing-16)] py-[var(--primitive-spacing-spacing-8)] align-middle text-[length:var(--font-size-usage-parts-md)] leading-[1.4] text-(--body-text) [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
90
+ className
91
+ )}
92
+ {...props}
93
+ />
94
+ )
95
+ }
96
+
97
+ function TableCaption({
98
+ className,
99
+ ...props
100
+ }: React.ComponentProps<"caption">) {
101
+ return (
102
+ <caption
103
+ data-slot="table-caption"
104
+ className={cn("text-muted-foreground mt-4 text-sm", className)}
105
+ {...props}
106
+ />
107
+ )
108
+ }
109
+
110
+ export {
111
+ Table,
112
+ TableHeader,
113
+ TableBody,
114
+ TableFooter,
115
+ TableHead,
116
+ TableRow,
117
+ TableCell,
118
+ TableCaption,
119
+ }
@@ -0,0 +1,66 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ function Tabs({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) {
12
+ return (
13
+ <TabsPrimitive.Root
14
+ data-slot="tabs"
15
+ className={cn("flex flex-col gap-2", className)}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function TabsList({
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<typeof TabsPrimitive.List>) {
25
+ return (
26
+ <TabsPrimitive.List
27
+ data-slot="tabs-list"
28
+ className={cn(
29
+ "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function TabsTrigger({
38
+ className,
39
+ ...props
40
+ }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
41
+ return (
42
+ <TabsPrimitive.Trigger
43
+ data-slot="tabs-trigger"
44
+ className={cn(
45
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ function TabsContent({
54
+ className,
55
+ ...props
56
+ }: React.ComponentProps<typeof TabsPrimitive.Content>) {
57
+ return (
58
+ <TabsPrimitive.Content
59
+ data-slot="tabs-content"
60
+ className={cn("flex-1 outline-none", className)}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,72 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import NextLink from "next/link"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ import { Icon, type IconName } from "./icon"
10
+
11
+ const linkVariants = cva(
12
+ "inline-flex items-center gap-[var(--primitive-spacing-spacing-4)] text-[var(--text-link)] font-medium transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--usage-active-border)]",
13
+ {
14
+ variants: {
15
+ border: {
16
+ none: "",
17
+ underline:
18
+ "border-b border-[var(--text-link)] pb-[calc(var(--primitive-spacing-spacing-2)/2)]",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ border: "none",
23
+ },
24
+ },
25
+ )
26
+
27
+ type TextLinkVariants = VariantProps<typeof linkVariants>
28
+
29
+ type LinkBaseProps = React.ComponentPropsWithoutRef<typeof NextLink>
30
+
31
+ type LinkProps = LinkBaseProps &
32
+ TextLinkVariants & {
33
+ icon?: IconName
34
+ iconPosition?: "left" | "right"
35
+ }
36
+
37
+ const TextLink = React.forwardRef<HTMLAnchorElement, LinkProps>(
38
+ (
39
+ {
40
+ className,
41
+ border,
42
+ icon,
43
+ iconPosition = "left",
44
+ children,
45
+ ...props
46
+ },
47
+ ref,
48
+ ) => {
49
+ const iconElement = icon ? (
50
+ <Icon
51
+ name={icon}
52
+ aria-hidden="true"
53
+ width="1em"
54
+ height="1em"
55
+ className="shrink-0"
56
+ />
57
+ ) : null
58
+
59
+ return (
60
+ <NextLink ref={ref} className={cn(linkVariants({ border }), className)} {...props}>
61
+ {iconElement && iconPosition === "left" && iconElement}
62
+ <span className="inline-flex items-center">{children}</span>
63
+ {iconElement && iconPosition === "right" && iconElement}
64
+ </NextLink>
65
+ )
66
+ },
67
+ )
68
+
69
+ TextLink.displayName = "TextLink"
70
+
71
+ export { TextLink, linkVariants }
72
+