sheerly 0.0.1

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 (140) hide show
  1. package/README.md +3 -0
  2. package/lib/accordion.css +1 -0
  3. package/lib/accordion.d.ts +6 -0
  4. package/lib/alert.css +1 -0
  5. package/lib/alert.d.ts +8 -0
  6. package/lib/avatar.css +1 -0
  7. package/lib/avatar.d.ts +13 -0
  8. package/lib/badge.css +1 -0
  9. package/lib/badge.d.ts +5 -0
  10. package/lib/breadcrumb.css +1 -0
  11. package/lib/breadcrumb.d.ts +8 -0
  12. package/lib/button.css +1 -0
  13. package/lib/button.d.ts +7 -0
  14. package/lib/card.css +1 -0
  15. package/lib/card.d.ts +14 -0
  16. package/lib/carousel.css +1 -0
  17. package/lib/carousel.d.ts +21 -0
  18. package/lib/checkbox.css +1 -0
  19. package/lib/checkbox.d.ts +3 -0
  20. package/lib/cn.d.ts +5 -0
  21. package/lib/dialog.css +1 -0
  22. package/lib/dialog.d.ts +15 -0
  23. package/lib/dropdown-menu.css +1 -0
  24. package/lib/dropdown-menu.d.ts +49 -0
  25. package/lib/hover-card.css +1 -0
  26. package/lib/hover-card.d.ts +7 -0
  27. package/lib/index.css +1 -0
  28. package/lib/index.d.ts +33 -0
  29. package/lib/index.js +652 -0
  30. package/lib/input.css +1 -0
  31. package/lib/input.d.ts +5 -0
  32. package/lib/kbd.css +1 -0
  33. package/lib/kbd.d.ts +5 -0
  34. package/lib/label.css +1 -0
  35. package/lib/label.d.ts +5 -0
  36. package/lib/pagination.css +1 -0
  37. package/lib/pagination.d.ts +17 -0
  38. package/lib/popover.css +1 -0
  39. package/lib/popover.d.ts +7 -0
  40. package/lib/progress.css +1 -0
  41. package/lib/progress.d.ts +6 -0
  42. package/lib/radio.css +1 -0
  43. package/lib/radio.d.ts +5 -0
  44. package/lib/root.css +1 -0
  45. package/lib/select.css +1 -0
  46. package/lib/select.d.ts +10 -0
  47. package/lib/separator.css +1 -0
  48. package/lib/separator.d.ts +5 -0
  49. package/lib/sheet.css +1 -0
  50. package/lib/sheet.d.ts +16 -0
  51. package/lib/skeleton.css +1 -0
  52. package/lib/skeleton.d.ts +3 -0
  53. package/lib/slider.css +1 -0
  54. package/lib/slider.d.ts +3 -0
  55. package/lib/spinner.css +1 -0
  56. package/lib/spinner.d.ts +10 -0
  57. package/lib/switch.css +1 -0
  58. package/lib/switch.d.ts +5 -0
  59. package/lib/table.css +1 -0
  60. package/lib/table.d.ts +5 -0
  61. package/lib/tabs.css +1 -0
  62. package/lib/tabs.d.ts +13 -0
  63. package/lib/textarea.css +1 -0
  64. package/lib/textarea.d.ts +3 -0
  65. package/lib/toggle.css +1 -0
  66. package/lib/toggle.d.ts +16 -0
  67. package/lib/tooltip.css +1 -0
  68. package/lib/tooltip.d.ts +7 -0
  69. package/lib/typography.css +1 -0
  70. package/lib/typography.d.ts +9 -0
  71. package/package.json +33 -0
  72. package/src/accordion.css +108 -0
  73. package/src/accordion.tsx +15 -0
  74. package/src/alert.css +126 -0
  75. package/src/alert.tsx +25 -0
  76. package/src/avatar.css +75 -0
  77. package/src/avatar.tsx +30 -0
  78. package/src/badge.css +71 -0
  79. package/src/badge.tsx +19 -0
  80. package/src/breadcrumb.css +166 -0
  81. package/src/breadcrumb.tsx +71 -0
  82. package/src/button.css +214 -0
  83. package/src/button.tsx +30 -0
  84. package/src/card.css +99 -0
  85. package/src/card.tsx +41 -0
  86. package/src/carousel.css +183 -0
  87. package/src/carousel.tsx +30 -0
  88. package/src/checkbox.css +56 -0
  89. package/src/checkbox.tsx +10 -0
  90. package/src/cn.ts +7 -0
  91. package/src/dialog.css +175 -0
  92. package/src/dialog.tsx +47 -0
  93. package/src/dropdown-menu.css +257 -0
  94. package/src/dropdown-menu.tsx +88 -0
  95. package/src/hover-card.css +75 -0
  96. package/src/hover-card.tsx +16 -0
  97. package/src/index.css +33 -0
  98. package/src/index.ts +37 -0
  99. package/src/input.css +62 -0
  100. package/src/input.tsx +10 -0
  101. package/src/kbd.css +32 -0
  102. package/src/kbd.tsx +14 -0
  103. package/src/label.css +17 -0
  104. package/src/label.tsx +10 -0
  105. package/src/pagination.css +150 -0
  106. package/src/pagination.tsx +95 -0
  107. package/src/popover.css +65 -0
  108. package/src/popover.tsx +16 -0
  109. package/src/progress.css +79 -0
  110. package/src/progress.tsx +19 -0
  111. package/src/radio.css +61 -0
  112. package/src/radio.tsx +14 -0
  113. package/src/root.css +78 -0
  114. package/src/select.css +164 -0
  115. package/src/select.tsx +22 -0
  116. package/src/separator.css +14 -0
  117. package/src/separator.tsx +10 -0
  118. package/src/sheet.css +225 -0
  119. package/src/sheet.tsx +48 -0
  120. package/src/skeleton.css +15 -0
  121. package/src/skeleton.tsx +8 -0
  122. package/src/slider.css +68 -0
  123. package/src/slider.tsx +8 -0
  124. package/src/spinner.css +43 -0
  125. package/src/spinner.tsx +24 -0
  126. package/src/switch.css +79 -0
  127. package/src/switch.tsx +18 -0
  128. package/src/table.css +77 -0
  129. package/src/table.tsx +14 -0
  130. package/src/tabs.css +110 -0
  131. package/src/tabs.tsx +38 -0
  132. package/src/textarea.css +38 -0
  133. package/src/textarea.tsx +8 -0
  134. package/src/toggle.css +148 -0
  135. package/src/toggle.tsx +47 -0
  136. package/src/tooltip.css +60 -0
  137. package/src/tooltip.tsx +16 -0
  138. package/src/types.d.ts +42 -0
  139. package/src/typography.css +176 -0
  140. package/src/typography.tsx +13 -0
package/src/select.css ADDED
@@ -0,0 +1,164 @@
1
+ /* Select — native <select> with the customizable-select API (appearance: base-select) */
2
+ .sheerly-select,
3
+ .sheerly-select::picker(select) {
4
+ appearance: base-select;
5
+ }
6
+
7
+ /* .sheerly-select is the trigger button */
8
+ .sheerly-select {
9
+ display: inline-flex;
10
+ width: fit-content;
11
+ align-items: center;
12
+ gap: 8px;
13
+ border-radius: var(--sheerly-radius-lg);
14
+ border: 1px solid var(--sheerly-input);
15
+ background-color: transparent;
16
+ padding: 0 12px;
17
+ font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
18
+ font-size: 14px;
19
+ font-weight: 400;
20
+ line-height: 20px;
21
+ white-space: nowrap;
22
+ color: var(--sheerly-foreground);
23
+
24
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
25
+ outline: none;
26
+ cursor: pointer;
27
+
28
+ /* Default size (32px) */
29
+ height: 32px;
30
+
31
+ &:focus-visible {
32
+ border-color: var(--sheerly-ring);
33
+ box-shadow: var(--sheerly-focus-ring);
34
+ }
35
+
36
+ &:disabled {
37
+ pointer-events: none;
38
+ opacity: 0.5;
39
+ }
40
+
41
+ &[aria-invalid="true"] {
42
+ border-color: var(--sheerly-destructive);
43
+ box-shadow: 0 0 0 3px oklch(from var(--sheerly-destructive) l c h / 20%);
44
+ }
45
+
46
+ &:invalid,
47
+ &[data-placeholder] {
48
+ color: var(--sheerly-muted-foreground);
49
+ }
50
+
51
+ &[data-size="sm"] {
52
+ height: 32px;
53
+ }
54
+
55
+ &[data-size="lg"] {
56
+ height: 40px;
57
+ font-size: 16px;
58
+ }
59
+ }
60
+
61
+ /* Picker icon (thin chevron, replacing the default triangle glyph) */
62
+ .sheerly-select::picker-icon {
63
+ width: 16px;
64
+ height: 16px;
65
+ color: var(--sheerly-muted-foreground);
66
+ content: "";
67
+ background-color: currentColor;
68
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;
69
+ transition: rotate 0.2s ease;
70
+ }
71
+
72
+ .sheerly-select:open::picker-icon {
73
+ rotate: 180deg;
74
+ }
75
+
76
+ /* Displays the current value in the trigger */
77
+ .sheerly-select selectedcontent {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 8px;
81
+ }
82
+
83
+ /* An empty-value option acts as the placeholder */
84
+ .sheerly-select option[value=""] {
85
+ color: var(--sheerly-muted-foreground);
86
+ }
87
+
88
+ /* The drop-down panel (::picker) */
89
+ .sheerly-select::picker(select) {
90
+ border: 1px solid var(--sheerly-border);
91
+ border-radius: var(--sheerly-radius-xl);
92
+ background-color: var(--sheerly-popover);
93
+ color: var(--sheerly-popover-foreground);
94
+ box-shadow: 0 4px 16px -4px oklch(0 0 0 / 8%), 0 1px 2px oklch(0 0 0 / 4%);
95
+ padding: 4px;
96
+ max-height: 256px;
97
+ overflow-y: auto;
98
+ opacity: 0;
99
+ transition: all 0.2s allow-discrete;
100
+ }
101
+
102
+ .sheerly-select:open::picker(select) {
103
+ opacity: 1;
104
+ }
105
+
106
+ @starting-style {
107
+ .sheerly-select:open::picker(select) {
108
+ opacity: 0;
109
+ }
110
+ }
111
+
112
+ /* Rows inside the drop-down picker */
113
+ .sheerly-select option {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 8px;
117
+ border-radius: var(--sheerly-radius-md);
118
+ padding: 6px 8px;
119
+ font-size: 14px;
120
+ line-height: 20px;
121
+ cursor: default;
122
+ user-select: none;
123
+ transition: background-color 0.1s ease;
124
+
125
+ &:hover,
126
+ &:focus {
127
+ background-color: var(--sheerly-accent);
128
+ }
129
+
130
+ &:checked {
131
+ font-weight: 500;
132
+ }
133
+
134
+ &:disabled {
135
+ color: var(--sheerly-muted-foreground);
136
+ pointer-events: none;
137
+ }
138
+ }
139
+
140
+ /* Checkmark on selected option */
141
+ .sheerly-select option::checkmark {
142
+ color: var(--sheerly-foreground);
143
+ order: 1;
144
+ margin-left: auto;
145
+ }
146
+
147
+ .sheerly-select optgroup {
148
+ padding: 4px 0;
149
+ }
150
+
151
+ .sheerly-select optgroup legend {
152
+ padding: 4px 8px;
153
+ font-size: 12px;
154
+ line-height: 16px;
155
+ font-weight: 500;
156
+ color: var(--sheerly-muted-foreground);
157
+ }
158
+
159
+ /* Separator between groups */
160
+ .sheerly-select optgroup + optgroup {
161
+ border-top: 1px solid var(--sheerly-border);
162
+ margin-top: 4px;
163
+ padding-top: 4px;
164
+ }
package/src/select.tsx ADDED
@@ -0,0 +1,22 @@
1
+ import type { SelectHTMLAttributes } from 'react'
2
+ import { cn } from './cn'
3
+
4
+ export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
5
+ 'data-size'?: 'sm' | 'default' | 'lg'
6
+ }
7
+
8
+ /**
9
+ * Uses the customizable-select API. Requires
10
+ * `<button><selectedcontent></selectedcontent></button>` as the first child,
11
+ * which this wrapper injects before the consumer's <option>s.
12
+ */
13
+ export function Select({ className, children, ...props }: SelectProps) {
14
+ return (
15
+ <select className={cn('sheerly-select', className)} {...props}>
16
+ <button>
17
+ <selectedcontent />
18
+ </button>
19
+ {children}
20
+ </select>
21
+ )
22
+ }
@@ -0,0 +1,14 @@
1
+ /* Separator — thin dividing line */
2
+ .sheerly-separator {
3
+ flex-shrink: 0;
4
+ background-color: var(--sheerly-border);
5
+
6
+ /* Horizontal (default) */
7
+ height: 1px;
8
+ width: 100%;
9
+
10
+ &[data-orientation="vertical"] {
11
+ height: 100%;
12
+ width: 1px;
13
+ }
14
+ }
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from 'react'
2
+ import { cn } from './cn'
3
+
4
+ export interface SeparatorProps extends HTMLAttributes<HTMLDivElement> {
5
+ 'data-orientation'?: 'horizontal' | 'vertical'
6
+ }
7
+
8
+ export function Separator({ className, ...props }: SeparatorProps) {
9
+ return <div className={cn('sheerly-separator', className)} {...props} />
10
+ }
package/src/sheet.css ADDED
@@ -0,0 +1,225 @@
1
+ /*
2
+ * Sheet — a native <dialog> that slides in from an edge of the screen.
3
+ * Extends the Dialog pattern; open via invoker command (command="show-modal")
4
+ * or `.showModal()`.
5
+ *
6
+ * Side via data-side: right (default) | left | top | bottom.
7
+ *
8
+ * Usage:
9
+ * <dialog id="s" class="sheerly-sheet" data-side="right" closedby="any">
10
+ * <button class="sheerly-sheet-close" commandfor="s" command="close" aria-label="Close"></button>
11
+ * <div class="sheerly-sheet-header">
12
+ * <h3 class="sheerly-sheet-title">Edit profile</h3>
13
+ * <p class="sheerly-sheet-description">Make changes here.</p>
14
+ * </div>
15
+ * <div class="sheerly-sheet-body">…</div>
16
+ * <div class="sheerly-sheet-footer">…</div>
17
+ * </dialog>
18
+ */
19
+
20
+ .sheerly-sheet {
21
+ position: fixed;
22
+ z-index: 50;
23
+ flex-direction: column;
24
+ gap: 16px;
25
+ border: none;
26
+ background-color: var(--sheerly-background);
27
+ color: var(--sheerly-foreground);
28
+ box-shadow: 0 0 0 1px oklch(from var(--sheerly-foreground) l c h / 10%);
29
+ padding: 0;
30
+ margin: 0;
31
+ outline: none;
32
+ overflow: hidden;
33
+ font-size: 14px;
34
+ line-height: 20px;
35
+
36
+ /* Closed state */
37
+ display: none;
38
+ opacity: 0;
39
+ max-height: 100dvh;
40
+ max-width: 100vw;
41
+
42
+ transition:
43
+ translate 0.3s cubic-bezier(0.32, 0.72, 0, 1),
44
+ opacity 0.3s ease,
45
+ display 0.3s allow-discrete,
46
+ overlay 0.3s allow-discrete;
47
+
48
+ &[open] {
49
+ display: flex;
50
+ opacity: 1;
51
+ }
52
+
53
+ &::backdrop {
54
+ background-color: oklch(0 0 0 / 10%);
55
+ backdrop-filter: blur(1px);
56
+ opacity: 0;
57
+ transition: opacity 0.3s ease, overlay 0.3s allow-discrete, display 0.3s allow-discrete;
58
+ }
59
+
60
+ &[open]::backdrop {
61
+ opacity: 1;
62
+ }
63
+
64
+ @starting-style {
65
+ &[open]::backdrop {
66
+ opacity: 0;
67
+ }
68
+ }
69
+
70
+ /* --- Sides ------------------------------------------------------------ */
71
+
72
+ /* Right (default) */
73
+ inset: 0 0 0 auto;
74
+ height: 100dvh;
75
+ width: 100%;
76
+ max-width: 384px;
77
+ translate: 100% 0;
78
+
79
+ &[open] {
80
+ translate: 0 0;
81
+
82
+ @starting-style {
83
+ translate: 100% 0;
84
+ }
85
+ }
86
+
87
+ &[data-side="left"] {
88
+ inset: 0 auto 0 0;
89
+ height: 100dvh;
90
+ width: 100%;
91
+ max-width: 384px;
92
+ translate: -100% 0;
93
+
94
+ &[open] {
95
+ translate: 0 0;
96
+
97
+ @starting-style {
98
+ translate: -100% 0;
99
+ }
100
+ }
101
+ }
102
+
103
+ &[data-side="top"] {
104
+ inset: 0 0 auto 0;
105
+ width: 100%;
106
+ max-width: none;
107
+ height: auto;
108
+ translate: 0 -100%;
109
+
110
+ &[open] {
111
+ translate: 0 0;
112
+
113
+ @starting-style {
114
+ translate: 0 -100%;
115
+ }
116
+ }
117
+ }
118
+
119
+ &[data-side="bottom"] {
120
+ inset: auto 0 0 0;
121
+ width: 100%;
122
+ max-width: none;
123
+ height: auto;
124
+ translate: 0 100%;
125
+
126
+ &[open] {
127
+ translate: 0 0;
128
+
129
+ @starting-style {
130
+ translate: 0 100%;
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ .sheerly-sheet-header {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 4px;
140
+ padding: 16px;
141
+ flex-shrink: 0;
142
+ }
143
+
144
+ .sheerly-sheet-title {
145
+ font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
146
+ font-size: 16px;
147
+ font-weight: 500;
148
+ line-height: 1;
149
+ color: var(--sheerly-foreground);
150
+ margin: 0;
151
+ padding-right: 32px;
152
+ }
153
+
154
+ .sheerly-sheet-description {
155
+ font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
156
+ font-size: 14px;
157
+ font-weight: 400;
158
+ line-height: 20px;
159
+ color: var(--sheerly-muted-foreground);
160
+ margin: 0;
161
+ }
162
+
163
+ /* Scrollable content area */
164
+ .sheerly-sheet-body {
165
+ padding: 0 16px;
166
+ overflow-y: auto;
167
+ flex: 1;
168
+ }
169
+
170
+ .sheerly-sheet-footer {
171
+ display: flex;
172
+ flex-direction: column;
173
+ gap: 8px;
174
+ padding: 16px;
175
+ flex-shrink: 0;
176
+ margin-top: auto;
177
+
178
+ & > * {
179
+ width: 100%;
180
+ }
181
+ }
182
+
183
+ .sheerly-sheet-close {
184
+ position: absolute;
185
+ top: 12px;
186
+ right: 16px;
187
+ display: inline-flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ width: 28px;
191
+ height: 28px;
192
+ padding: 0;
193
+ border: none;
194
+ border-radius: var(--sheerly-radius-md);
195
+ background-color: transparent;
196
+ color: var(--sheerly-muted-foreground);
197
+ cursor: pointer;
198
+ transition: background-color 0.15s ease, color 0.15s ease;
199
+ outline: none;
200
+ z-index: 1;
201
+
202
+ &:hover {
203
+ background-color: var(--sheerly-muted);
204
+ color: var(--sheerly-foreground);
205
+ }
206
+
207
+ &:focus-visible {
208
+ border-color: var(--sheerly-ring);
209
+ box-shadow: var(--sheerly-focus-ring);
210
+ }
211
+
212
+ & svg {
213
+ width: 16px;
214
+ height: 16px;
215
+ }
216
+
217
+ /* Default close (X) icon when no child is provided */
218
+ &:empty::before {
219
+ content: "";
220
+ width: 16px;
221
+ height: 16px;
222
+ background-color: currentColor;
223
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E") center / contain no-repeat;
224
+ }
225
+ }
package/src/sheet.tsx ADDED
@@ -0,0 +1,48 @@
1
+ import type { ButtonHTMLAttributes, DialogHTMLAttributes, HTMLAttributes } from 'react'
2
+ import { cn } from './cn'
3
+
4
+ export interface SheetProps extends DialogHTMLAttributes<HTMLDialogElement> {
5
+ 'data-side'?: 'right' | 'left' | 'top' | 'bottom'
6
+ }
7
+
8
+ /**
9
+ * Native <dialog> that slides in from an edge. Open via invoker command
10
+ * (`command="show-modal"`) or `.showModal()`. Add `closedby="any"` for
11
+ * light-dismiss.
12
+ */
13
+ export function Sheet({ className, ...props }: SheetProps) {
14
+ return <dialog className={cn('sheerly-sheet', className)} {...props} />
15
+ }
16
+
17
+ export function SheetHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
18
+ return <div className={cn('sheerly-sheet-header', className)} {...props} />
19
+ }
20
+
21
+ export function SheetTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {
22
+ return <h3 className={cn('sheerly-sheet-title', className)} {...props} />
23
+ }
24
+
25
+ export function SheetDescription({
26
+ className,
27
+ ...props
28
+ }: HTMLAttributes<HTMLParagraphElement>) {
29
+ return <p className={cn('sheerly-sheet-description', className)} {...props} />
30
+ }
31
+
32
+ export function SheetBody({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
33
+ return <div className={cn('sheerly-sheet-body', className)} {...props} />
34
+ }
35
+
36
+ export function SheetFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
37
+ return <div className={cn('sheerly-sheet-footer', className)} {...props} />
38
+ }
39
+
40
+ export function SheetClose({
41
+ className,
42
+ type = 'button',
43
+ ...props
44
+ }: ButtonHTMLAttributes<HTMLButtonElement>) {
45
+ return (
46
+ <button type={type} className={cn('sheerly-sheet-close', className)} {...props} />
47
+ )
48
+ }
@@ -0,0 +1,15 @@
1
+ /* Skeleton — pulsing loading placeholder */
2
+ .sheerly-skeleton {
3
+ border-radius: var(--sheerly-radius-sm);
4
+ background-color: var(--sheerly-muted);
5
+ animation: sheerly-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
6
+ }
7
+
8
+ @keyframes sheerly-pulse {
9
+ 0%, 100% {
10
+ opacity: 1;
11
+ }
12
+ 50% {
13
+ opacity: 0.5;
14
+ }
15
+ }
@@ -0,0 +1,8 @@
1
+ import type { HTMLAttributes } from 'react'
2
+ import { cn } from './cn'
3
+
4
+ export type SkeletonProps = HTMLAttributes<HTMLDivElement>
5
+
6
+ export function Skeleton({ className, ...props }: SkeletonProps) {
7
+ return <div className={cn('sheerly-skeleton', className)} {...props} />
8
+ }
package/src/slider.css ADDED
@@ -0,0 +1,68 @@
1
+ /* Slider — native <input type="range"> (WebKit pseudo-elements only) */
2
+ .sheerly-slider {
3
+ -webkit-appearance: none;
4
+ appearance: none;
5
+ width: 100%;
6
+ height: 4px;
7
+ background-color: var(--sheerly-border);
8
+ border-radius: var(--sheerly-radius-full);
9
+ outline: none;
10
+ cursor: pointer;
11
+ margin: 8px 0;
12
+
13
+ &::-webkit-slider-runnable-track {
14
+ -webkit-appearance: none;
15
+ appearance: none;
16
+ height: 4px;
17
+ background-color: var(--sheerly-border);
18
+ border-radius: var(--sheerly-radius-full);
19
+ }
20
+
21
+ &::-webkit-slider-thumb {
22
+ -webkit-appearance: none;
23
+ appearance: none;
24
+ width: 16px;
25
+ height: 16px;
26
+ background-color: #ffffff;
27
+ border: 2px solid var(--sheerly-foreground);
28
+ border-radius: 50%;
29
+ cursor: grab;
30
+ margin-top: -6px;
31
+ transform: scale(0.85);
32
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
33
+ }
34
+
35
+ &:active::-webkit-slider-thumb {
36
+ transform: scale(1);
37
+ cursor: grabbing;
38
+ }
39
+
40
+ &:focus-visible::-webkit-slider-thumb {
41
+ transform: scale(1);
42
+ box-shadow: var(--sheerly-focus-ring);
43
+ }
44
+
45
+ &:disabled {
46
+ opacity: 0.5;
47
+ cursor: not-allowed;
48
+ }
49
+
50
+ &:disabled::-webkit-slider-thumb {
51
+ cursor: not-allowed;
52
+ border-color: var(--sheerly-input);
53
+ }
54
+ }
55
+
56
+ /* Dark mode */
57
+ .dark .sheerly-slider {
58
+ background-color: var(--sheerly-border);
59
+
60
+ &::-webkit-slider-runnable-track {
61
+ background-color: var(--sheerly-border);
62
+ }
63
+
64
+ &::-webkit-slider-thumb {
65
+ background-color: var(--sheerly-foreground);
66
+ border-color: var(--sheerly-foreground);
67
+ }
68
+ }
package/src/slider.tsx ADDED
@@ -0,0 +1,8 @@
1
+ import type { InputHTMLAttributes } from 'react'
2
+ import { cn } from './cn'
3
+
4
+ export type SliderProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>
5
+
6
+ export function Slider({ className, ...props }: SliderProps) {
7
+ return <input type="range" className={cn('sheerly-slider', className)} {...props} />
8
+ }
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Spinner — a spinning loading indicator.
3
+ *
4
+ * The spinner glyph is embedded in CSS (a masked ::before), so no <svg> child
5
+ * is required — a bare element works:
6
+ *
7
+ * <span class="sheerly-spinner" role="status" aria-label="Loading"></span>
8
+ *
9
+ * It inherits the current text color.
10
+ */
11
+ .sheerly-spinner {
12
+ display: inline-block;
13
+ width: 16px;
14
+ height: 16px;
15
+ flex-shrink: 0;
16
+ background-color: currentColor;
17
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-6.219-8.56'/%3E%3C/svg%3E") center / contain no-repeat;
18
+ animation: sheerly-spin 1s linear infinite;
19
+
20
+ &[data-size="sm"] {
21
+ width: 12px;
22
+ height: 12px;
23
+ }
24
+
25
+ &[data-size="md"] {
26
+ width: 20px;
27
+ height: 20px;
28
+ }
29
+
30
+ &[data-size="lg"] {
31
+ width: 24px;
32
+ height: 24px;
33
+ }
34
+ }
35
+
36
+ @keyframes sheerly-spin {
37
+ from {
38
+ rotate: 0deg;
39
+ }
40
+ to {
41
+ rotate: 360deg;
42
+ }
43
+ }
@@ -0,0 +1,24 @@
1
+ import type { HTMLAttributes } from 'react'
2
+ import { cn } from './cn'
3
+
4
+ export interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
5
+ 'data-size'?: 'sm' | 'default' | 'md' | 'lg'
6
+ }
7
+
8
+ /**
9
+ * Loading spinner. The glyph is embedded in CSS (masked ::before), so no SVG
10
+ * child is needed — this renders a bare <span>. It inherits the current text
11
+ * color. Pass an <svg> child if you prefer to ship your own icon.
12
+ */
13
+ export function Spinner({ className, children, ...props }: SpinnerProps) {
14
+ return (
15
+ <span
16
+ className={cn('sheerly-spinner', className)}
17
+ role="status"
18
+ aria-label="Loading"
19
+ {...props}
20
+ >
21
+ {children}
22
+ </span>
23
+ )
24
+ }