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.
- package/README.md +3 -0
- package/lib/accordion.css +1 -0
- package/lib/accordion.d.ts +6 -0
- package/lib/alert.css +1 -0
- package/lib/alert.d.ts +8 -0
- package/lib/avatar.css +1 -0
- package/lib/avatar.d.ts +13 -0
- package/lib/badge.css +1 -0
- package/lib/badge.d.ts +5 -0
- package/lib/breadcrumb.css +1 -0
- package/lib/breadcrumb.d.ts +8 -0
- package/lib/button.css +1 -0
- package/lib/button.d.ts +7 -0
- package/lib/card.css +1 -0
- package/lib/card.d.ts +14 -0
- package/lib/carousel.css +1 -0
- package/lib/carousel.d.ts +21 -0
- package/lib/checkbox.css +1 -0
- package/lib/checkbox.d.ts +3 -0
- package/lib/cn.d.ts +5 -0
- package/lib/dialog.css +1 -0
- package/lib/dialog.d.ts +15 -0
- package/lib/dropdown-menu.css +1 -0
- package/lib/dropdown-menu.d.ts +49 -0
- package/lib/hover-card.css +1 -0
- package/lib/hover-card.d.ts +7 -0
- package/lib/index.css +1 -0
- package/lib/index.d.ts +33 -0
- package/lib/index.js +652 -0
- package/lib/input.css +1 -0
- package/lib/input.d.ts +5 -0
- package/lib/kbd.css +1 -0
- package/lib/kbd.d.ts +5 -0
- package/lib/label.css +1 -0
- package/lib/label.d.ts +5 -0
- package/lib/pagination.css +1 -0
- package/lib/pagination.d.ts +17 -0
- package/lib/popover.css +1 -0
- package/lib/popover.d.ts +7 -0
- package/lib/progress.css +1 -0
- package/lib/progress.d.ts +6 -0
- package/lib/radio.css +1 -0
- package/lib/radio.d.ts +5 -0
- package/lib/root.css +1 -0
- package/lib/select.css +1 -0
- package/lib/select.d.ts +10 -0
- package/lib/separator.css +1 -0
- package/lib/separator.d.ts +5 -0
- package/lib/sheet.css +1 -0
- package/lib/sheet.d.ts +16 -0
- package/lib/skeleton.css +1 -0
- package/lib/skeleton.d.ts +3 -0
- package/lib/slider.css +1 -0
- package/lib/slider.d.ts +3 -0
- package/lib/spinner.css +1 -0
- package/lib/spinner.d.ts +10 -0
- package/lib/switch.css +1 -0
- package/lib/switch.d.ts +5 -0
- package/lib/table.css +1 -0
- package/lib/table.d.ts +5 -0
- package/lib/tabs.css +1 -0
- package/lib/tabs.d.ts +13 -0
- package/lib/textarea.css +1 -0
- package/lib/textarea.d.ts +3 -0
- package/lib/toggle.css +1 -0
- package/lib/toggle.d.ts +16 -0
- package/lib/tooltip.css +1 -0
- package/lib/tooltip.d.ts +7 -0
- package/lib/typography.css +1 -0
- package/lib/typography.d.ts +9 -0
- package/package.json +33 -0
- package/src/accordion.css +108 -0
- package/src/accordion.tsx +15 -0
- package/src/alert.css +126 -0
- package/src/alert.tsx +25 -0
- package/src/avatar.css +75 -0
- package/src/avatar.tsx +30 -0
- package/src/badge.css +71 -0
- package/src/badge.tsx +19 -0
- package/src/breadcrumb.css +166 -0
- package/src/breadcrumb.tsx +71 -0
- package/src/button.css +214 -0
- package/src/button.tsx +30 -0
- package/src/card.css +99 -0
- package/src/card.tsx +41 -0
- package/src/carousel.css +183 -0
- package/src/carousel.tsx +30 -0
- package/src/checkbox.css +56 -0
- package/src/checkbox.tsx +10 -0
- package/src/cn.ts +7 -0
- package/src/dialog.css +175 -0
- package/src/dialog.tsx +47 -0
- package/src/dropdown-menu.css +257 -0
- package/src/dropdown-menu.tsx +88 -0
- package/src/hover-card.css +75 -0
- package/src/hover-card.tsx +16 -0
- package/src/index.css +33 -0
- package/src/index.ts +37 -0
- package/src/input.css +62 -0
- package/src/input.tsx +10 -0
- package/src/kbd.css +32 -0
- package/src/kbd.tsx +14 -0
- package/src/label.css +17 -0
- package/src/label.tsx +10 -0
- package/src/pagination.css +150 -0
- package/src/pagination.tsx +95 -0
- package/src/popover.css +65 -0
- package/src/popover.tsx +16 -0
- package/src/progress.css +79 -0
- package/src/progress.tsx +19 -0
- package/src/radio.css +61 -0
- package/src/radio.tsx +14 -0
- package/src/root.css +78 -0
- package/src/select.css +164 -0
- package/src/select.tsx +22 -0
- package/src/separator.css +14 -0
- package/src/separator.tsx +10 -0
- package/src/sheet.css +225 -0
- package/src/sheet.tsx +48 -0
- package/src/skeleton.css +15 -0
- package/src/skeleton.tsx +8 -0
- package/src/slider.css +68 -0
- package/src/slider.tsx +8 -0
- package/src/spinner.css +43 -0
- package/src/spinner.tsx +24 -0
- package/src/switch.css +79 -0
- package/src/switch.tsx +18 -0
- package/src/table.css +77 -0
- package/src/table.tsx +14 -0
- package/src/tabs.css +110 -0
- package/src/tabs.tsx +38 -0
- package/src/textarea.css +38 -0
- package/src/textarea.tsx +8 -0
- package/src/toggle.css +148 -0
- package/src/toggle.tsx +47 -0
- package/src/tooltip.css +60 -0
- package/src/tooltip.tsx +16 -0
- package/src/types.d.ts +42 -0
- package/src/typography.css +176 -0
- package/src/typography.tsx +13 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Dropdown Menu — a click-triggered menu built on the native popover API
|
|
3
|
+
* (command/commandfor invoker) + CSS anchor positioning. Pure CSS, no JS.
|
|
4
|
+
*
|
|
5
|
+
* Open/close via an invoker button (command="toggle-popover"); light-dismiss
|
|
6
|
+
* (click-outside / Esc) comes free from popover="auto". Items are plain
|
|
7
|
+
* <button>/<a> rows — selecting one closes the menu (auto-popover behaviour).
|
|
8
|
+
*
|
|
9
|
+
* Submenus are nested popovers: a -sub-trigger opens its own -content[popover]
|
|
10
|
+
* (DOM-nested inside the parent -content, so Chrome keeps the ancestor chain
|
|
11
|
+
* open). The sub-trigger MUST use `popovertarget` (+ popovertargetaction) —
|
|
12
|
+
* NOT command/commandfor: the invoker-command API light-dismisses the parent
|
|
13
|
+
* when the submenu opens, whereas popovertarget preserves the nested chain.
|
|
14
|
+
* Anchored to the sub-trigger, opening inline-end.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* <span class="sheerly-dropdown-menu">
|
|
18
|
+
* <button class="sheerly-button" data-variant="outline"
|
|
19
|
+
* commandfor="menu" command="toggle-popover" aria-haspopup="menu">Open</button>
|
|
20
|
+
* <div class="sheerly-dropdown-menu-content" id="menu" popover role="menu">
|
|
21
|
+
* <div class="sheerly-dropdown-menu-group" role="group">
|
|
22
|
+
* <div class="sheerly-dropdown-menu-label">My Account</div>
|
|
23
|
+
* <button class="sheerly-dropdown-menu-item" role="menuitem">
|
|
24
|
+
* Profile <span class="sheerly-dropdown-menu-shortcut">⇧⌘P</span>
|
|
25
|
+
* </button>
|
|
26
|
+
* </div>
|
|
27
|
+
* <div class="sheerly-dropdown-menu-separator" role="separator"></div>
|
|
28
|
+
*
|
|
29
|
+
* <!-- Submenu: a -sub wrapping a -sub-trigger + a nested -content.
|
|
30
|
+
* The sub-trigger uses popovertarget (NOT command/commandfor). -->
|
|
31
|
+
* <div class="sheerly-dropdown-menu-sub">
|
|
32
|
+
* <button class="sheerly-dropdown-menu-sub-trigger" role="menuitem"
|
|
33
|
+
* popovertarget="sub" popovertargetaction="toggle" aria-haspopup="menu">Invite users</button>
|
|
34
|
+
* <div class="sheerly-dropdown-menu-content" id="sub" popover role="menu">
|
|
35
|
+
* <button class="sheerly-dropdown-menu-item" role="menuitem">Email</button>
|
|
36
|
+
* <button class="sheerly-dropdown-menu-item" role="menuitem">Message</button>
|
|
37
|
+
* </div>
|
|
38
|
+
* </div>
|
|
39
|
+
*
|
|
40
|
+
* <div class="sheerly-dropdown-menu-separator" role="separator"></div>
|
|
41
|
+
* <button class="sheerly-dropdown-menu-item" data-variant="destructive" role="menuitem">Delete</button>
|
|
42
|
+
* </div>
|
|
43
|
+
* </span>
|
|
44
|
+
*
|
|
45
|
+
* Position via --sheerly-dropdown-menu-position:
|
|
46
|
+
* block-start | block-end | inline-start | inline-end
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
.sheerly-dropdown-menu {
|
|
50
|
+
anchor-name: --sheerly-dropdown-menu;
|
|
51
|
+
display: inline-block;
|
|
52
|
+
width: fit-content;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.sheerly-dropdown-menu-content {
|
|
56
|
+
position: fixed;
|
|
57
|
+
position-anchor: --sheerly-dropdown-menu;
|
|
58
|
+
position-area: var(--sheerly-dropdown-menu-position, block-end);
|
|
59
|
+
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
|
60
|
+
position-visibility: anchors-visible;
|
|
61
|
+
inset: unset;
|
|
62
|
+
margin: 4px 0;
|
|
63
|
+
|
|
64
|
+
/* Popover surface (shadcn: min-w-32 rounded-lg bg-popover p-1 shadow-md ring-1).
|
|
65
|
+
Layout is `flex` only while open; the UA keeps it `display: none` when
|
|
66
|
+
closed, so a closed (esp. sub-)menu can't float over and block clicks. */
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
min-width: 128px;
|
|
69
|
+
max-height: var(--sheerly-dropdown-menu-max-height, auto);
|
|
70
|
+
padding: 4px;
|
|
71
|
+
border: none;
|
|
72
|
+
border-radius: var(--sheerly-radius-lg);
|
|
73
|
+
background-color: var(--sheerly-popover);
|
|
74
|
+
color: var(--sheerly-popover-foreground);
|
|
75
|
+
box-shadow:
|
|
76
|
+
inset 0 0 0 1px oklch(from var(--sheerly-foreground) l c h / 10%),
|
|
77
|
+
0 4px 16px -4px oklch(0 0 0 / 8%),
|
|
78
|
+
0 1px 2px oklch(0 0 0 / 4%);
|
|
79
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
line-height: 20px;
|
|
82
|
+
overflow-x: hidden;
|
|
83
|
+
overflow-y: auto;
|
|
84
|
+
|
|
85
|
+
/* Enter/exit animation (shadcn: fade-in-0 zoom-in-95) */
|
|
86
|
+
opacity: 0;
|
|
87
|
+
scale: 0.95;
|
|
88
|
+
transition:
|
|
89
|
+
opacity 0.1s ease,
|
|
90
|
+
scale 0.1s ease,
|
|
91
|
+
display 0.1s allow-discrete,
|
|
92
|
+
overlay 0.1s allow-discrete;
|
|
93
|
+
|
|
94
|
+
&:popover-open {
|
|
95
|
+
display: flex;
|
|
96
|
+
opacity: 1;
|
|
97
|
+
scale: 1;
|
|
98
|
+
|
|
99
|
+
@starting-style {
|
|
100
|
+
opacity: 0;
|
|
101
|
+
scale: 0.95;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Group — a semantic wrapper for a set of related items/labels. */
|
|
107
|
+
.sheerly-dropdown-menu-group {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Label — non-interactive section heading (shadcn: px-1.5 py-1 text-xs font-medium text-muted). */
|
|
113
|
+
.sheerly-dropdown-menu-label {
|
|
114
|
+
padding: 4px 6px;
|
|
115
|
+
font-size: 12px;
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
line-height: 16px;
|
|
118
|
+
color: var(--sheerly-muted-foreground);
|
|
119
|
+
|
|
120
|
+
&[data-inset] {
|
|
121
|
+
padding-left: 28px;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Item — an interactive row (shadcn: gap-1.5 rounded-md px-1.5 py-1 text-sm focus:bg-accent). */
|
|
126
|
+
.sheerly-dropdown-menu-item,
|
|
127
|
+
.sheerly-dropdown-menu-sub-trigger {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 6px;
|
|
131
|
+
width: 100%;
|
|
132
|
+
padding: 4px 6px;
|
|
133
|
+
border: none;
|
|
134
|
+
border-radius: var(--sheerly-radius-md);
|
|
135
|
+
background-color: transparent;
|
|
136
|
+
color: inherit;
|
|
137
|
+
font: inherit;
|
|
138
|
+
font-size: 14px;
|
|
139
|
+
line-height: 20px;
|
|
140
|
+
text-align: start;
|
|
141
|
+
text-decoration: none;
|
|
142
|
+
cursor: default;
|
|
143
|
+
user-select: none;
|
|
144
|
+
outline: none;
|
|
145
|
+
|
|
146
|
+
&:hover,
|
|
147
|
+
&:focus-visible {
|
|
148
|
+
background-color: var(--sheerly-accent);
|
|
149
|
+
color: var(--sheerly-accent-foreground);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&[disabled],
|
|
153
|
+
&[data-disabled],
|
|
154
|
+
&[aria-disabled="true"] {
|
|
155
|
+
pointer-events: none;
|
|
156
|
+
opacity: 0.5;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
&[data-inset] {
|
|
160
|
+
padding-left: 28px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
& svg {
|
|
164
|
+
pointer-events: none;
|
|
165
|
+
flex-shrink: 0;
|
|
166
|
+
width: 16px;
|
|
167
|
+
height: 16px;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Destructive item (shadcn: text-destructive focus:bg-destructive/10). */
|
|
172
|
+
.sheerly-dropdown-menu-item[data-variant="destructive"] {
|
|
173
|
+
color: var(--sheerly-destructive);
|
|
174
|
+
|
|
175
|
+
& svg {
|
|
176
|
+
color: var(--sheerly-destructive);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
&:hover,
|
|
180
|
+
&:focus-visible {
|
|
181
|
+
background-color: oklch(from var(--sheerly-destructive) l c h / 10%);
|
|
182
|
+
color: var(--sheerly-destructive);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.dark .sheerly-dropdown-menu-item[data-variant="destructive"] {
|
|
187
|
+
&:hover,
|
|
188
|
+
&:focus-visible {
|
|
189
|
+
background-color: oklch(from var(--sheerly-destructive) l c h / 20%);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Shortcut — trailing keyboard hint (shadcn: ml-auto text-xs tracking-widest text-muted). */
|
|
194
|
+
.sheerly-dropdown-menu-shortcut {
|
|
195
|
+
margin-inline-start: auto;
|
|
196
|
+
padding-inline-start: 12px;
|
|
197
|
+
font-size: 12px;
|
|
198
|
+
line-height: 16px;
|
|
199
|
+
letter-spacing: 0.1em;
|
|
200
|
+
color: var(--sheerly-muted-foreground);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.sheerly-dropdown-menu-item:hover .sheerly-dropdown-menu-shortcut,
|
|
204
|
+
.sheerly-dropdown-menu-item:focus-visible .sheerly-dropdown-menu-shortcut {
|
|
205
|
+
color: var(--sheerly-accent-foreground);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Separator (shadcn: -mx-1 my-1 h-px bg-border). */
|
|
209
|
+
.sheerly-dropdown-menu-separator {
|
|
210
|
+
height: 1px;
|
|
211
|
+
margin: 4px -4px;
|
|
212
|
+
background-color: var(--sheerly-border);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Submenu — the sub-trigger opens a nested -content anchored to itself. */
|
|
216
|
+
.sheerly-dropdown-menu-sub {
|
|
217
|
+
display: contents;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.sheerly-dropdown-menu-sub-trigger {
|
|
221
|
+
anchor-name: --sheerly-dropdown-menu-sub;
|
|
222
|
+
|
|
223
|
+
/* Trailing chevron (embedded in CSS via masked ::after — no <svg> needed). */
|
|
224
|
+
&::after {
|
|
225
|
+
content: "";
|
|
226
|
+
margin-inline-start: auto;
|
|
227
|
+
width: 16px;
|
|
228
|
+
height: 16px;
|
|
229
|
+
flex-shrink: 0;
|
|
230
|
+
background-color: currentColor;
|
|
231
|
+
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='m9 18 6-6-6-6'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Highlight the sub-trigger while its submenu is open (shadcn: data-popup-open:bg-accent).
|
|
236
|
+
No JS: the -sub wrapper reflects the nested popover's :popover-open via :has(). */
|
|
237
|
+
.sheerly-dropdown-menu-sub:has(> .sheerly-dropdown-menu-content:popover-open)
|
|
238
|
+
> .sheerly-dropdown-menu-sub-trigger,
|
|
239
|
+
.sheerly-dropdown-menu-sub-trigger[aria-expanded="true"] {
|
|
240
|
+
background-color: var(--sheerly-accent);
|
|
241
|
+
color: var(--sheerly-accent-foreground);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Sub-content: a narrower panel anchored to the sub-trigger, opening inline-end
|
|
245
|
+
(shadcn: side="right" align="start", min-w-[96px], shadow-lg). A submenu panel
|
|
246
|
+
is simply a -content nested directly inside a -sub wrapper — no data-* needed. */
|
|
247
|
+
.sheerly-dropdown-menu-sub > .sheerly-dropdown-menu-content {
|
|
248
|
+
position-anchor: --sheerly-dropdown-menu-sub;
|
|
249
|
+
position-area: inline-end span-block-end;
|
|
250
|
+
position-try-fallbacks: flip-inline;
|
|
251
|
+
margin: 0 4px;
|
|
252
|
+
min-width: 96px;
|
|
253
|
+
box-shadow:
|
|
254
|
+
inset 0 0 0 1px oklch(from var(--sheerly-foreground) l c h / 10%),
|
|
255
|
+
0 8px 24px -6px oklch(0 0 0 / 12%),
|
|
256
|
+
0 2px 4px oklch(0 0 0 / 6%);
|
|
257
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
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} />
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The floating menu panel. Give it `popover`, an `id` matched by the trigger's
|
|
11
|
+
* `commandfor`, and `role="menu"`. When nested inside a <DropdownMenuSub> it is
|
|
12
|
+
* automatically styled as a submenu panel (no extra attribute needed).
|
|
13
|
+
*/
|
|
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} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 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} />
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 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} />
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** An interactive menu row. Renders a <button> by default. */
|
|
35
|
+
export interface DropdownMenuItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
36
|
+
'data-variant'?: 'destructive'
|
|
37
|
+
'data-inset'?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function DropdownMenuItem({ className, type = 'button', ...props }: DropdownMenuItemProps) {
|
|
41
|
+
return <button type={type} className={cn('sheerly-dropdown-menu-item', className)} {...props} />
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** An interactive menu row rendered as a link (<a>). */
|
|
45
|
+
export interface DropdownMenuLinkItemProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
46
|
+
'data-variant'?: 'destructive'
|
|
47
|
+
'data-inset'?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function DropdownMenuLinkItem({ className, ...props }: DropdownMenuLinkItemProps) {
|
|
51
|
+
return <a className={cn('sheerly-dropdown-menu-item', className)} {...props} />
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 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} />
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 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} />
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 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} />
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Opens a submenu. Use `popoverTarget` (+ `popoverTargetAction="toggle"`)
|
|
71
|
+
* pointing at the sub-content id — NOT command/commandfor, which would
|
|
72
|
+
* light-dismiss the parent menu when the submenu opens.
|
|
73
|
+
*/
|
|
74
|
+
export interface DropdownMenuSubTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
75
|
+
'data-inset'?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function DropdownMenuSubTrigger({ className, type = 'button', ...props }: DropdownMenuSubTriggerProps) {
|
|
79
|
+
return <button type={type} className={cn('sheerly-dropdown-menu-sub-trigger', className)} {...props} />
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The submenu panel. It's a DropdownMenuContent nested inside a DropdownMenuSub;
|
|
84
|
+
* the child-combinator CSS picks it up as a submenu — no extra attribute needed.
|
|
85
|
+
*/
|
|
86
|
+
export function DropdownMenuSubContent({ className, ...props }: DropdownMenuContentProps) {
|
|
87
|
+
return <div className={cn('sheerly-dropdown-menu-content', className)} {...props} />
|
|
88
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hover Card — a hover/focus-triggered floating panel using
|
|
3
|
+
* popover="hint" + interestfor + anchor positioning.
|
|
4
|
+
*
|
|
5
|
+
* Like the tooltip it opens on hover (no JS, no click), but like the
|
|
6
|
+
* popover its content is a neutral card-like surface you fill with rich
|
|
7
|
+
* content (avatars, headings, links) — not a dark label.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* <span class="sheerly-hover-card">
|
|
11
|
+
* <a href="#" interestfor="hc">@nextjs</a>
|
|
12
|
+
* <div class="sheerly-hover-card-content" id="hc" popover="hint">
|
|
13
|
+
* The React Framework – created and maintained by @vercel.
|
|
14
|
+
* </div>
|
|
15
|
+
* </span>
|
|
16
|
+
*
|
|
17
|
+
* Position via --sheerly-hover-card-position:
|
|
18
|
+
* block-start | block-end | inline-start | inline-end
|
|
19
|
+
*
|
|
20
|
+
* Open/close delay via --sheerly-hover-card-delay (default 0.2s), applied to
|
|
21
|
+
* the trigger's `interest-delay`. Override per-instance for shadcn-style
|
|
22
|
+
* `delay`/`closeDelay` behaviour, e.g.
|
|
23
|
+
* style="--sheerly-hover-card-delay: 0.1s 0.3s"
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
.sheerly-hover-card {
|
|
27
|
+
anchor-name: --sheerly-hover-card;
|
|
28
|
+
anchor-scope: --sheerly-hover-card;
|
|
29
|
+
display: inline-block;
|
|
30
|
+
width: fit-content;
|
|
31
|
+
|
|
32
|
+
/* Small hover delay so the card doesn't flash on incidental pointer travel. */
|
|
33
|
+
& [interestfor] {
|
|
34
|
+
interest-delay: var(--sheerly-hover-card-delay, 0.2s);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.sheerly-hover-card-content {
|
|
39
|
+
position: fixed;
|
|
40
|
+
position-anchor: --sheerly-hover-card;
|
|
41
|
+
position-area: var(--sheerly-hover-card-position, block-end);
|
|
42
|
+
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
|
43
|
+
position-visibility: anchors-visible;
|
|
44
|
+
inset: unset;
|
|
45
|
+
margin: 6px;
|
|
46
|
+
|
|
47
|
+
/* Neutral card-like surface */
|
|
48
|
+
background-color: var(--sheerly-popover);
|
|
49
|
+
color: var(--sheerly-popover-foreground);
|
|
50
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
line-height: 20px;
|
|
53
|
+
width: max-content;
|
|
54
|
+
max-width: 288px;
|
|
55
|
+
padding: 16px;
|
|
56
|
+
border: 1px solid var(--sheerly-border);
|
|
57
|
+
border-radius: var(--sheerly-radius-md);
|
|
58
|
+
box-shadow: 0 4px 16px -4px oklch(0 0 0 / 8%), 0 1px 2px oklch(0 0 0 / 4%);
|
|
59
|
+
overflow: auto;
|
|
60
|
+
|
|
61
|
+
/* Popover animation — quick fade */
|
|
62
|
+
opacity: 0;
|
|
63
|
+
transition:
|
|
64
|
+
opacity 0.1s ease,
|
|
65
|
+
display 0.1s allow-discrete,
|
|
66
|
+
overlay 0.1s allow-discrete;
|
|
67
|
+
|
|
68
|
+
&:popover-open {
|
|
69
|
+
opacity: 1;
|
|
70
|
+
|
|
71
|
+
@starting-style {
|
|
72
|
+
opacity: 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type HoverCardProps = HTMLAttributes<HTMLSpanElement>
|
|
5
|
+
|
|
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} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type HoverCardContentProps = HTMLAttributes<HTMLDivElement>
|
|
12
|
+
|
|
13
|
+
/** 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} />
|
|
16
|
+
}
|
package/src/index.css
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@import "./root.css";
|
|
2
|
+
@import "./button.css";
|
|
3
|
+
@import "./input.css";
|
|
4
|
+
@import "./textarea.css";
|
|
5
|
+
@import "./label.css";
|
|
6
|
+
@import "./checkbox.css";
|
|
7
|
+
@import "./radio.css";
|
|
8
|
+
@import "./switch.css";
|
|
9
|
+
@import "./select.css";
|
|
10
|
+
@import "./dialog.css";
|
|
11
|
+
@import "./dropdown-menu.css";
|
|
12
|
+
@import "./sheet.css";
|
|
13
|
+
@import "./accordion.css";
|
|
14
|
+
@import "./slider.css";
|
|
15
|
+
@import "./progress.css";
|
|
16
|
+
@import "./kbd.css";
|
|
17
|
+
@import "./card.css";
|
|
18
|
+
@import "./table.css";
|
|
19
|
+
@import "./badge.css";
|
|
20
|
+
@import "./alert.css";
|
|
21
|
+
@import "./separator.css";
|
|
22
|
+
@import "./skeleton.css";
|
|
23
|
+
@import "./spinner.css";
|
|
24
|
+
@import "./avatar.css";
|
|
25
|
+
@import "./tooltip.css";
|
|
26
|
+
@import "./popover.css";
|
|
27
|
+
@import "./hover-card.css";
|
|
28
|
+
@import "./carousel.css";
|
|
29
|
+
@import "./tabs.css";
|
|
30
|
+
@import "./toggle.css";
|
|
31
|
+
@import "./pagination.css";
|
|
32
|
+
@import "./breadcrumb.css";
|
|
33
|
+
@import "./typography.css";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Sheerly React — optional, hookless typed wrappers around the CSS components.
|
|
2
|
+
// The core library is CSS-only; import this entry only if you want typed JSX.
|
|
3
|
+
// You still need to import the stylesheet: `import 'sheerly/index.css'`.
|
|
4
|
+
|
|
5
|
+
export * from './cn'
|
|
6
|
+
export * from './accordion'
|
|
7
|
+
export * from './alert'
|
|
8
|
+
export * from './avatar'
|
|
9
|
+
export * from './badge'
|
|
10
|
+
export * from './breadcrumb'
|
|
11
|
+
export * from './button'
|
|
12
|
+
export * from './card'
|
|
13
|
+
export * from './carousel'
|
|
14
|
+
export * from './checkbox'
|
|
15
|
+
export * from './dialog'
|
|
16
|
+
export * from './dropdown-menu'
|
|
17
|
+
export * from './hover-card'
|
|
18
|
+
export * from './input'
|
|
19
|
+
export * from './kbd'
|
|
20
|
+
export * from './label'
|
|
21
|
+
export * from './pagination'
|
|
22
|
+
export * from './popover'
|
|
23
|
+
export * from './progress'
|
|
24
|
+
export * from './radio'
|
|
25
|
+
export * from './select'
|
|
26
|
+
export * from './separator'
|
|
27
|
+
export * from './sheet'
|
|
28
|
+
export * from './skeleton'
|
|
29
|
+
export * from './slider'
|
|
30
|
+
export * from './spinner'
|
|
31
|
+
export * from './switch'
|
|
32
|
+
export * from './table'
|
|
33
|
+
export * from './tabs'
|
|
34
|
+
export * from './textarea'
|
|
35
|
+
export * from './toggle'
|
|
36
|
+
export * from './tooltip'
|
|
37
|
+
export * from './typography'
|
package/src/input.css
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* Input — <input> text field */
|
|
2
|
+
.sheerly-input {
|
|
3
|
+
width: 100%;
|
|
4
|
+
min-width: 0;
|
|
5
|
+
border-radius: var(--sheerly-radius-lg);
|
|
6
|
+
border: 1px solid var(--sheerly-input);
|
|
7
|
+
background-color: transparent;
|
|
8
|
+
padding: 0 10px;
|
|
9
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
10
|
+
font-size: 14px;
|
|
11
|
+
font-weight: 400;
|
|
12
|
+
line-height: 20px;
|
|
13
|
+
color: var(--sheerly-foreground);
|
|
14
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
15
|
+
outline: none;
|
|
16
|
+
|
|
17
|
+
/* Default size (32px) */
|
|
18
|
+
height: 32px;
|
|
19
|
+
|
|
20
|
+
&::placeholder {
|
|
21
|
+
color: var(--sheerly-muted-foreground);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:focus-visible {
|
|
25
|
+
border-color: var(--sheerly-ring);
|
|
26
|
+
box-shadow: var(--sheerly-focus-ring);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&:disabled {
|
|
30
|
+
pointer-events: none;
|
|
31
|
+
cursor: not-allowed;
|
|
32
|
+
opacity: 0.5;
|
|
33
|
+
background-color: oklch(from var(--sheerly-input) l c h / 50%);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&[aria-invalid="true"] {
|
|
37
|
+
border-color: var(--sheerly-destructive);
|
|
38
|
+
box-shadow: 0 0 0 3px oklch(from var(--sheerly-destructive) l c h / 20%);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&::file-selector-button {
|
|
42
|
+
display: inline-flex;
|
|
43
|
+
height: 24px;
|
|
44
|
+
border: 0;
|
|
45
|
+
background-color: transparent;
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
font-weight: 500;
|
|
48
|
+
color: var(--sheerly-foreground);
|
|
49
|
+
margin-right: 8px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* SM (28px) */
|
|
53
|
+
&[data-size="sm"] {
|
|
54
|
+
height: 28px;
|
|
55
|
+
font-size: 13px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* LG (36px) */
|
|
59
|
+
&[data-size="lg"] {
|
|
60
|
+
height: 36px;
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/input.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
5
|
+
'data-size'?: 'xs' | 'sm' | 'default' | 'lg'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Input({ className, ...props }: InputProps) {
|
|
9
|
+
return <input className={cn('sheerly-input', className)} {...props} />
|
|
10
|
+
}
|
package/src/kbd.css
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* Kbd — <kbd> key hint */
|
|
2
|
+
.sheerly-kbd {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
gap: 4px;
|
|
7
|
+
height: 20px;
|
|
8
|
+
min-width: 20px;
|
|
9
|
+
width: fit-content;
|
|
10
|
+
padding: 0 4px;
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
background-color: var(--sheerly-muted);
|
|
13
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
14
|
+
font-size: 12px;
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
line-height: 16px;
|
|
17
|
+
color: var(--sheerly-muted-foreground);
|
|
18
|
+
user-select: none;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
|
|
21
|
+
& svg {
|
|
22
|
+
width: 12px;
|
|
23
|
+
height: 12px;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Groups several keys into one combo (e.g. Ctrl+K) */
|
|
28
|
+
.sheerly-kbd-group {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 4px;
|
|
32
|
+
}
|
package/src/kbd.tsx
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type KbdProps = HTMLAttributes<HTMLElement>
|
|
5
|
+
|
|
6
|
+
export function Kbd({ className, ...props }: KbdProps) {
|
|
7
|
+
return <kbd className={cn('sheerly-kbd', className)} {...props} />
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type KbdGroupProps = HTMLAttributes<HTMLElement>
|
|
11
|
+
|
|
12
|
+
export function KbdGroup({ className, ...props }: KbdGroupProps) {
|
|
13
|
+
return <span className={cn('sheerly-kbd-group', className)} {...props} />
|
|
14
|
+
}
|
package/src/label.css
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Label — <label>; data-disabled="true" mutes it */
|
|
2
|
+
.sheerly-label {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
7
|
+
font-size: 14px;
|
|
8
|
+
font-weight: 400;
|
|
9
|
+
line-height: 20px;
|
|
10
|
+
color: var(--sheerly-foreground);
|
|
11
|
+
user-select: none;
|
|
12
|
+
|
|
13
|
+
&[data-disabled="true"] {
|
|
14
|
+
color: var(--sheerly-muted-foreground);
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/label.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LabelHTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
|
|
5
|
+
'data-disabled'?: 'true'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Label({ className, ...props }: LabelProps) {
|
|
9
|
+
return <label className={cn('sheerly-label', className)} {...props} />
|
|
10
|
+
}
|