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
package/src/card.css
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Card — content container */
|
|
2
|
+
.sheerly-card {
|
|
3
|
+
--sheerly-card-spacing: 16px;
|
|
4
|
+
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
gap: var(--sheerly-card-spacing);
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
border-radius: var(--sheerly-radius-xl);
|
|
10
|
+
background-color: var(--sheerly-card);
|
|
11
|
+
color: var(--sheerly-card-foreground);
|
|
12
|
+
font-size: 14px;
|
|
13
|
+
line-height: 20px;
|
|
14
|
+
box-shadow: inset 0 0 0 1px oklch(from var(--sheerly-foreground) l c h / 10%);
|
|
15
|
+
padding-block: var(--sheerly-card-spacing);
|
|
16
|
+
|
|
17
|
+
/* Remove bottom padding when footer is present */
|
|
18
|
+
&:has(.sheerly-card-footer) {
|
|
19
|
+
padding-bottom: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Remove top padding when first child is an image */
|
|
23
|
+
&:has(> img:first-child) {
|
|
24
|
+
padding-top: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&[data-size="sm"] {
|
|
28
|
+
--sheerly-card-spacing: 12px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
& > img:first-child {
|
|
32
|
+
border-radius: var(--sheerly-radius-xl) var(--sheerly-radius-xl) 0 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sheerly-card-header {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-rows: auto;
|
|
39
|
+
auto-rows: min-content;
|
|
40
|
+
align-items: start;
|
|
41
|
+
gap: 4px;
|
|
42
|
+
border-radius: var(--sheerly-radius-xl) var(--sheerly-radius-xl) 0 0;
|
|
43
|
+
padding-inline: var(--sheerly-card-spacing);
|
|
44
|
+
|
|
45
|
+
&:has(.sheerly-card-action) {
|
|
46
|
+
grid-template-columns: 1fr auto;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&:has(.sheerly-card-description) {
|
|
50
|
+
grid-template-rows: auto auto;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sheerly-card-title {
|
|
55
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
56
|
+
font-size: 16px;
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
line-height: 1.3;
|
|
59
|
+
color: var(--sheerly-card-foreground);
|
|
60
|
+
margin: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sheerly-card-description {
|
|
64
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
65
|
+
font-size: 14px;
|
|
66
|
+
font-weight: 400;
|
|
67
|
+
line-height: 20px;
|
|
68
|
+
color: var(--sheerly-muted-foreground);
|
|
69
|
+
margin: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.sheerly-card-action {
|
|
73
|
+
grid-column: 2;
|
|
74
|
+
grid-row: 1 / -1;
|
|
75
|
+
align-self: start;
|
|
76
|
+
justify-self: end;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sheerly-card-content {
|
|
80
|
+
padding-inline: var(--sheerly-card-spacing);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.sheerly-card-footer {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
border-radius: 0 0 var(--sheerly-radius-xl) var(--sheerly-radius-xl);
|
|
88
|
+
border-top: 1px solid var(--sheerly-border);
|
|
89
|
+
background-color: oklch(from var(--sheerly-muted) l c h / 50%);
|
|
90
|
+
padding: var(--sheerly-card-spacing);
|
|
91
|
+
|
|
92
|
+
&[data-direction="column"] {
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
|
|
95
|
+
& > * {
|
|
96
|
+
width: 100%;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/card.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
'data-size'?: 'default' | 'sm'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Card({ className, ...props }: CardProps) {
|
|
9
|
+
return <div className={cn('sheerly-card', className)} {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
13
|
+
return <div className={cn('sheerly-card-header', className)} {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {
|
|
17
|
+
return <h3 className={cn('sheerly-card-title', className)} {...props} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function CardDescription({
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}: HTMLAttributes<HTMLParagraphElement>) {
|
|
24
|
+
return <p className={cn('sheerly-card-description', className)} {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function CardAction({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
28
|
+
return <div className={cn('sheerly-card-action', className)} {...props} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
32
|
+
return <div className={cn('sheerly-card-content', className)} {...props} />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CardFooterProps extends HTMLAttributes<HTMLDivElement> {
|
|
36
|
+
'data-direction'?: 'column'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function CardFooter({ className, ...props }: CardFooterProps) {
|
|
40
|
+
return <div className={cn('sheerly-card-footer', className)} {...props} />
|
|
41
|
+
}
|
package/src/carousel.css
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Carousel — pure-CSS, no JS. Built on the CSS Overflow scroll features
|
|
3
|
+
* (scroll-snap + `::scroll-button()` + `::scroll-marker`/`::scroll-marker-group`)
|
|
4
|
+
* plus anchor positioning, per the MDN CSS Carousels guide:
|
|
5
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Overflow/Carousels
|
|
6
|
+
* Latest Chrome only.
|
|
7
|
+
*
|
|
8
|
+
* The carousel element IS the scroll container; its direct children ARE the
|
|
9
|
+
* slides — no per-slide class is required (a `<ul>` of `<li>`s or a `<div>` of
|
|
10
|
+
* `<div>`s both work; `.sheerly-carousel-item` is optional sugar). The browser
|
|
11
|
+
* generates the prev/next buttons (Radix chevrons, revealed on hover) and the
|
|
12
|
+
* dot markers (a pill group pinned to the bottom).
|
|
13
|
+
*
|
|
14
|
+
* <ul class="sheerly-carousel">
|
|
15
|
+
* <li data-name="Slide 1" style="width: 100%">…</li>
|
|
16
|
+
* <li data-name="Slide 2" style="width: 100%">…</li>
|
|
17
|
+
* </ul>
|
|
18
|
+
*
|
|
19
|
+
* `data-name` on a slide supplies its marker's accessible name (WCAG).
|
|
20
|
+
* Slide width: `.sheerly-carousel-item` defaults to `width: 100%` (one per
|
|
21
|
+
* page); override with your own `width` (e.g. 50% for two). Bare children get
|
|
22
|
+
* no default. The component sets NO gap and no width on `& > *` (a fixed basis
|
|
23
|
+
* + gap breaks first/last snapping, so buttons wouldn't disable / dots
|
|
24
|
+
* wouldn't highlight). Set height/aspect-ratio on the items yourself.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
.sheerly-carousel {
|
|
28
|
+
anchor-name: --sheerly-carousel;
|
|
29
|
+
anchor-scope: --sheerly-carousel;
|
|
30
|
+
|
|
31
|
+
overflow: auto hidden;
|
|
32
|
+
scrollbar-width: none;
|
|
33
|
+
overscroll-behavior-x: contain;
|
|
34
|
+
scroll-snap-type: x mandatory;
|
|
35
|
+
scroll-behavior: smooth;
|
|
36
|
+
scroll-marker-group: after;
|
|
37
|
+
|
|
38
|
+
/* Reset in case of ul/ol. */
|
|
39
|
+
list-style: none;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
|
|
43
|
+
display: flex;
|
|
44
|
+
|
|
45
|
+
&::scroll-button(*),
|
|
46
|
+
&::scroll-marker-group {
|
|
47
|
+
-webkit-tap-highlight-color: transparent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&::-webkit-scrollbar {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* A scroller's direct children ARE the slides: non-shrinking snap targets.
|
|
55
|
+
* The AUTHOR sets each slide's width (e.g. `width: 100%` for one-per-page,
|
|
56
|
+
* `50%` for two). Don't force a flex-basis here or gap — a fixed basis + gap
|
|
57
|
+
* makes the first/last snap positions unreachable, so the browser can't
|
|
58
|
+
* disable the prev/next buttons or mark the first/last dot :target-current. */
|
|
59
|
+
& > * {
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
flex-shrink: 0;
|
|
62
|
+
list-style-type: none;
|
|
63
|
+
scroll-snap-align: center;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* The opt-in item class defaults to one slide per page. Override with your
|
|
67
|
+
* own `width` (e.g. 50%) — bare children get no default so authors keep
|
|
68
|
+
* full control. */
|
|
69
|
+
& > .sheerly-carousel-item {
|
|
70
|
+
width: 100%;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ----- Prev / Next scroll buttons ----- */
|
|
74
|
+
&::scroll-button(*) {
|
|
75
|
+
all: unset;
|
|
76
|
+
position: fixed;
|
|
77
|
+
position-anchor: --sheerly-carousel;
|
|
78
|
+
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
width: 2rem;
|
|
81
|
+
height: 2rem;
|
|
82
|
+
line-height: 0;
|
|
83
|
+
text-align: center;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
color: var(--sheerly-foreground);
|
|
86
|
+
background-color: var(--sheerly-background);
|
|
87
|
+
border: 1px solid var(--sheerly-border);
|
|
88
|
+
border-radius: var(--sheerly-radius-full);
|
|
89
|
+
box-shadow: 0 1px 2px oklch(0 0 0 / 6%);
|
|
90
|
+
transition:
|
|
91
|
+
background-color 0.15s ease,
|
|
92
|
+
opacity 0.15s ease;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&::scroll-button(*):not(:disabled):hover {
|
|
96
|
+
background-color: var(--sheerly-accent);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&::scroll-button(*):not(:disabled):focus-visible {
|
|
100
|
+
border-color: var(--sheerly-ring);
|
|
101
|
+
box-shadow: var(--sheerly-focus-ring);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&::scroll-button(*):not(:disabled):active {
|
|
105
|
+
translate: 0 1px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&::scroll-button(*):disabled {
|
|
109
|
+
cursor: not-allowed;
|
|
110
|
+
opacity: 0.4;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Radix Icons — MIT LICENSE. Recolored via currentColor. */
|
|
114
|
+
&::scroll-button(left) {
|
|
115
|
+
content: url('data:image/svg+xml;utf8,<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>') / "Previous slide";
|
|
116
|
+
left: calc(anchor(left) - 2.5rem);
|
|
117
|
+
top: anchor(center);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
&::scroll-button(right) {
|
|
121
|
+
content: url('data:image/svg+xml;utf8,<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>') / "Next slide";
|
|
122
|
+
right: calc(anchor(right) - 2.5rem);
|
|
123
|
+
top: anchor(center);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Reveal on hover / keyboard focus only. */
|
|
127
|
+
&:not(:hover):not(:focus-within)::scroll-button(*) {
|
|
128
|
+
opacity: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ----- Dot markers (pill group pinned to the bottom) ----- */
|
|
132
|
+
&:empty::scroll-marker-group {
|
|
133
|
+
display: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
&::scroll-marker-group {
|
|
137
|
+
position: absolute;
|
|
138
|
+
position-anchor: --sheerly-carousel;
|
|
139
|
+
position-area: bottom center;
|
|
140
|
+
transform: translateY(-0.75rem);
|
|
141
|
+
|
|
142
|
+
display: grid;
|
|
143
|
+
grid-auto-flow: column;
|
|
144
|
+
gap: 0.4rem;
|
|
145
|
+
max-inline-size: 80%;
|
|
146
|
+
padding: 0.3rem 0.5rem;
|
|
147
|
+
|
|
148
|
+
background-color: oklch(from var(--sheerly-background) l c h / 70%);
|
|
149
|
+
border: 1px solid var(--sheerly-border);
|
|
150
|
+
border-radius: var(--sheerly-radius-full);
|
|
151
|
+
backdrop-filter: blur(0.5rem);
|
|
152
|
+
|
|
153
|
+
/* Scrollable if there are more dots than fit. */
|
|
154
|
+
overflow-x: auto;
|
|
155
|
+
scrollbar-width: none;
|
|
156
|
+
scroll-behavior: smooth;
|
|
157
|
+
scroll-snap-type: x mandatory;
|
|
158
|
+
overscroll-behavior-x: contain;
|
|
159
|
+
scroll-padding-inline: 50%;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
& > *::scroll-marker {
|
|
163
|
+
content: attr(data-name);
|
|
164
|
+
box-sizing: border-box;
|
|
165
|
+
width: 0.5rem;
|
|
166
|
+
height: 0.5rem;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
text-indent: 1rem;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
background-color: var(--sheerly-border);
|
|
171
|
+
border-radius: var(--sheerly-radius-full);
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
transition: background-color 0.15s ease;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
& > *::scroll-marker:not(:target-current):hover {
|
|
177
|
+
background-color: var(--sheerly-muted-foreground);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
& > *::scroll-marker:target-current {
|
|
181
|
+
background-color: var(--sheerly-primary);
|
|
182
|
+
}
|
|
183
|
+
}
|
package/src/carousel.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type CarouselProps = HTMLAttributes<HTMLDivElement>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pure-CSS carousel. The element itself is the scroll container — put
|
|
8
|
+
* `<CarouselItem>`s (or any children) directly inside. The browser generates
|
|
9
|
+
* the prev/next buttons and the dot markers via `::scroll-button()` /
|
|
10
|
+
* `::scroll-marker` — no JS.
|
|
11
|
+
*
|
|
12
|
+
* `<CarouselItem>` defaults to `width: 100%` (one slide per page); override
|
|
13
|
+
* with your own `width` (e.g. `50%` for two) plus a height / aspect-ratio. The
|
|
14
|
+
* component sets no gap on purpose (a fixed basis + gap breaks first/last
|
|
15
|
+
* snapping, so the prev/next buttons wouldn't disable and end dots wouldn't
|
|
16
|
+
* highlight).
|
|
17
|
+
*/
|
|
18
|
+
export function Carousel({ className, ...props }: CarouselProps) {
|
|
19
|
+
return <div className={cn('sheerly-carousel', className)} {...props} />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CarouselItemProps extends HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
/** Accessible name for this slide's generated scroll marker (WCAG). */
|
|
24
|
+
'data-name'?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A slide. Defaults to `width: 100%` (one per page); override via `style`/`className`. */
|
|
28
|
+
export function CarouselItem({ className, ...props }: CarouselItemProps) {
|
|
29
|
+
return <div className={cn('sheerly-carousel-item', className)} {...props} />
|
|
30
|
+
}
|
package/src/checkbox.css
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Checkbox — <input type="checkbox">; supports :checked and [data-state="checked"] */
|
|
2
|
+
.sheerly-checkbox {
|
|
3
|
+
width: 16px;
|
|
4
|
+
height: 16px;
|
|
5
|
+
flex-shrink: 0;
|
|
6
|
+
border-radius: 4px;
|
|
7
|
+
border: 1px solid var(--sheerly-input);
|
|
8
|
+
background-color: var(--sheerly-background);
|
|
9
|
+
transition: background-color 0.15s ease, border-color 0.15s ease;
|
|
10
|
+
outline: none;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
appearance: none;
|
|
13
|
+
display: grid;
|
|
14
|
+
place-content: center;
|
|
15
|
+
|
|
16
|
+
&::before {
|
|
17
|
+
content: "";
|
|
18
|
+
width: 10px;
|
|
19
|
+
height: 10px;
|
|
20
|
+
transform: scale(0);
|
|
21
|
+
transition: transform 0.1s ease;
|
|
22
|
+
background-color: var(--sheerly-background);
|
|
23
|
+
clip-path: polygon(14% 44%, 0% 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:hover {
|
|
27
|
+
border-color: var(--sheerly-border);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:checked,
|
|
31
|
+
&[data-state="checked"] {
|
|
32
|
+
background-color: var(--sheerly-foreground);
|
|
33
|
+
border-color: var(--sheerly-foreground);
|
|
34
|
+
|
|
35
|
+
&::before {
|
|
36
|
+
transform: scale(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&:focus-visible {
|
|
41
|
+
border-color: var(--sheerly-ring);
|
|
42
|
+
box-shadow: var(--sheerly-focus-ring);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&:disabled {
|
|
46
|
+
background-color: var(--sheerly-muted);
|
|
47
|
+
border-color: var(--sheerly-border);
|
|
48
|
+
cursor: not-allowed;
|
|
49
|
+
opacity: 0.5;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&[aria-invalid="true"] {
|
|
53
|
+
border-color: var(--sheerly-destructive);
|
|
54
|
+
box-shadow: 0 0 0 3px oklch(from var(--sheerly-destructive) l c h / 20%);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/checkbox.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type CheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>
|
|
5
|
+
|
|
6
|
+
export function Checkbox({ className, ...props }: CheckboxProps) {
|
|
7
|
+
return (
|
|
8
|
+
<input type="checkbox" className={cn('sheerly-checkbox', className)} {...props} />
|
|
9
|
+
)
|
|
10
|
+
}
|
package/src/cn.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Join class names, skipping falsy values. No dependency, no hooks.
|
|
3
|
+
* Lets a consumer's `className` extend (not replace) a component's base class.
|
|
4
|
+
*/
|
|
5
|
+
export function cn(...classes: Array<string | false | null | undefined>): string {
|
|
6
|
+
return classes.filter(Boolean).join(' ')
|
|
7
|
+
}
|
package/src/dialog.css
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Dialog — native <dialog> element.
|
|
3
|
+
* Open via invoker command (command="show-modal") or `.showModal()`.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* <dialog id="d" class="sheerly-dialog" closedby="any">
|
|
7
|
+
* <button class="sheerly-dialog-close" commandfor="d" command="close" aria-label="Close"></button>
|
|
8
|
+
* <div class="sheerly-dialog-header">
|
|
9
|
+
* <h3 class="sheerly-dialog-title">Title</h3>
|
|
10
|
+
* <p class="sheerly-dialog-description">Description.</p>
|
|
11
|
+
* </div>
|
|
12
|
+
* <div class="sheerly-dialog-body">…</div>
|
|
13
|
+
* <div class="sheerly-dialog-footer">…</div>
|
|
14
|
+
* </dialog>
|
|
15
|
+
*/
|
|
16
|
+
.sheerly-dialog {
|
|
17
|
+
position: fixed;
|
|
18
|
+
top: 50%;
|
|
19
|
+
left: 50%;
|
|
20
|
+
translate: -50% -50%;
|
|
21
|
+
z-index: 50;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
width: calc(100% - 32px);
|
|
24
|
+
max-width: 384px;
|
|
25
|
+
max-height: calc(100dvh - 64px);
|
|
26
|
+
border: none;
|
|
27
|
+
border-radius: var(--sheerly-radius-xl);
|
|
28
|
+
background-color: var(--sheerly-popover);
|
|
29
|
+
color: var(--sheerly-popover-foreground);
|
|
30
|
+
box-shadow: 0 0 0 1px oklch(from var(--sheerly-foreground) l c h / 10%);
|
|
31
|
+
padding: 0;
|
|
32
|
+
margin: 0;
|
|
33
|
+
outline: none;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
font-size: 14px;
|
|
36
|
+
line-height: 20px;
|
|
37
|
+
|
|
38
|
+
/* Closed state */
|
|
39
|
+
display: none;
|
|
40
|
+
opacity: 0;
|
|
41
|
+
scale: 0.95;
|
|
42
|
+
|
|
43
|
+
transition:
|
|
44
|
+
opacity 0.1s ease,
|
|
45
|
+
scale 0.1s ease,
|
|
46
|
+
display 0.1s allow-discrete,
|
|
47
|
+
overlay 0.1s allow-discrete;
|
|
48
|
+
|
|
49
|
+
&[open] {
|
|
50
|
+
display: flex;
|
|
51
|
+
opacity: 1;
|
|
52
|
+
scale: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@starting-style {
|
|
56
|
+
&[open] {
|
|
57
|
+
opacity: 0;
|
|
58
|
+
scale: 0.95;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&::backdrop {
|
|
63
|
+
background-color: oklch(0 0 0 / 10%);
|
|
64
|
+
backdrop-filter: blur(1px);
|
|
65
|
+
opacity: 0;
|
|
66
|
+
transition: opacity 0.1s ease, overlay 0.1s allow-discrete, display 0.1s allow-discrete;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&[open]::backdrop {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@starting-style {
|
|
74
|
+
&[open]::backdrop {
|
|
75
|
+
opacity: 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.sheerly-dialog-header {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
gap: 4px;
|
|
84
|
+
padding: 16px 16px 0;
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sheerly-dialog-title {
|
|
89
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
90
|
+
font-size: 16px;
|
|
91
|
+
font-weight: 500;
|
|
92
|
+
line-height: 1;
|
|
93
|
+
color: var(--sheerly-popover-foreground);
|
|
94
|
+
margin: 0;
|
|
95
|
+
padding-right: 32px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sheerly-dialog-description {
|
|
99
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
font-weight: 400;
|
|
102
|
+
line-height: 20px;
|
|
103
|
+
color: var(--sheerly-muted-foreground);
|
|
104
|
+
margin: 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Scrollable content area */
|
|
108
|
+
.sheerly-dialog-body {
|
|
109
|
+
padding: 16px;
|
|
110
|
+
overflow-y: auto;
|
|
111
|
+
flex: 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.sheerly-dialog-footer {
|
|
115
|
+
display: flex;
|
|
116
|
+
justify-content: flex-end;
|
|
117
|
+
gap: 8px;
|
|
118
|
+
padding: 16px;
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
border-top: 1px solid var(--sheerly-border);
|
|
121
|
+
border-radius: 0 0 var(--sheerly-radius-xl) var(--sheerly-radius-xl);
|
|
122
|
+
background-color: oklch(from var(--sheerly-muted) l c h / 50%);
|
|
123
|
+
|
|
124
|
+
&[data-direction="column"] {
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
|
|
127
|
+
& > * {
|
|
128
|
+
width: 100%;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.sheerly-dialog-close {
|
|
134
|
+
position: absolute;
|
|
135
|
+
top: 8px;
|
|
136
|
+
right: 8px;
|
|
137
|
+
display: inline-flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
width: 28px;
|
|
141
|
+
height: 28px;
|
|
142
|
+
padding: 0;
|
|
143
|
+
border: none;
|
|
144
|
+
border-radius: var(--sheerly-radius-md);
|
|
145
|
+
background-color: transparent;
|
|
146
|
+
color: var(--sheerly-muted-foreground);
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
149
|
+
outline: none;
|
|
150
|
+
z-index: 1;
|
|
151
|
+
|
|
152
|
+
&:hover {
|
|
153
|
+
background-color: var(--sheerly-muted);
|
|
154
|
+
color: var(--sheerly-foreground);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
&:focus-visible {
|
|
158
|
+
border-color: var(--sheerly-ring);
|
|
159
|
+
box-shadow: var(--sheerly-focus-ring);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
& svg {
|
|
163
|
+
width: 16px;
|
|
164
|
+
height: 16px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Default close (X) icon when no child is provided */
|
|
168
|
+
&:empty::before {
|
|
169
|
+
content: "";
|
|
170
|
+
width: 16px;
|
|
171
|
+
height: 16px;
|
|
172
|
+
background-color: currentColor;
|
|
173
|
+
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;
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/dialog.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, DialogHTMLAttributes, HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Native <dialog>. Open via invoker command (`command="show-modal"`) or
|
|
6
|
+
* `.showModal()`. Add `closedby="any"` for light-dismiss.
|
|
7
|
+
*/
|
|
8
|
+
export function Dialog({ className, ...props }: DialogHTMLAttributes<HTMLDialogElement>) {
|
|
9
|
+
return <dialog className={cn('sheerly-dialog', className)} {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function DialogHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
13
|
+
return <div className={cn('sheerly-dialog-header', className)} {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function DialogTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {
|
|
17
|
+
return <h3 className={cn('sheerly-dialog-title', className)} {...props} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function DialogDescription({
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}: HTMLAttributes<HTMLParagraphElement>) {
|
|
24
|
+
return <p className={cn('sheerly-dialog-description', className)} {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function DialogBody({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
28
|
+
return <div className={cn('sheerly-dialog-body', className)} {...props} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DialogFooterProps extends HTMLAttributes<HTMLDivElement> {
|
|
32
|
+
'data-direction'?: 'column'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function DialogFooter({ className, ...props }: DialogFooterProps) {
|
|
36
|
+
return <div className={cn('sheerly-dialog-footer', className)} {...props} />
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function DialogClose({
|
|
40
|
+
className,
|
|
41
|
+
type = 'button',
|
|
42
|
+
...props
|
|
43
|
+
}: ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
44
|
+
return (
|
|
45
|
+
<button type={type} className={cn('sheerly-dialog-close', className)} {...props} />
|
|
46
|
+
)
|
|
47
|
+
}
|