sheerly 0.0.1 → 0.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 (90) hide show
  1. package/lib/accordion.d.ts +13 -4
  2. package/lib/alert.d.ts +22 -7
  3. package/lib/avatar.css +1 -1
  4. package/lib/avatar.d.ts +21 -8
  5. package/lib/badge.d.ts +14 -4
  6. package/lib/breadcrumb.d.ts +18 -3
  7. package/lib/button.css +1 -1
  8. package/lib/button.d.ts +13 -4
  9. package/lib/card.d.ts +32 -12
  10. package/lib/carousel.d.ts +13 -7
  11. package/lib/checkbox.d.ts +6 -0
  12. package/lib/dialog.d.ts +20 -8
  13. package/lib/dropdown-menu.d.ts +30 -13
  14. package/lib/hover-card.d.ts +15 -6
  15. package/lib/index.css +1 -1
  16. package/lib/index.d.ts +1 -1
  17. package/lib/index.js +245 -237
  18. package/lib/input.css +1 -1
  19. package/lib/input.d.ts +6 -0
  20. package/lib/kbd.d.ts +10 -3
  21. package/lib/label.css +1 -1
  22. package/lib/label.d.ts +6 -0
  23. package/lib/pagination.d.ts +16 -2
  24. package/lib/popover.d.ts +15 -6
  25. package/lib/progress.d.ts +6 -0
  26. package/lib/radio.d.ts +13 -3
  27. package/lib/scrollbar.css +1 -0
  28. package/lib/scrollbar.d.ts +17 -0
  29. package/lib/select.css +1 -1
  30. package/lib/select.d.ts +6 -0
  31. package/lib/separator.d.ts +14 -4
  32. package/lib/sheet.d.ts +19 -6
  33. package/lib/skeleton.d.ts +12 -3
  34. package/lib/slider.d.ts +6 -0
  35. package/lib/spinner.d.ts +10 -4
  36. package/lib/switch.d.ts +6 -0
  37. package/lib/table.css +1 -1
  38. package/lib/table.d.ts +14 -3
  39. package/lib/tabs.d.ts +31 -8
  40. package/lib/textarea.d.ts +6 -0
  41. package/lib/toggle.d.ts +7 -0
  42. package/lib/tooltip.d.ts +15 -6
  43. package/lib/typography.d.ts +12 -4
  44. package/lib/utils/polymorphic.d.ts +14 -0
  45. package/package.json +12 -12
  46. package/src/accordion.tsx +20 -6
  47. package/src/alert.tsx +52 -14
  48. package/src/avatar.css +0 -1
  49. package/src/avatar.tsx +44 -15
  50. package/src/badge.tsx +34 -16
  51. package/src/breadcrumb.tsx +32 -8
  52. package/src/button.css +1 -0
  53. package/src/button.tsx +44 -27
  54. package/src/card.tsx +82 -23
  55. package/src/carousel.tsx +28 -11
  56. package/src/checkbox.tsx +7 -1
  57. package/src/dialog.tsx +55 -16
  58. package/src/dropdown-menu.tsx +94 -25
  59. package/src/hover-card.tsx +28 -9
  60. package/src/index.css +1 -0
  61. package/src/index.ts +1 -1
  62. package/src/input.css +1 -0
  63. package/src/input.tsx +7 -1
  64. package/src/kbd.tsx +17 -5
  65. package/src/label.css +1 -0
  66. package/src/label.tsx +7 -1
  67. package/src/pagination.tsx +23 -10
  68. package/src/popover.tsx +28 -9
  69. package/src/progress.tsx +7 -1
  70. package/src/radio.tsx +20 -5
  71. package/src/scrollbar.css +56 -0
  72. package/src/scrollbar.tsx +28 -0
  73. package/src/select.css +24 -0
  74. package/src/select.tsx +7 -1
  75. package/src/separator.tsx +23 -7
  76. package/src/sheet.tsx +53 -13
  77. package/src/skeleton.tsx +19 -5
  78. package/src/slider.tsx +7 -1
  79. package/src/spinner.tsx +21 -8
  80. package/src/switch.tsx +7 -1
  81. package/src/table.css +0 -1
  82. package/src/table.tsx +21 -5
  83. package/src/tabs.tsx +50 -17
  84. package/src/textarea.tsx +7 -1
  85. package/src/toggle.tsx +8 -1
  86. package/src/tooltip.tsx +28 -9
  87. package/src/typography.tsx +19 -5
  88. package/src/utils/polymorphic.ts +19 -0
  89. /package/lib/{cn.d.ts → utils/cn.d.ts} +0 -0
  90. /package/src/{cn.ts → utils/cn.ts} +0 -0
@@ -1,9 +1,28 @@
1
- import type { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes } from 'react'
2
- import { cn } from './cn'
1
+ import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ElementType } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
3
4
 
4
- /** Wrapper carrying the anchor. Nest the trigger button + content inside. */
5
- export function DropdownMenu({ className, ...props }: HTMLAttributes<HTMLSpanElement>) {
6
- return <span className={cn('sheerly-dropdown-menu', className)} {...props} />
5
+ /**
6
+ * Wrapper carrying the anchor. Nest the trigger button + content inside.
7
+ *
8
+ * @example
9
+ * <DropdownMenu>
10
+ * <Button commandfor="m" command="toggle-popover">Open</Button>
11
+ * <DropdownMenuContent id="m" popover role="menu">
12
+ * <DropdownMenuItem>Profile</DropdownMenuItem>
13
+ * <DropdownMenuItem data-variant="destructive">Sign out</DropdownMenuItem>
14
+ * </DropdownMenuContent>
15
+ * </DropdownMenu>
16
+ */
17
+ export type DropdownMenuProps<T extends ElementType = 'span'> = PolymorphicProps<T>
18
+
19
+ export function DropdownMenu<T extends ElementType = 'span'>({
20
+ as,
21
+ className,
22
+ ...props
23
+ }: DropdownMenuProps<T>) {
24
+ const Tag = as ?? 'span'
25
+ return <Tag className={cn('sheerly-dropdown-menu', className)} {...props} />
7
26
  }
8
27
 
9
28
  /**
@@ -11,24 +30,42 @@ export function DropdownMenu({ className, ...props }: HTMLAttributes<HTMLSpanEle
11
30
  * `commandfor`, and `role="menu"`. When nested inside a <DropdownMenuSub> it is
12
31
  * automatically styled as a submenu panel (no extra attribute needed).
13
32
  */
14
- export type DropdownMenuContentProps = HTMLAttributes<HTMLDivElement>
15
-
16
- export function DropdownMenuContent({ className, ...props }: DropdownMenuContentProps) {
17
- return <div className={cn('sheerly-dropdown-menu-content', className)} {...props} />
33
+ export type DropdownMenuContentProps<T extends ElementType = 'div'> = PolymorphicProps<T>
34
+
35
+ export function DropdownMenuContent<T extends ElementType = 'div'>({
36
+ as,
37
+ className,
38
+ ...props
39
+ }: DropdownMenuContentProps<T>) {
40
+ const Tag = as ?? 'div'
41
+ return <Tag className={cn('sheerly-dropdown-menu-content', className)} {...props} />
18
42
  }
19
43
 
20
44
  /** A semantic wrapper for a set of related items/labels. */
21
- export function DropdownMenuGroup({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
22
- return <div className={cn('sheerly-dropdown-menu-group', className)} {...props} />
45
+ export type DropdownMenuGroupProps<T extends ElementType = 'div'> = PolymorphicProps<T>
46
+
47
+ export function DropdownMenuGroup<T extends ElementType = 'div'>({
48
+ as,
49
+ className,
50
+ ...props
51
+ }: DropdownMenuGroupProps<T>) {
52
+ const Tag = as ?? 'div'
53
+ return <Tag className={cn('sheerly-dropdown-menu-group', className)} {...props} />
23
54
  }
24
55
 
25
56
  /** A non-interactive section heading. */
26
- export interface DropdownMenuLabelProps extends HTMLAttributes<HTMLDivElement> {
27
- 'data-inset'?: boolean
28
- }
29
-
30
- export function DropdownMenuLabel({ className, ...props }: DropdownMenuLabelProps) {
31
- return <div className={cn('sheerly-dropdown-menu-label', className)} {...props} />
57
+ export type DropdownMenuLabelProps<T extends ElementType = 'div'> = PolymorphicProps<
58
+ T,
59
+ { 'data-inset'?: boolean }
60
+ >
61
+
62
+ export function DropdownMenuLabel<T extends ElementType = 'div'>({
63
+ as,
64
+ className,
65
+ ...props
66
+ }: DropdownMenuLabelProps<T>) {
67
+ const Tag = as ?? 'div'
68
+ return <Tag className={cn('sheerly-dropdown-menu-label', className)} {...props} />
32
69
  }
33
70
 
34
71
  /** An interactive menu row. Renders a <button> by default. */
@@ -52,18 +89,45 @@ export function DropdownMenuLinkItem({ className, ...props }: DropdownMenuLinkIt
52
89
  }
53
90
 
54
91
  /** A trailing keyboard-shortcut hint inside an item. */
55
- export function DropdownMenuShortcut({ className, ...props }: HTMLAttributes<HTMLSpanElement>) {
56
- return <span className={cn('sheerly-dropdown-menu-shortcut', className)} {...props} />
92
+ export type DropdownMenuShortcutProps<T extends ElementType = 'span'> = PolymorphicProps<T>
93
+
94
+ export function DropdownMenuShortcut<T extends ElementType = 'span'>({
95
+ as,
96
+ className,
97
+ ...props
98
+ }: DropdownMenuShortcutProps<T>) {
99
+ const Tag = as ?? 'span'
100
+ return <Tag className={cn('sheerly-dropdown-menu-shortcut', className)} {...props} />
57
101
  }
58
102
 
59
103
  /** A horizontal divider between item groups. */
60
- export function DropdownMenuSeparator({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
61
- return <div role="separator" className={cn('sheerly-dropdown-menu-separator', className)} {...props} />
104
+ export type DropdownMenuSeparatorProps<T extends ElementType = 'div'> = PolymorphicProps<T>
105
+
106
+ export function DropdownMenuSeparator<T extends ElementType = 'div'>({
107
+ as,
108
+ className,
109
+ ...props
110
+ }: DropdownMenuSeparatorProps<T>) {
111
+ const Tag = as ?? 'div'
112
+ return (
113
+ <Tag
114
+ role="separator"
115
+ className={cn('sheerly-dropdown-menu-separator', className)}
116
+ {...props}
117
+ />
118
+ )
62
119
  }
63
120
 
64
121
  /** Submenu wrapper (display: contents). Nest a sub-trigger + a sub-content. */
65
- export function DropdownMenuSub({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
66
- return <div className={cn('sheerly-dropdown-menu-sub', className)} {...props} />
122
+ export type DropdownMenuSubProps<T extends ElementType = 'div'> = PolymorphicProps<T>
123
+
124
+ export function DropdownMenuSub<T extends ElementType = 'div'>({
125
+ as,
126
+ className,
127
+ ...props
128
+ }: DropdownMenuSubProps<T>) {
129
+ const Tag = as ?? 'div'
130
+ return <Tag className={cn('sheerly-dropdown-menu-sub', className)} {...props} />
67
131
  }
68
132
 
69
133
  /**
@@ -83,6 +147,11 @@ export function DropdownMenuSubTrigger({ className, type = 'button', ...props }:
83
147
  * The submenu panel. It's a DropdownMenuContent nested inside a DropdownMenuSub;
84
148
  * the child-combinator CSS picks it up as a submenu — no extra attribute needed.
85
149
  */
86
- export function DropdownMenuSubContent({ className, ...props }: DropdownMenuContentProps) {
87
- return <div className={cn('sheerly-dropdown-menu-content', className)} {...props} />
150
+ export function DropdownMenuSubContent<T extends ElementType = 'div'>({
151
+ as,
152
+ className,
153
+ ...props
154
+ }: DropdownMenuContentProps<T>) {
155
+ const Tag = as ?? 'div'
156
+ return <Tag className={cn('sheerly-dropdown-menu-content', className)} {...props} />
88
157
  }
@@ -1,16 +1,35 @@
1
- import type { HTMLAttributes } from 'react'
2
- import { cn } from './cn'
1
+ import type { ElementType } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
3
4
 
4
- export type HoverCardProps = HTMLAttributes<HTMLSpanElement>
5
+ export type HoverCardProps<T extends ElementType = 'span'> = PolymorphicProps<T>
5
6
 
6
- /** Wrapper carrying the anchor. Nest the trigger + content inside. */
7
- export function HoverCard({ className, ...props }: HoverCardProps) {
8
- return <span className={cn('sheerly-hover-card', className)} {...props} />
7
+ /**
8
+ * Wrapper carrying the anchor. Nest the trigger + content inside.
9
+ *
10
+ * @example
11
+ * <HoverCard>
12
+ * <a href="/user/ada" interestfor="hc">@ada</a>
13
+ * <HoverCardContent id="hc" popover="hint">Ada Lovelace — 1815</HoverCardContent>
14
+ * </HoverCard>
15
+ */
16
+ export function HoverCard<T extends ElementType = 'span'>({
17
+ as,
18
+ className,
19
+ ...props
20
+ }: HoverCardProps<T>) {
21
+ const Tag = as ?? 'span'
22
+ return <Tag className={cn('sheerly-hover-card', className)} {...props} />
9
23
  }
10
24
 
11
- export type HoverCardContentProps = HTMLAttributes<HTMLDivElement>
25
+ export type HoverCardContentProps<T extends ElementType = 'div'> = PolymorphicProps<T>
12
26
 
13
27
  /** The floating card. Give it `popover="hint"` and an `id` matched by the trigger's `interestfor`. */
14
- export function HoverCardContent({ className, ...props }: HoverCardContentProps) {
15
- return <div className={cn('sheerly-hover-card-content', className)} {...props} />
28
+ export function HoverCardContent<T extends ElementType = 'div'>({
29
+ as,
30
+ className,
31
+ ...props
32
+ }: HoverCardContentProps<T>) {
33
+ const Tag = as ?? 'div'
34
+ return <Tag className={cn('sheerly-hover-card-content', className)} {...props} />
16
35
  }
package/src/index.css CHANGED
@@ -19,6 +19,7 @@
19
19
  @import "./badge.css";
20
20
  @import "./alert.css";
21
21
  @import "./separator.css";
22
+ @import "./scrollbar.css";
22
23
  @import "./skeleton.css";
23
24
  @import "./spinner.css";
24
25
  @import "./avatar.css";
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  // The core library is CSS-only; import this entry only if you want typed JSX.
3
3
  // You still need to import the stylesheet: `import 'sheerly/index.css'`.
4
4
 
5
- export * from './cn'
6
5
  export * from './accordion'
7
6
  export * from './alert'
8
7
  export * from './avatar'
@@ -22,6 +21,7 @@ export * from './pagination'
22
21
  export * from './popover'
23
22
  export * from './progress'
24
23
  export * from './radio'
24
+ export * from './scrollbar'
25
25
  export * from './select'
26
26
  export * from './separator'
27
27
  export * from './sheet'
package/src/input.css CHANGED
@@ -4,6 +4,7 @@
4
4
  min-width: 0;
5
5
  border-radius: var(--sheerly-radius-lg);
6
6
  border: 1px solid var(--sheerly-input);
7
+ box-sizing: border-box;
7
8
  background-color: transparent;
8
9
  padding: 0 10px;
9
10
  font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
package/src/input.tsx CHANGED
@@ -1,10 +1,16 @@
1
1
  import type { InputHTMLAttributes } from 'react'
2
- import { cn } from './cn'
2
+ import { cn } from './utils/cn'
3
3
 
4
4
  export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
5
5
  'data-size'?: 'xs' | 'sm' | 'default' | 'lg'
6
6
  }
7
7
 
8
+ /**
9
+ * A text input. Set `aria-invalid` for the error ring; `data-size` for height.
10
+ *
11
+ * @example
12
+ * <Input type="email" placeholder="you@example.com" />
13
+ */
8
14
  export function Input({ className, ...props }: InputProps) {
9
15
  return <input className={cn('sheerly-input', className)} {...props} />
10
16
  }
package/src/kbd.tsx CHANGED
@@ -1,14 +1,26 @@
1
- import type { HTMLAttributes } from 'react'
2
- import { cn } from './cn'
1
+ import type { ElementType, HTMLAttributes } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
3
4
 
4
5
  export type KbdProps = HTMLAttributes<HTMLElement>
5
6
 
7
+ /**
8
+ * A keyboard key hint. Wrap several in a <KbdGroup> for combos.
9
+ *
10
+ * @example
11
+ * <KbdGroup><Kbd>⌘</Kbd><Kbd>K</Kbd></KbdGroup>
12
+ */
6
13
  export function Kbd({ className, ...props }: KbdProps) {
7
14
  return <kbd className={cn('sheerly-kbd', className)} {...props} />
8
15
  }
9
16
 
10
- export type KbdGroupProps = HTMLAttributes<HTMLElement>
17
+ export type KbdGroupProps<T extends ElementType = 'span'> = PolymorphicProps<T>
11
18
 
12
- export function KbdGroup({ className, ...props }: KbdGroupProps) {
13
- return <span className={cn('sheerly-kbd-group', className)} {...props} />
19
+ export function KbdGroup<T extends ElementType = 'span'>({
20
+ as,
21
+ className,
22
+ ...props
23
+ }: KbdGroupProps<T>) {
24
+ const Tag = as ?? 'span'
25
+ return <Tag className={cn('sheerly-kbd-group', className)} {...props} />
14
26
  }
package/src/label.css CHANGED
@@ -9,6 +9,7 @@
9
9
  line-height: 20px;
10
10
  color: var(--sheerly-foreground);
11
11
  user-select: none;
12
+ cursor: pointer;
12
13
 
13
14
  &[data-disabled="true"] {
14
15
  color: var(--sheerly-muted-foreground);
package/src/label.tsx CHANGED
@@ -1,10 +1,16 @@
1
1
  import type { LabelHTMLAttributes } from 'react'
2
- import { cn } from './cn'
2
+ import { cn } from './utils/cn'
3
3
 
4
4
  export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
5
5
  'data-disabled'?: 'true'
6
6
  }
7
7
 
8
+ /**
9
+ * A form label. Set `data-disabled="true"` to mute it.
10
+ *
11
+ * @example
12
+ * <Label htmlFor="email">Email</Label>
13
+ */
8
14
  export function Label({ className, ...props }: LabelProps) {
9
15
  return <label className={cn('sheerly-label', className)} {...props} />
10
16
  }
@@ -1,6 +1,19 @@
1
- import type { AnchorHTMLAttributes, HTMLAttributes } from 'react'
2
- import { cn } from './cn'
1
+ import type { AnchorHTMLAttributes, ElementType, HTMLAttributes } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
3
4
 
5
+ /**
6
+ * Page navigation. Mark the current page's link with `isActive`.
7
+ *
8
+ * @example
9
+ * <Pagination>
10
+ * <PaginationContent>
11
+ * <PaginationItem><PaginationPrevious href="#" /></PaginationItem>
12
+ * <PaginationItem><PaginationLink href="#" isActive>1</PaginationLink></PaginationItem>
13
+ * <PaginationItem><PaginationNext href="#" /></PaginationItem>
14
+ * </PaginationContent>
15
+ * </Pagination>
16
+ */
4
17
  export function Pagination({ className, ...props }: HTMLAttributes<HTMLElement>) {
5
18
  return (
6
19
  <nav
@@ -79,17 +92,17 @@ export function PaginationNext({
79
92
  )
80
93
  }
81
94
 
82
- export function PaginationEllipsis({
95
+ export type PaginationEllipsisProps<T extends ElementType = 'span'> = PolymorphicProps<T>
96
+
97
+ export function PaginationEllipsis<T extends ElementType = 'span'>({
98
+ as,
83
99
  className,
84
100
  ...props
85
- }: HTMLAttributes<HTMLSpanElement>) {
101
+ }: PaginationEllipsisProps<T>) {
102
+ const Tag = as ?? 'span'
86
103
  return (
87
- <span
88
- aria-hidden
89
- className={cn('sheerly-pagination-ellipsis', className)}
90
- {...props}
91
- >
104
+ <Tag aria-hidden className={cn('sheerly-pagination-ellipsis', className)} {...props}>
92
105
  &hellip;
93
- </span>
106
+ </Tag>
94
107
  )
95
108
  }
package/src/popover.tsx CHANGED
@@ -1,16 +1,35 @@
1
- import type { HTMLAttributes } from 'react'
2
- import { cn } from './cn'
1
+ import type { ElementType } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
3
4
 
4
- export type PopoverProps = HTMLAttributes<HTMLSpanElement>
5
+ export type PopoverProps<T extends ElementType = 'span'> = PolymorphicProps<T>
5
6
 
6
- /** Wrapper carrying the anchor. Nest the trigger button + content inside. */
7
- export function Popover({ className, ...props }: PopoverProps) {
8
- return <span className={cn('sheerly-popover', className)} {...props} />
7
+ /**
8
+ * Wrapper carrying the anchor. Nest the trigger button + content inside.
9
+ *
10
+ * @example
11
+ * <Popover>
12
+ * <Button commandfor="p" command="toggle-popover">Open</Button>
13
+ * <PopoverContent id="p" popover>Place content here.</PopoverContent>
14
+ * </Popover>
15
+ */
16
+ export function Popover<T extends ElementType = 'span'>({
17
+ as,
18
+ className,
19
+ ...props
20
+ }: PopoverProps<T>) {
21
+ const Tag = as ?? 'span'
22
+ return <Tag className={cn('sheerly-popover', className)} {...props} />
9
23
  }
10
24
 
11
- export type PopoverContentProps = HTMLAttributes<HTMLDivElement>
25
+ export type PopoverContentProps<T extends ElementType = 'div'> = PolymorphicProps<T>
12
26
 
13
27
  /** The floating panel. Give it `popover` and an `id` matched by the trigger's `commandfor`. */
14
- export function PopoverContent({ className, ...props }: PopoverContentProps) {
15
- return <div className={cn('sheerly-popover-content', className)} {...props} />
28
+ export function PopoverContent<T extends ElementType = 'div'>({
29
+ as,
30
+ className,
31
+ ...props
32
+ }: PopoverContentProps<T>) {
33
+ const Tag = as ?? 'div'
34
+ return <Tag className={cn('sheerly-popover-content', className)} {...props} />
16
35
  }
package/src/progress.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ProgressHTMLAttributes } from 'react'
2
- import { cn } from './cn'
2
+ import { cn } from './utils/cn'
3
3
 
4
4
  export interface ProgressProps extends ProgressHTMLAttributes<HTMLProgressElement> {
5
5
  'data-variant'?:
@@ -14,6 +14,12 @@ export interface ProgressProps extends ProgressHTMLAttributes<HTMLProgressElemen
14
14
  'data-size'?: 'sm' | 'default' | 'lg'
15
15
  }
16
16
 
17
+ /**
18
+ * A determinate progress bar. Set `value` (and `max`, default 1).
19
+ *
20
+ * @example
21
+ * <Progress value={60} max={100} data-variant="success" />
22
+ */
17
23
  export function Progress({ className, ...props }: ProgressProps) {
18
24
  return <progress className={cn('sheerly-progress', className)} {...props} />
19
25
  }
package/src/radio.tsx CHANGED
@@ -1,14 +1,29 @@
1
- import type { HTMLAttributes, InputHTMLAttributes } from 'react'
2
- import { cn } from './cn'
1
+ import type { ElementType, InputHTMLAttributes } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
3
4
 
4
- export type RadioGroupProps = HTMLAttributes<HTMLDivElement>
5
+ export type RadioGroupProps<T extends ElementType = 'div'> = PolymorphicProps<T>
5
6
 
6
- export function RadioGroup({ className, ...props }: RadioGroupProps) {
7
- return <div className={cn('sheerly-radio-group', className)} {...props} />
7
+ export function RadioGroup<T extends ElementType = 'div'>({
8
+ as,
9
+ className,
10
+ ...props
11
+ }: RadioGroupProps<T>) {
12
+ const Tag = as ?? 'div'
13
+ return <Tag className={cn('sheerly-radio-group', className)} {...props} />
8
14
  }
9
15
 
10
16
  export type RadioProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>
11
17
 
18
+ /**
19
+ * A radio `<input>`. Share a `name` and wrap options in a <RadioGroup>.
20
+ *
21
+ * @example
22
+ * <RadioGroup>
23
+ * <Label><Radio name="plan" value="free" defaultChecked /> Free</Label>
24
+ * <Label><Radio name="plan" value="pro" /> Pro</Label>
25
+ * </RadioGroup>
26
+ */
12
27
  export function Radio({ className, ...props }: RadioProps) {
13
28
  return <input type="radio" className={cn('sheerly-radio', className)} {...props} />
14
29
  }
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Scrollbar — a sheerly-styled scrollbar for any overflow container.
3
+ * Apply `.sheerly-scrollbar` to a scrollable element to replace the native
4
+ * scrollbar with a thin, theme-tinted one (matching the select picker).
5
+ * You still set the element's own size + overflow (e.g. `max-height` +
6
+ * `overflow-y: auto`), or use a `data-orientation` helper below.
7
+ *
8
+ * Usage:
9
+ * <div class="sheerly-scrollbar" style="max-height: 200px; overflow-y: auto">…</div>
10
+ * <div class="sheerly-scrollbar" data-orientation="vertical" style="max-height: 200px">…</div>
11
+ * <div class="sheerly-scrollbar" data-orientation="horizontal">…</div>
12
+ *
13
+ * Latest Chrome only — uses standard `scrollbar-width`/`scrollbar-color` plus
14
+ * `::-webkit-scrollbar-*` for the fuller styled look.
15
+ */
16
+ .sheerly-scrollbar {
17
+ /* Standard properties (thin thumb tinted from the foreground). */
18
+ scrollbar-width: thin;
19
+ scrollbar-color: oklch(from var(--sheerly-foreground) l c h / 20%) transparent;
20
+
21
+ /* WebKit/Blink styled scrollbar. */
22
+ &::-webkit-scrollbar {
23
+ width: 8px;
24
+ height: 8px;
25
+ }
26
+
27
+ &::-webkit-scrollbar-track {
28
+ background: transparent;
29
+ }
30
+
31
+ &::-webkit-scrollbar-thumb {
32
+ border: 2px solid transparent;
33
+ border-radius: var(--sheerly-radius-full);
34
+ background-clip: padding-box;
35
+ background-color: oklch(from var(--sheerly-foreground) l c h / 20%);
36
+ }
37
+
38
+ &::-webkit-scrollbar-thumb:hover {
39
+ background-color: oklch(from var(--sheerly-foreground) l c h / 35%);
40
+ }
41
+
42
+ &::-webkit-scrollbar-corner {
43
+ background: transparent;
44
+ }
45
+
46
+ /* Orientation helpers set the matching overflow so the class is enough. */
47
+ &[data-orientation="vertical"] {
48
+ overflow-x: hidden;
49
+ overflow-y: auto;
50
+ }
51
+
52
+ &[data-orientation="horizontal"] {
53
+ overflow-x: auto;
54
+ overflow-y: hidden;
55
+ }
56
+ }
@@ -0,0 +1,28 @@
1
+ import type { ElementType } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
4
+
5
+ export type ScrollbarProps<T extends ElementType = 'div'> = PolymorphicProps<
6
+ T,
7
+ { 'data-orientation'?: 'vertical' | 'horizontal' }
8
+ >
9
+
10
+ /**
11
+ * A scroll container with a thin, theme-tinted scrollbar (matching the select
12
+ * picker). Set the element's own size + overflow inline, or use
13
+ * `data-orientation` to have the matching overflow applied for you.
14
+ *
15
+ * Polymorphic: pass `as` to change the rendered tag (defaults to `div`).
16
+ *
17
+ * @example
18
+ * <Scrollbar data-orientation="vertical" style={{ maxHeight: 200 }}>…</Scrollbar>
19
+ * <Scrollbar style={{ maxHeight: 200, overflowY: 'auto' }}>…</Scrollbar>
20
+ */
21
+ export function Scrollbar<T extends ElementType = 'div'>({
22
+ as,
23
+ className,
24
+ ...props
25
+ }: ScrollbarProps<T>) {
26
+ const Tag = as ?? 'div'
27
+ return <Tag className={cn('sheerly-scrollbar', className)} {...props} />
28
+ }
package/src/select.css CHANGED
@@ -12,6 +12,7 @@
12
12
  gap: 8px;
13
13
  border-radius: var(--sheerly-radius-lg);
14
14
  border: 1px solid var(--sheerly-input);
15
+ box-sizing: border-box;
15
16
  background-color: transparent;
16
17
  padding: 0 12px;
17
18
  font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
@@ -97,6 +98,29 @@
97
98
  overflow-y: auto;
98
99
  opacity: 0;
99
100
  transition: all 0.2s allow-discrete;
101
+
102
+ /* Sheerly-styled scrollbar (replaces the native one) */
103
+ scrollbar-width: thin;
104
+ scrollbar-color: oklch(from var(--sheerly-foreground) l c h / 20%) transparent;
105
+
106
+ &::-webkit-scrollbar {
107
+ width: 8px;
108
+ }
109
+
110
+ &::-webkit-scrollbar-track {
111
+ background: transparent;
112
+ }
113
+
114
+ &::-webkit-scrollbar-thumb {
115
+ border: 2px solid transparent;
116
+ border-radius: var(--sheerly-radius-full);
117
+ background-clip: padding-box;
118
+ background-color: oklch(from var(--sheerly-foreground) l c h / 20%);
119
+ }
120
+
121
+ &::-webkit-scrollbar-thumb:hover {
122
+ background-color: oklch(from var(--sheerly-foreground) l c h / 35%);
123
+ }
100
124
  }
101
125
 
102
126
  .sheerly-select:open::picker(select) {
package/src/select.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { SelectHTMLAttributes } from 'react'
2
- import { cn } from './cn'
2
+ import { cn } from './utils/cn'
3
3
 
4
4
  export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
5
5
  'data-size'?: 'sm' | 'default' | 'lg'
@@ -9,6 +9,12 @@ export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
9
9
  * Uses the customizable-select API. Requires
10
10
  * `<button><selectedcontent></selectedcontent></button>` as the first child,
11
11
  * which this wrapper injects before the consumer's <option>s.
12
+ *
13
+ * @example
14
+ * <Select defaultValue="apple">
15
+ * <option value="apple">Apple</option>
16
+ * <option value="banana">Banana</option>
17
+ * </Select>
12
18
  */
13
19
  export function Select({ className, children, ...props }: SelectProps) {
14
20
  return (
package/src/separator.tsx CHANGED
@@ -1,10 +1,26 @@
1
- import type { HTMLAttributes } from 'react'
2
- import { cn } from './cn'
1
+ import type { ElementType } from 'react'
2
+ import { cn } from './utils/cn'
3
+ import type { PolymorphicProps } from './utils/polymorphic'
3
4
 
4
- export interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {
5
- 'data-orientation'?: 'horizontal' | 'vertical'
6
- }
5
+ export type SeparatorProps<T extends ElementType = 'div'> = PolymorphicProps<
6
+ T,
7
+ { 'data-orientation'?: 'horizontal' | 'vertical' }
8
+ >
7
9
 
8
- export function Separator({ className, ...props }: SeparatorProps) {
9
- return <div className={cn('sheerly-separator', className)} {...props} />
10
+ /**
11
+ * A divider line. Set `data-orientation="vertical"` for a vertical rule.
12
+ *
13
+ * Polymorphic: pass `as` to change the rendered tag (defaults to `div`, e.g.
14
+ * `as="hr"`).
15
+ *
16
+ * @example
17
+ * <Separator />
18
+ */
19
+ export function Separator<T extends ElementType = 'div'>({
20
+ as,
21
+ className,
22
+ ...props
23
+ }: SeparatorProps<T>) {
24
+ const Tag = as ?? 'div'
25
+ return <Tag className={cn('sheerly-separator', className)} {...props} />
10
26
  }