@trycompai/design-system 1.0.0 → 1.0.2

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 (101) hide show
  1. package/package.json +6 -3
  2. package/src/components/atoms/badge.tsx +49 -0
  3. package/src/components/{ui → atoms}/button.tsx +6 -1
  4. package/src/components/{ui → atoms}/checkbox.tsx +3 -3
  5. package/src/components/{ui → atoms}/heading.tsx +6 -6
  6. package/src/components/atoms/index.ts +21 -0
  7. package/src/components/atoms/kbd.tsx +21 -0
  8. package/src/components/atoms/logo.tsx +52 -0
  9. package/src/components/{ui → atoms}/slider.tsx +4 -4
  10. package/src/components/{ui → atoms}/spinner.tsx +3 -3
  11. package/src/components/atoms/stack.tsx +97 -0
  12. package/src/components/{ui → atoms}/switch.tsx +1 -1
  13. package/src/components/{ui → atoms}/text.tsx +5 -1
  14. package/src/components/{ui → atoms}/textarea.tsx +8 -2
  15. package/src/components/{ui → atoms}/toggle.tsx +3 -6
  16. package/src/components/{ui → molecules}/accordion.tsx +3 -3
  17. package/src/components/molecules/ai-chat.tsx +217 -0
  18. package/src/components/{ui → molecules}/alert.tsx +5 -5
  19. package/src/components/{ui → molecules}/breadcrumb.tsx +9 -8
  20. package/src/components/{ui → molecules}/card.tsx +24 -5
  21. package/src/components/molecules/command-search.tsx +147 -0
  22. package/src/components/molecules/empty.tsx +82 -0
  23. package/src/components/{ui → molecules}/field.tsx +16 -37
  24. package/src/components/{ui → molecules}/hover-card.tsx +2 -8
  25. package/src/components/molecules/index.ts +29 -0
  26. package/src/components/{ui → molecules}/input-group.tsx +1 -1
  27. package/src/components/molecules/input-otp.tsx +70 -0
  28. package/src/components/{ui → molecules}/item.tsx +18 -36
  29. package/src/components/molecules/page-header.tsx +80 -0
  30. package/src/components/{ui → molecules}/pagination.tsx +14 -23
  31. package/src/components/{ui → molecules}/popover.tsx +4 -2
  32. package/src/components/molecules/radio-group.tsx +33 -0
  33. package/src/components/{ui → molecules}/scroll-area.tsx +8 -11
  34. package/src/components/{ui → molecules}/section.tsx +3 -3
  35. package/src/components/{ui → molecules}/select.tsx +22 -10
  36. package/src/components/molecules/settings.tsx +169 -0
  37. package/src/components/{ui → molecules}/table.tsx +16 -3
  38. package/src/components/molecules/tabs.tsx +70 -0
  39. package/src/components/molecules/theme-switcher.tsx +176 -0
  40. package/src/components/{ui → molecules}/toggle-group.tsx +1 -1
  41. package/src/components/organisms/alert-dialog.tsx +135 -0
  42. package/src/components/organisms/app-shell.tsx +822 -0
  43. package/src/components/{ui → organisms}/calendar.tsx +6 -7
  44. package/src/components/{ui → organisms}/carousel.tsx +9 -11
  45. package/src/components/{ui → organisms}/chart.tsx +9 -24
  46. package/src/components/{ui → organisms}/combobox.tsx +7 -7
  47. package/src/components/{ui → organisms}/command.tsx +3 -3
  48. package/src/components/{ui → organisms}/context-menu.tsx +23 -53
  49. package/src/components/{ui → organisms}/dialog.tsx +3 -3
  50. package/src/components/{ui → organisms}/dropdown-menu.tsx +8 -6
  51. package/src/components/organisms/index.ts +17 -0
  52. package/src/components/{ui → organisms}/menubar.tsx +3 -3
  53. package/src/components/organisms/navigation-menu.tsx +137 -0
  54. package/src/components/organisms/page-layout.tsx +95 -0
  55. package/src/components/{ui → organisms}/sheet.tsx +7 -7
  56. package/src/components/{ui → organisms}/sidebar.tsx +61 -86
  57. package/src/components/organisms/sonner.tsx +41 -0
  58. package/src/components/ui/index.ts +3 -61
  59. package/src/fonts/TWKLausanne/TWKLausanne-300-Italic.woff +0 -0
  60. package/src/fonts/TWKLausanne/TWKLausanne-300-Italic.woff2 +0 -0
  61. package/src/fonts/TWKLausanne/TWKLausanne-300.woff +0 -0
  62. package/src/fonts/TWKLausanne/TWKLausanne-300.woff2 +0 -0
  63. package/src/fonts/TWKLausanne/TWKLausanne-350-Italic.woff +0 -0
  64. package/src/fonts/TWKLausanne/TWKLausanne-350-Italic.woff2 +0 -0
  65. package/src/fonts/TWKLausanne/TWKLausanne-350.woff +0 -0
  66. package/src/fonts/TWKLausanne/TWKLausanne-350.woff2 +0 -0
  67. package/src/fonts/TWKLausanne/TWKLausanne-400-Italic.woff +0 -0
  68. package/src/fonts/TWKLausanne/TWKLausanne-400-Italic.woff2 +0 -0
  69. package/src/fonts/TWKLausanne/TWKLausanne-400.woff +0 -0
  70. package/src/fonts/TWKLausanne/TWKLausanne-400.woff2 +0 -0
  71. package/src/fonts/TWKLausanne/TWKLausanne-700-Italic.woff +0 -0
  72. package/src/fonts/TWKLausanne/TWKLausanne-700-Italic.woff2 +0 -0
  73. package/src/fonts/TWKLausanne/TWKLausanne-700.woff +0 -0
  74. package/src/fonts/TWKLausanne/TWKLausanne-700.woff2 +0 -0
  75. package/src/styles/globals.css +167 -23
  76. package/src/components/ui/alert-dialog.tsx +0 -161
  77. package/src/components/ui/badge.tsx +0 -48
  78. package/src/components/ui/empty.tsx +0 -94
  79. package/src/components/ui/input-otp.tsx +0 -84
  80. package/src/components/ui/kbd.tsx +0 -26
  81. package/src/components/ui/navigation-menu.tsx +0 -147
  82. package/src/components/ui/page-header.tsx +0 -51
  83. package/src/components/ui/page-layout.tsx +0 -65
  84. package/src/components/ui/radio-group.tsx +0 -37
  85. package/src/components/ui/sonner.tsx +0 -43
  86. package/src/components/ui/stack.tsx +0 -72
  87. package/src/components/ui/tabs.tsx +0 -69
  88. /package/src/components/{ui → atoms}/aspect-ratio.tsx +0 -0
  89. /package/src/components/{ui → atoms}/avatar.tsx +0 -0
  90. /package/src/components/{ui → atoms}/container.tsx +0 -0
  91. /package/src/components/{ui → atoms}/input.tsx +0 -0
  92. /package/src/components/{ui → atoms}/label.tsx +0 -0
  93. /package/src/components/{ui → atoms}/progress.tsx +0 -0
  94. /package/src/components/{ui → atoms}/separator.tsx +0 -0
  95. /package/src/components/{ui → atoms}/skeleton.tsx +0 -0
  96. /package/src/components/{ui → molecules}/button-group.tsx +0 -0
  97. /package/src/components/{ui → molecules}/collapsible.tsx +0 -0
  98. /package/src/components/{ui → molecules}/grid.tsx +0 -0
  99. /package/src/components/{ui → molecules}/resizable.tsx +0 -0
  100. /package/src/components/{ui → molecules}/tooltip.tsx +0 -0
  101. /package/src/components/{ui → organisms}/drawer.tsx +0 -0
@@ -4,47 +4,36 @@ import { cva, type VariantProps } from 'class-variance-authority';
4
4
  import { useMemo } from 'react';
5
5
 
6
6
  import { Separator as SeparatorPrimitive } from '@base-ui/react/separator';
7
- import { cn } from '../../../lib/utils';
8
7
 
9
- function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
8
+ function FieldSet({ ...props }: Omit<React.ComponentProps<'fieldset'>, 'className'>) {
10
9
  return (
11
10
  <fieldset
12
11
  data-slot="field-set"
13
- className={cn(
14
- 'gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col',
15
- className,
16
- )}
12
+ className="gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col"
17
13
  {...props}
18
14
  />
19
15
  );
20
16
  }
21
17
 
22
18
  function FieldLegend({
23
- className,
24
19
  variant = 'legend',
25
20
  ...props
26
- }: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
21
+ }: Omit<React.ComponentProps<'legend'>, 'className'> & { variant?: 'legend' | 'label' }) {
27
22
  return (
28
23
  <legend
29
24
  data-slot="field-legend"
30
25
  data-variant={variant}
31
- className={cn(
32
- 'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',
33
- className,
34
- )}
26
+ className="mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base"
35
27
  {...props}
36
28
  />
37
29
  );
38
30
  }
39
31
 
40
- function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
32
+ function FieldGroup({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
41
33
  return (
42
34
  <div
43
35
  data-slot="field-group"
44
- className={cn(
45
- 'gap-5 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col',
46
- className,
47
- )}
36
+ className="gap-5 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col"
48
37
  {...props}
49
38
  />
50
39
  );
@@ -66,26 +55,25 @@ const fieldVariants = cva('data-[invalid=true]:text-destructive gap-2 group/fiel
66
55
  });
67
56
 
68
57
  function Field({
69
- className,
70
58
  orientation = 'vertical',
71
59
  ...props
72
- }: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
60
+ }: Omit<React.ComponentProps<'div'>, 'className'> & VariantProps<typeof fieldVariants>) {
73
61
  return (
74
62
  <div
75
63
  role="group"
76
64
  data-slot="field"
77
65
  data-orientation={orientation}
78
- className={cn(fieldVariants({ orientation }), className)}
66
+ className={fieldVariants({ orientation })}
79
67
  {...props}
80
68
  />
81
69
  );
82
70
  }
83
71
 
84
- function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
72
+ function FieldContent({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
85
73
  return (
86
74
  <div
87
75
  data-slot="field-content"
88
- className={cn('gap-0.5 group/field-content flex flex-1 flex-col leading-snug', className)}
76
+ className="gap-0.5 group/field-content flex flex-1 flex-col leading-snug"
89
77
  {...props}
90
78
  />
91
79
  );
@@ -101,29 +89,21 @@ function FieldLabel({ ...props }: Omit<React.ComponentProps<'label'>, 'className
101
89
  );
102
90
  }
103
91
 
104
- function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
92
+ function FieldTitle({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
105
93
  return (
106
94
  <div
107
95
  data-slot="field-label"
108
- className={cn(
109
- 'gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug',
110
- className,
111
- )}
96
+ className="gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug"
112
97
  {...props}
113
98
  />
114
99
  );
115
100
  }
116
101
 
117
- function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
102
+ function FieldDescription({ ...props }: Omit<React.ComponentProps<'p'>, 'className'>) {
118
103
  return (
119
104
  <p
120
105
  data-slot="field-description"
121
- className={cn(
122
- 'text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
123
- 'last:mt-0 nth-last-2:-mt-1',
124
- '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
125
- className,
126
- )}
106
+ className="text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance last:mt-0 nth-last-2:-mt-1 [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4"
127
107
  {...props}
128
108
  />
129
109
  );
@@ -156,11 +136,10 @@ function FieldSeparator({
156
136
  }
157
137
 
158
138
  function FieldError({
159
- className,
160
139
  children,
161
140
  errors,
162
141
  ...props
163
- }: React.ComponentProps<'div'> & {
142
+ }: Omit<React.ComponentProps<'div'>, 'className'> & {
164
143
  errors?: Array<{ message?: string } | undefined>;
165
144
  }) {
166
145
  const content = useMemo(() => {
@@ -193,7 +172,7 @@ function FieldError({
193
172
  <div
194
173
  role="alert"
195
174
  data-slot="field-error"
196
- className={cn('text-destructive text-sm font-normal', className)}
175
+ className="text-destructive text-sm font-normal"
197
176
  {...props}
198
177
  >
199
178
  {content}
@@ -2,8 +2,6 @@
2
2
 
3
3
  import { PreviewCard as PreviewCardPrimitive } from '@base-ui/react/preview-card';
4
4
 
5
- import { cn } from '../../../lib/utils';
6
-
7
5
  function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
8
6
  return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />;
9
7
  }
@@ -13,13 +11,12 @@ function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
13
11
  }
14
12
 
15
13
  function HoverCardContent({
16
- className,
17
14
  side = 'bottom',
18
15
  sideOffset = 4,
19
16
  align = 'center',
20
17
  alignOffset = 4,
21
18
  ...props
22
- }: PreviewCardPrimitive.Popup.Props &
19
+ }: Omit<PreviewCardPrimitive.Popup.Props, 'className'> &
23
20
  Pick<PreviewCardPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'side' | 'sideOffset'>) {
24
21
  return (
25
22
  <PreviewCardPrimitive.Portal data-slot="hover-card-portal">
@@ -32,10 +29,7 @@ function HoverCardContent({
32
29
  >
33
30
  <PreviewCardPrimitive.Popup
34
31
  data-slot="hover-card-content"
35
- className={cn(
36
- 'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-lg p-4 text-sm shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden',
37
- className,
38
- )}
32
+ className="data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-lg p-4 text-sm shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden"
39
33
  {...props}
40
34
  />
41
35
  </PreviewCardPrimitive.Positioner>
@@ -0,0 +1,29 @@
1
+ export * from './accordion';
2
+ export * from './ai-chat';
3
+ export * from './alert';
4
+ export * from './breadcrumb';
5
+ export * from './button-group';
6
+ export * from './card';
7
+ export * from './collapsible';
8
+ export * from './command-search';
9
+ export * from './empty';
10
+ export * from './field';
11
+ export * from './grid';
12
+ export * from './hover-card';
13
+ export * from './input-group';
14
+ export * from './input-otp';
15
+ export * from './item';
16
+ export * from './page-header';
17
+ export * from './pagination';
18
+ export * from './popover';
19
+ export * from './radio-group';
20
+ export * from './resizable';
21
+ export * from './scroll-area';
22
+ export * from './section';
23
+ export * from './select';
24
+ export * from './settings';
25
+ export * from './table';
26
+ export * from './tabs';
27
+ export * from './theme-switcher';
28
+ export * from './toggle-group';
29
+ export * from './tooltip';
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
4
4
  import * as React from 'react';
5
5
 
6
6
  import { cn } from '../../../lib/utils';
7
- import { buttonVariants } from './button';
7
+ import { buttonVariants } from '../atoms/button';
8
8
 
9
9
  function InputGroup({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
10
10
  return (
@@ -0,0 +1,70 @@
1
+ import { OTPInput, OTPInputContext } from 'input-otp';
2
+ import * as React from 'react';
3
+
4
+ import { Subtract } from '@carbon/icons-react';
5
+
6
+ function InputOTP({
7
+ className: _className,
8
+ ...props
9
+ }: React.ComponentProps<typeof OTPInput>) {
10
+ return (
11
+ <OTPInput
12
+ data-slot="input-otp"
13
+ containerClassName="cn-input-otp flex items-center has-disabled:opacity-50"
14
+ spellCheck={false}
15
+ className="disabled:cursor-not-allowed"
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function InputOTPGroup({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
22
+ return (
23
+ <div
24
+ data-slot="input-otp-group"
25
+ className="has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive rounded-md has-aria-invalid:ring-[3px] flex items-center"
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function InputOTPSlot({
32
+ index,
33
+ ...props
34
+ }: Omit<React.ComponentProps<'div'>, 'className'> & {
35
+ index: number;
36
+ }) {
37
+ const inputOTPContext = React.useContext(OTPInputContext);
38
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
39
+
40
+ return (
41
+ <div
42
+ data-slot="input-otp-slot"
43
+ data-active={isActive}
44
+ className="dark:bg-input/30 border-input data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive size-9 border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:ring-[3px] relative flex items-center justify-center data-[active=true]:z-10"
45
+ {...props}
46
+ >
47
+ {char}
48
+ {hasFakeCaret && (
49
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
50
+ <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000 bg-foreground h-4 w-px" />
51
+ </div>
52
+ )}
53
+ </div>
54
+ );
55
+ }
56
+
57
+ function InputOTPSeparator({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
58
+ return (
59
+ <div
60
+ data-slot="input-otp-separator"
61
+ className="[&_svg:not([class*='size-'])]:size-4 flex items-center"
62
+ role="separator"
63
+ {...props}
64
+ >
65
+ <Subtract />
66
+ </div>
67
+ );
68
+ }
69
+
70
+ export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };
@@ -1,20 +1,15 @@
1
1
  import { mergeProps } from '@base-ui/react/merge-props';
2
2
  import { useRender } from '@base-ui/react/use-render';
3
3
  import { cva, type VariantProps } from 'class-variance-authority';
4
- import * as React from 'react';
5
4
 
6
5
  import { Separator as SeparatorPrimitive } from '@base-ui/react/separator';
7
- import { cn } from '../../../lib/utils';
8
6
 
9
- function ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {
7
+ function ItemGroup({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
10
8
  return (
11
9
  <div
12
10
  role="list"
13
11
  data-slot="item-group"
14
- className={cn(
15
- 'gap-4 has-[[data-size=sm]]:gap-2.5 has-[[data-size=xs]]:gap-2 group/item-group flex w-full flex-col',
16
- className,
17
- )}
12
+ className="gap-4 has-[[data-size=sm]]:gap-2.5 has-[[data-size=xs]]:gap-2 group/item-group flex w-full flex-col"
18
13
  {...props}
19
14
  />
20
15
  );
@@ -54,17 +49,16 @@ const itemVariants = cva(
54
49
  );
55
50
 
56
51
  function Item({
57
- className,
58
52
  variant = 'default',
59
53
  size = 'default',
60
54
  render,
61
55
  ...props
62
- }: useRender.ComponentProps<'div'> & VariantProps<typeof itemVariants>) {
56
+ }: Omit<useRender.ComponentProps<'div'>, 'className'> & VariantProps<typeof itemVariants>) {
63
57
  return useRender({
64
58
  defaultTagName: 'div',
65
59
  props: mergeProps<'div'>(
66
60
  {
67
- className: cn(itemVariants({ variant, size, className })),
61
+ className: itemVariants({ variant, size }),
68
62
  },
69
63
  props,
70
64
  ),
@@ -95,80 +89,68 @@ const itemMediaVariants = cva(
95
89
  );
96
90
 
97
91
  function ItemMedia({
98
- className,
99
92
  variant = 'default',
100
93
  ...props
101
- }: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) {
94
+ }: Omit<React.ComponentProps<'div'>, 'className'> & VariantProps<typeof itemMediaVariants>) {
102
95
  return (
103
96
  <div
104
97
  data-slot="item-media"
105
98
  data-variant={variant}
106
- className={cn(itemMediaVariants({ variant, className }))}
99
+ className={itemMediaVariants({ variant })}
107
100
  {...props}
108
101
  />
109
102
  );
110
103
  }
111
104
 
112
- function ItemContent({ className, ...props }: React.ComponentProps<'div'>) {
105
+ function ItemContent({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
113
106
  return (
114
107
  <div
115
108
  data-slot="item-content"
116
- className={cn(
117
- 'gap-1 group-data-[size=xs]/item:gap-0 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none',
118
- className,
119
- )}
109
+ className="gap-1 group-data-[size=xs]/item:gap-0 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none"
120
110
  {...props}
121
111
  />
122
112
  );
123
113
  }
124
114
 
125
- function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {
115
+ function ItemTitle({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
126
116
  return (
127
117
  <div
128
118
  data-slot="item-title"
129
- className={cn(
130
- 'gap-2 text-sm leading-snug font-medium underline-offset-4 line-clamp-1 flex w-fit items-center',
131
- className,
132
- )}
119
+ className="gap-2 text-sm leading-snug font-medium underline-offset-4 line-clamp-1 flex w-fit items-center"
133
120
  {...props}
134
121
  />
135
122
  );
136
123
  }
137
124
 
138
- function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {
125
+ function ItemDescription({ ...props }: Omit<React.ComponentProps<'p'>, 'className'>) {
139
126
  return (
140
127
  <p
141
128
  data-slot="item-description"
142
- className={cn(
143
- 'text-muted-foreground text-left text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4',
144
- className,
145
- )}
129
+ className="text-muted-foreground text-left text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4"
146
130
  {...props}
147
131
  />
148
132
  );
149
133
  }
150
134
 
151
- function ItemActions({ className, ...props }: React.ComponentProps<'div'>) {
152
- return (
153
- <div data-slot="item-actions" className={cn('gap-2 flex items-center', className)} {...props} />
154
- );
135
+ function ItemActions({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
136
+ return <div data-slot="item-actions" className="gap-2 flex items-center" {...props} />;
155
137
  }
156
138
 
157
- function ItemHeader({ className, ...props }: React.ComponentProps<'div'>) {
139
+ function ItemHeader({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
158
140
  return (
159
141
  <div
160
142
  data-slot="item-header"
161
- className={cn('gap-2 flex basis-full items-center justify-between', className)}
143
+ className="gap-2 flex basis-full items-center justify-between"
162
144
  {...props}
163
145
  />
164
146
  );
165
147
  }
166
148
 
167
- function ItemFooter({ className, ...props }: React.ComponentProps<'div'>) {
149
+ function ItemFooter({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
168
150
  return (
169
151
  <div
170
152
  data-slot="item-footer"
171
- className={cn('gap-2 flex basis-full items-center justify-between', className)}
153
+ className="gap-2 flex basis-full items-center justify-between"
172
154
  {...props}
173
155
  />
174
156
  );
@@ -0,0 +1,80 @@
1
+ import * as React from 'react';
2
+
3
+ import { Heading } from '../atoms/heading';
4
+ import { Text } from '../atoms/text';
5
+ import { Stack } from '../atoms/stack';
6
+
7
+ interface PageHeaderProps extends Omit<React.ComponentProps<'div'>, 'className'> {
8
+ title: string;
9
+ description?: string;
10
+ /** Additional descriptive text below description */
11
+ meta?: string;
12
+ actions?: React.ReactNode;
13
+ }
14
+
15
+ function PageHeader({ title, description, meta, actions, children, ...props }: PageHeaderProps) {
16
+ const childArray = React.Children.toArray(children);
17
+ const extractedActionChildren: React.ReactNode[] = [];
18
+ const nonActionChildren: React.ReactNode[] = [];
19
+
20
+ childArray.forEach((child) => {
21
+ if (
22
+ React.isValidElement(child) &&
23
+ (child.type === PageHeaderActions ||
24
+ (typeof child.type === 'function' &&
25
+ (child.type as unknown as { __pageHeaderSlot?: string }).__pageHeaderSlot === 'actions'))
26
+ ) {
27
+ extractedActionChildren.push((child.props as { children?: React.ReactNode }).children);
28
+ return;
29
+ }
30
+ nonActionChildren.push(child);
31
+ });
32
+
33
+ const resolvedActions =
34
+ actions ??
35
+ (extractedActionChildren.length > 0 ? extractedActionChildren : undefined);
36
+
37
+ return (
38
+ <div data-slot="page-header" className="flex items-start justify-between gap-4" {...props}>
39
+ <Stack gap="1">
40
+ <Heading level="1">{title}</Heading>
41
+ {description && (
42
+ <Text size="sm" variant="muted">
43
+ {description}
44
+ </Text>
45
+ )}
46
+ {meta && (
47
+ <Text size="xs" variant="muted">
48
+ {meta}
49
+ </Text>
50
+ )}
51
+ {nonActionChildren}
52
+ </Stack>
53
+ {resolvedActions &&
54
+ (React.isValidElement(resolvedActions) && resolvedActions.type === PageHeaderActions ? (
55
+ resolvedActions
56
+ ) : (
57
+ <PageHeaderActions>{resolvedActions}</PageHeaderActions>
58
+ ))}
59
+ </div>
60
+ );
61
+ }
62
+
63
+ function PageHeaderTitle({ ...props }: Omit<React.ComponentProps<typeof Heading>, 'className'>) {
64
+ return <Heading data-slot="page-header-title" level="1" {...props} />;
65
+ }
66
+
67
+ function PageHeaderDescription({ ...props }: Omit<React.ComponentProps<typeof Text>, 'className'>) {
68
+ return <Text data-slot="page-header-description" size="sm" variant="muted" {...props} />;
69
+ }
70
+
71
+ function PageHeaderActions({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
72
+ return (
73
+ <div data-slot="page-header-actions" className="flex shrink-0 items-center gap-3" {...props} />
74
+ );
75
+ }
76
+
77
+ // Mark compound slots so PageHeader can detect them even if module instances differ.
78
+ (PageHeaderActions as unknown as { __pageHeaderSlot?: string }).__pageHeaderSlot = 'actions';
79
+
80
+ export { PageHeader, PageHeaderActions, PageHeaderDescription, PageHeaderTitle };
@@ -1,33 +1,27 @@
1
1
  import { Button as ButtonPrimitive } from '@base-ui/react/button';
2
- import * as React from 'react';
3
2
 
4
- import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react';
5
- import { cn } from '../../../lib/utils';
6
- import { buttonVariants } from './button';
3
+ import { ChevronLeft, ChevronRight, OverflowMenuHorizontal } from '@carbon/icons-react';
4
+ import { buttonVariants } from '../atoms/button';
7
5
 
8
- function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
6
+ function Pagination({ ...props }: Omit<React.ComponentProps<'nav'>, 'className'>) {
9
7
  return (
10
8
  <nav
11
9
  role="navigation"
12
10
  aria-label="pagination"
13
11
  data-slot="pagination"
14
- className={cn('mx-auto flex w-full justify-center', className)}
12
+ className="mx-auto flex w-full justify-center"
15
13
  {...props}
16
14
  />
17
15
  );
18
16
  }
19
17
 
20
- function PaginationContent({ className, ...props }: React.ComponentProps<'ul'>) {
18
+ function PaginationContent({ ...props }: Omit<React.ComponentProps<'ul'>, 'className'>) {
21
19
  return (
22
- <ul
23
- data-slot="pagination-content"
24
- className={cn('gap-1 flex items-center', className)}
25
- {...props}
26
- />
20
+ <ul data-slot="pagination-content" className="gap-1 flex items-center" {...props} />
27
21
  );
28
22
  }
29
23
 
30
- function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
24
+ function PaginationItem({ ...props }: Omit<React.ComponentProps<'li'>, 'className'>) {
31
25
  return <li data-slot="pagination-item" {...props} />;
32
26
  }
33
27
 
@@ -55,10 +49,10 @@ function PaginationLink({ isActive, size = 'icon', ...props }: PaginationLinkPro
55
49
  function PaginationPrevious({ ...props }: Omit<PaginationLinkProps, 'size'>) {
56
50
  return (
57
51
  <ButtonPrimitive
58
- className={cn(buttonVariants({ variant: 'ghost', size: 'default' }), 'pl-2')}
52
+ className={`${buttonVariants({ variant: 'ghost', size: 'default' })} pl-2`}
59
53
  render={<a aria-label="Go to previous page" data-slot="pagination-link" {...props} />}
60
54
  >
61
- <ChevronLeftIcon data-icon="inline-start" />
55
+ <ChevronLeft data-icon="inline-start" />
62
56
  <span className="hidden sm:block">Previous</span>
63
57
  </ButtonPrimitive>
64
58
  );
@@ -67,27 +61,24 @@ function PaginationPrevious({ ...props }: Omit<PaginationLinkProps, 'size'>) {
67
61
  function PaginationNext({ ...props }: Omit<PaginationLinkProps, 'size'>) {
68
62
  return (
69
63
  <ButtonPrimitive
70
- className={cn(buttonVariants({ variant: 'ghost', size: 'default' }), 'pr-2')}
64
+ className={`${buttonVariants({ variant: 'ghost', size: 'default' })} pr-2`}
71
65
  render={<a aria-label="Go to next page" data-slot="pagination-link" {...props} />}
72
66
  >
73
67
  <span className="hidden sm:block">Next</span>
74
- <ChevronRightIcon data-icon="inline-end" />
68
+ <ChevronRight data-icon="inline-end" />
75
69
  </ButtonPrimitive>
76
70
  );
77
71
  }
78
72
 
79
- function PaginationEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
73
+ function PaginationEllipsis({ ...props }: Omit<React.ComponentProps<'span'>, 'className'>) {
80
74
  return (
81
75
  <span
82
76
  aria-hidden
83
77
  data-slot="pagination-ellipsis"
84
- className={cn(
85
- "size-9 items-center justify-center [&_svg:not([class*='size-'])]:size-4 flex items-center justify-center",
86
- className,
87
- )}
78
+ className="size-9 items-center justify-center [&_svg:not([class*='size-'])]:size-4 flex items-center justify-center"
88
79
  {...props}
89
80
  >
90
- <MoreHorizontalIcon />
81
+ <OverflowMenuHorizontal />
91
82
  <span className="sr-only">More pages</span>
92
83
  </span>
93
84
  );
@@ -1,12 +1,14 @@
1
1
  import { Popover as PopoverPrimitive } from '@base-ui/react/popover';
2
2
  import * as React from 'react';
3
3
 
4
+ import { cn } from '../../../lib/utils';
5
+
4
6
  function Popover({ ...props }: PopoverPrimitive.Root.Props) {
5
7
  return <PopoverPrimitive.Root data-slot="popover" {...props} />;
6
8
  }
7
9
 
8
- function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
9
- return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
10
+ function PopoverTrigger({ className, ...props }: PopoverPrimitive.Trigger.Props) {
11
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" className={cn(className)} {...props} />;
10
12
  }
11
13
 
12
14
  function PopoverContent({
@@ -0,0 +1,33 @@
1
+ import { Radio as RadioPrimitive } from '@base-ui/react/radio';
2
+ import { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group';
3
+
4
+ import { CircleFilled } from '@carbon/icons-react';
5
+
6
+ function RadioGroup({ ...props }: Omit<RadioGroupPrimitive.Props, 'className'>) {
7
+ return (
8
+ <RadioGroupPrimitive
9
+ data-slot="radio-group"
10
+ className="grid gap-2 w-full"
11
+ {...props}
12
+ />
13
+ );
14
+ }
15
+
16
+ function RadioGroupItem({ ...props }: Omit<RadioPrimitive.Root.Props, 'className'>) {
17
+ return (
18
+ <RadioPrimitive.Root
19
+ data-slot="radio-group-item"
20
+ className="border-input text-primary dark:bg-input/30 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:aria-invalid:border-destructive/50 flex size-4 rounded-full shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50"
21
+ {...props}
22
+ >
23
+ <RadioPrimitive.Indicator
24
+ data-slot="radio-group-indicator"
25
+ className="group-aria-invalid/radio-group-item:text-destructive text-primary flex size-4 items-center justify-center"
26
+ >
27
+ <CircleFilled className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
28
+ </RadioPrimitive.Indicator>
29
+ </RadioPrimitive.Root>
30
+ );
31
+ }
32
+
33
+ export { RadioGroup, RadioGroupItem };