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/switch.css
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Switch — <input type="checkbox" role="switch"> */
|
|
2
|
+
.sheerly-switch {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
flex-shrink: 0;
|
|
5
|
+
align-items: center;
|
|
6
|
+
border-radius: var(--sheerly-radius-full);
|
|
7
|
+
border: none;
|
|
8
|
+
transition: background-color 0.15s ease;
|
|
9
|
+
outline: none;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
appearance: none;
|
|
12
|
+
position: relative;
|
|
13
|
+
|
|
14
|
+
/* Default size */
|
|
15
|
+
width: 32px;
|
|
16
|
+
height: 18px;
|
|
17
|
+
background-color: var(--sheerly-input);
|
|
18
|
+
|
|
19
|
+
&::before {
|
|
20
|
+
content: "";
|
|
21
|
+
display: block;
|
|
22
|
+
border-radius: var(--sheerly-radius-full);
|
|
23
|
+
background-color: var(--sheerly-background);
|
|
24
|
+
transition: transform 0.15s ease;
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
position: absolute;
|
|
27
|
+
left: 2px;
|
|
28
|
+
top: 50%;
|
|
29
|
+
transform: translateY(-50%);
|
|
30
|
+
|
|
31
|
+
/* Default size thumb */
|
|
32
|
+
width: 14px;
|
|
33
|
+
height: 14px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&:hover {
|
|
37
|
+
background-color: var(--sheerly-ring);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&:checked,
|
|
41
|
+
&[data-state="checked"] {
|
|
42
|
+
background-color: var(--sheerly-foreground);
|
|
43
|
+
|
|
44
|
+
&::before {
|
|
45
|
+
transform: translateX(14px) translateY(-50%);
|
|
46
|
+
background-color: var(--sheerly-background);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&:focus-visible {
|
|
51
|
+
box-shadow: var(--sheerly-focus-ring);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&:disabled {
|
|
55
|
+
background-color: var(--sheerly-muted);
|
|
56
|
+
cursor: not-allowed;
|
|
57
|
+
|
|
58
|
+
&::before {
|
|
59
|
+
background-color: var(--sheerly-input);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&[data-size="sm"] {
|
|
64
|
+
width: 24px;
|
|
65
|
+
height: 14px;
|
|
66
|
+
|
|
67
|
+
&::before {
|
|
68
|
+
width: 10px;
|
|
69
|
+
height: 10px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&:checked,
|
|
73
|
+
&[data-state="checked"] {
|
|
74
|
+
&::before {
|
|
75
|
+
transform: translateX(10px) translateY(-50%);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/switch.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export interface SwitchProps
|
|
5
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'role'> {
|
|
6
|
+
'data-size'?: 'default' | 'sm'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Switch({ className, ...props }: SwitchProps) {
|
|
10
|
+
return (
|
|
11
|
+
<input
|
|
12
|
+
type="checkbox"
|
|
13
|
+
role="switch"
|
|
14
|
+
className={cn('sheerly-switch', className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
package/src/table.css
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* Wrapper enabling horizontal scroll on overflow */
|
|
2
|
+
.sheerly-table-container {
|
|
3
|
+
position: relative;
|
|
4
|
+
width: 100%;
|
|
5
|
+
overflow-x: auto;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Table — semantic <table>, styled via element selectors */
|
|
9
|
+
.sheerly-table {
|
|
10
|
+
width: 100%;
|
|
11
|
+
caption-side: bottom;
|
|
12
|
+
border-collapse: collapse;
|
|
13
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
14
|
+
font-size: 14px;
|
|
15
|
+
line-height: 20px;
|
|
16
|
+
color: var(--sheerly-foreground);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sheerly-table caption {
|
|
20
|
+
margin-top: 16px;
|
|
21
|
+
font-size: 14px;
|
|
22
|
+
line-height: 20px;
|
|
23
|
+
color: var(--sheerly-muted-foreground);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sheerly-table thead tr {
|
|
27
|
+
border-bottom: 1px solid var(--sheerly-border);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sheerly-table th {
|
|
31
|
+
height: 40px;
|
|
32
|
+
padding: 0 8px;
|
|
33
|
+
text-align: left;
|
|
34
|
+
vertical-align: middle;
|
|
35
|
+
font-weight: 500;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
color: var(--sheerly-foreground);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sheerly-table tbody tr {
|
|
41
|
+
border-bottom: 1px solid var(--sheerly-border);
|
|
42
|
+
transition: background-color 0.1s ease;
|
|
43
|
+
|
|
44
|
+
&:last-child {
|
|
45
|
+
border-bottom: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&:hover {
|
|
49
|
+
background-color: var(--sheerly-accent);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&[data-state="selected"] {
|
|
53
|
+
background-color: var(--sheerly-muted);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sheerly-table td {
|
|
58
|
+
padding: 8px;
|
|
59
|
+
vertical-align: middle;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sheerly-table tfoot {
|
|
64
|
+
border-top: 1px solid var(--sheerly-border);
|
|
65
|
+
background-color: var(--sheerly-accent);
|
|
66
|
+
font-weight: 500;
|
|
67
|
+
|
|
68
|
+
& tr:last-child {
|
|
69
|
+
border-bottom: none;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.sheerly-table tfoot td,
|
|
74
|
+
.sheerly-table tfoot th {
|
|
75
|
+
padding: 8px;
|
|
76
|
+
vertical-align: middle;
|
|
77
|
+
}
|
package/src/table.tsx
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HTMLAttributes, TableHTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type TableContainerProps = HTMLAttributes<HTMLDivElement>
|
|
5
|
+
|
|
6
|
+
export function TableContainer({ className, ...props }: TableContainerProps) {
|
|
7
|
+
return <div className={cn('sheerly-table-container', className)} {...props} />
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type TableProps = TableHTMLAttributes<HTMLTableElement>
|
|
11
|
+
|
|
12
|
+
export function Table({ className, ...props }: TableProps) {
|
|
13
|
+
return <table className={cn('sheerly-table', className)} {...props} />
|
|
14
|
+
}
|
package/src/tabs.css
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Tabs — <div> container; list + triggers + content panels */
|
|
2
|
+
.sheerly-tabs {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* The trigger bar */
|
|
9
|
+
.sheerly-tabs-list {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
width: fit-content;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
border-radius: 8px;
|
|
15
|
+
padding: 3px;
|
|
16
|
+
background-color: var(--sheerly-muted);
|
|
17
|
+
height: 36px;
|
|
18
|
+
|
|
19
|
+
&[data-variant="line"] {
|
|
20
|
+
background-color: transparent;
|
|
21
|
+
border-radius: 0;
|
|
22
|
+
gap: 4px;
|
|
23
|
+
padding: 0;
|
|
24
|
+
border-bottom: 1px solid var(--sheerly-border);
|
|
25
|
+
height: auto;
|
|
26
|
+
padding-bottom: 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sheerly-tabs-trigger {
|
|
31
|
+
position: relative;
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
flex: 1;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
gap: 6px;
|
|
37
|
+
border-radius: 6px;
|
|
38
|
+
border: 1px solid transparent;
|
|
39
|
+
padding: 4px 8px;
|
|
40
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
41
|
+
font-size: 14px;
|
|
42
|
+
font-weight: 500;
|
|
43
|
+
line-height: 20px;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
color: var(--sheerly-muted-foreground);
|
|
46
|
+
background-color: transparent;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
outline: none;
|
|
49
|
+
transition: color 0.15s ease, background-color 0.15s ease;
|
|
50
|
+
|
|
51
|
+
&:hover {
|
|
52
|
+
color: var(--sheerly-foreground);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&:focus-visible {
|
|
56
|
+
box-shadow: var(--sheerly-focus-ring);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&:disabled {
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
opacity: 0.5;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Active state */
|
|
65
|
+
&[aria-selected="true"],
|
|
66
|
+
&[data-state="active"] {
|
|
67
|
+
background-color: var(--sheerly-background);
|
|
68
|
+
color: var(--sheerly-foreground);
|
|
69
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Line variant trigger */
|
|
73
|
+
.sheerly-tabs-list[data-variant="line"] & {
|
|
74
|
+
border-radius: 0;
|
|
75
|
+
padding: 8px 12px;
|
|
76
|
+
box-shadow: none;
|
|
77
|
+
|
|
78
|
+
&[aria-selected="true"],
|
|
79
|
+
&[data-state="active"] {
|
|
80
|
+
background-color: transparent;
|
|
81
|
+
box-shadow: none;
|
|
82
|
+
|
|
83
|
+
&::after {
|
|
84
|
+
content: "";
|
|
85
|
+
position: absolute;
|
|
86
|
+
bottom: -1px;
|
|
87
|
+
left: 0;
|
|
88
|
+
right: 0;
|
|
89
|
+
height: 2px;
|
|
90
|
+
background-color: var(--sheerly-foreground);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
& svg {
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
flex-shrink: 0;
|
|
98
|
+
width: 16px;
|
|
99
|
+
height: 16px;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.sheerly-tabs-content {
|
|
104
|
+
flex: 1;
|
|
105
|
+
outline: none;
|
|
106
|
+
|
|
107
|
+
&[hidden] {
|
|
108
|
+
display: none;
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/tabs.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type TabsProps = HTMLAttributes<HTMLDivElement>
|
|
5
|
+
|
|
6
|
+
export function Tabs({ className, ...props }: TabsProps) {
|
|
7
|
+
return <div className={cn('sheerly-tabs', className)} {...props} />
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface TabsListProps extends HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
'data-variant'?: 'line'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function TabsList({ className, role = 'tablist', ...props }: TabsListProps) {
|
|
15
|
+
return <div role={role} className={cn('sheerly-tabs-list', className)} {...props} />
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TabsTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
19
|
+
'data-state'?: 'active'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function TabsTrigger({ className, role = 'tab', ...props }: TabsTriggerProps) {
|
|
23
|
+
return (
|
|
24
|
+
<button role={role} className={cn('sheerly-tabs-trigger', className)} {...props} />
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type TabsContentProps = HTMLAttributes<HTMLDivElement>
|
|
29
|
+
|
|
30
|
+
export function TabsContent({
|
|
31
|
+
className,
|
|
32
|
+
role = 'tabpanel',
|
|
33
|
+
...props
|
|
34
|
+
}: TabsContentProps) {
|
|
35
|
+
return (
|
|
36
|
+
<div role={role} className={cn('sheerly-tabs-content', className)} {...props} />
|
|
37
|
+
)
|
|
38
|
+
}
|
package/src/textarea.css
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Textarea — <textarea> that auto-grows via field-sizing: content */
|
|
2
|
+
.sheerly-textarea {
|
|
3
|
+
display: block;
|
|
4
|
+
width: 100%;
|
|
5
|
+
min-height: 64px;
|
|
6
|
+
border-radius: var(--sheerly-radius-lg);
|
|
7
|
+
border: 1px solid var(--sheerly-input);
|
|
8
|
+
background-color: transparent;
|
|
9
|
+
padding: 6px 10px;
|
|
10
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
11
|
+
font-size: 14px;
|
|
12
|
+
font-weight: 400;
|
|
13
|
+
line-height: 20px;
|
|
14
|
+
color: var(--sheerly-foreground);
|
|
15
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
16
|
+
outline: none;
|
|
17
|
+
resize: vertical;
|
|
18
|
+
field-sizing: content;
|
|
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
|
+
cursor: not-allowed;
|
|
31
|
+
opacity: 0.5;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&[aria-invalid="true"] {
|
|
35
|
+
border-color: var(--sheerly-destructive);
|
|
36
|
+
box-shadow: 0 0 0 3px oklch(from var(--sheerly-destructive) l c h / 20%);
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/textarea.tsx
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TextareaHTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>
|
|
5
|
+
|
|
6
|
+
export function Textarea({ className, ...props }: TextareaProps) {
|
|
7
|
+
return <textarea className={cn('sheerly-textarea', className)} {...props} />
|
|
8
|
+
}
|
package/src/toggle.css
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Toggle — a two-state (on / off) button. Mirrors shadcn base-nova.
|
|
3
|
+
*
|
|
4
|
+
* State is driven by a nested checkbox, so no JS is needed:
|
|
5
|
+
* <label class="sheerly-toggle">
|
|
6
|
+
* <input type="checkbox" />
|
|
7
|
+
* <svg data-icon="inline-start">…</svg>
|
|
8
|
+
* Italic
|
|
9
|
+
* </label>
|
|
10
|
+
*
|
|
11
|
+
* The checkbox is visually hidden; the label reflects `:checked` via
|
|
12
|
+
* `:has(:checked)`. A plain <button> with aria-pressed="true" /
|
|
13
|
+
* data-state="on" is still supported for command-driven / controlled use.
|
|
14
|
+
*/
|
|
15
|
+
.sheerly-toggle {
|
|
16
|
+
display: inline-flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
gap: 4px;
|
|
20
|
+
border-radius: var(--sheerly-radius-lg);
|
|
21
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
22
|
+
font-size: 14px;
|
|
23
|
+
font-weight: 500;
|
|
24
|
+
line-height: 20px;
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
background-color: transparent;
|
|
27
|
+
color: var(--sheerly-foreground);
|
|
28
|
+
border: 1px solid transparent;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
outline: none;
|
|
31
|
+
transition: all 0.15s ease;
|
|
32
|
+
user-select: none;
|
|
33
|
+
|
|
34
|
+
/* Default size (32px) */
|
|
35
|
+
height: 32px;
|
|
36
|
+
min-width: 32px;
|
|
37
|
+
padding: 0 10px;
|
|
38
|
+
|
|
39
|
+
/* Visually hide the state-bearing checkbox; keep it focusable/accessible. */
|
|
40
|
+
& > input[type="checkbox"] {
|
|
41
|
+
position: absolute;
|
|
42
|
+
width: 1px;
|
|
43
|
+
height: 1px;
|
|
44
|
+
padding: 0;
|
|
45
|
+
margin: -1px;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
clip: rect(0, 0, 0, 0);
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
border: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:hover {
|
|
53
|
+
background-color: var(--sheerly-muted);
|
|
54
|
+
color: var(--sheerly-foreground);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Keyboard focus — on the label (via the nested input) or a plain button. */
|
|
58
|
+
&:has(> input:focus-visible),
|
|
59
|
+
&:focus-visible {
|
|
60
|
+
border-color: var(--sheerly-ring);
|
|
61
|
+
box-shadow: var(--sheerly-focus-ring);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Disabled — either a disabled checkbox inside or a disabled button. */
|
|
65
|
+
&:has(> input:disabled),
|
|
66
|
+
&:disabled {
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
opacity: 0.5;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&[aria-invalid="true"] {
|
|
72
|
+
border-color: var(--sheerly-destructive);
|
|
73
|
+
box-shadow: 0 0 0 3px oklch(from var(--sheerly-destructive) l c h / 20%);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Active / pressed state */
|
|
77
|
+
&:has(> input:checked),
|
|
78
|
+
&[aria-pressed="true"],
|
|
79
|
+
&[data-state="on"] {
|
|
80
|
+
background-color: var(--sheerly-muted);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ========== Variants ========== */
|
|
84
|
+
|
|
85
|
+
&[data-variant="default"] {
|
|
86
|
+
background-color: transparent;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&[data-variant="outline"] {
|
|
90
|
+
border-color: var(--sheerly-input);
|
|
91
|
+
background-color: transparent;
|
|
92
|
+
|
|
93
|
+
&:hover {
|
|
94
|
+
background-color: var(--sheerly-muted);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* ========== Sizes ========== */
|
|
99
|
+
|
|
100
|
+
&[data-size="default"] {
|
|
101
|
+
height: 32px;
|
|
102
|
+
min-width: 32px;
|
|
103
|
+
padding: 0 10px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&[data-size="sm"] {
|
|
107
|
+
height: 28px;
|
|
108
|
+
min-width: 28px;
|
|
109
|
+
padding: 0 10px;
|
|
110
|
+
font-size: 12.8px;
|
|
111
|
+
border-radius: min(var(--sheerly-radius-md), 12px);
|
|
112
|
+
|
|
113
|
+
& svg {
|
|
114
|
+
width: 14px;
|
|
115
|
+
height: 14px;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&[data-size="lg"] {
|
|
120
|
+
height: 36px;
|
|
121
|
+
min-width: 36px;
|
|
122
|
+
padding: 0 10px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Icon spacing (matches shadcn has-data-[icon=…] paddings) */
|
|
126
|
+
&:has([data-icon="inline-start"]) {
|
|
127
|
+
padding-inline-start: 8px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&:has([data-icon="inline-end"]) {
|
|
131
|
+
padding-inline-end: 8px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&[data-size="sm"]:has([data-icon="inline-start"]) {
|
|
135
|
+
padding-inline-start: 6px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&[data-size="sm"]:has([data-icon="inline-end"]) {
|
|
139
|
+
padding-inline-end: 6px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
& svg {
|
|
143
|
+
pointer-events: none;
|
|
144
|
+
flex-shrink: 0;
|
|
145
|
+
width: 16px;
|
|
146
|
+
height: 16px;
|
|
147
|
+
}
|
|
148
|
+
}
|
package/src/toggle.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ChangeEventHandler, LabelHTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export interface ToggleProps
|
|
5
|
+
extends Omit<LabelHTMLAttributes<HTMLLabelElement>, 'onChange'> {
|
|
6
|
+
'data-variant'?: 'default' | 'outline'
|
|
7
|
+
'data-size'?: 'sm' | 'default' | 'lg'
|
|
8
|
+
/** Controlled pressed state (drives the nested checkbox). */
|
|
9
|
+
pressed?: boolean
|
|
10
|
+
/** Uncontrolled initial pressed state. */
|
|
11
|
+
defaultPressed?: boolean
|
|
12
|
+
/** Fires with the next pressed state when toggled. */
|
|
13
|
+
onPressedChange?: (pressed: boolean) => void
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
name?: string
|
|
16
|
+
value?: string
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Toggle({
|
|
21
|
+
className,
|
|
22
|
+
pressed,
|
|
23
|
+
defaultPressed,
|
|
24
|
+
onPressedChange,
|
|
25
|
+
disabled,
|
|
26
|
+
name,
|
|
27
|
+
value,
|
|
28
|
+
children,
|
|
29
|
+
...props
|
|
30
|
+
}: ToggleProps) {
|
|
31
|
+
const onChange: ChangeEventHandler<HTMLInputElement> = (e) =>
|
|
32
|
+
onPressedChange?.(e.target.checked)
|
|
33
|
+
return (
|
|
34
|
+
<label className={cn('sheerly-toggle', className)} {...props}>
|
|
35
|
+
<input
|
|
36
|
+
type="checkbox"
|
|
37
|
+
checked={pressed}
|
|
38
|
+
defaultChecked={defaultPressed}
|
|
39
|
+
onChange={onChange}
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
name={name}
|
|
42
|
+
value={value}
|
|
43
|
+
/>
|
|
44
|
+
{children}
|
|
45
|
+
</label>
|
|
46
|
+
)
|
|
47
|
+
}
|
package/src/tooltip.css
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tooltip — uses popover="hint" + anchor positioning + interestfor
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* <span class="sheerly-tooltip">
|
|
6
|
+
* <button interestfor="my-tip">Hover me</button>
|
|
7
|
+
* <span class="sheerly-tooltip-content" id="my-tip" popover="hint">Tooltip text</span>
|
|
8
|
+
* </span>
|
|
9
|
+
*
|
|
10
|
+
* Position via --sheerly-tooltip-position: block-start | block-end | inline-start | inline-end
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.sheerly-tooltip {
|
|
14
|
+
anchor-name: --sheerly-tip;
|
|
15
|
+
anchor-scope: --sheerly-tip;
|
|
16
|
+
display: inline-block;
|
|
17
|
+
width: fit-content;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sheerly-tooltip-content {
|
|
21
|
+
position: fixed;
|
|
22
|
+
position-anchor: --sheerly-tip;
|
|
23
|
+
position-area: var(--sheerly-tooltip-position, block-start);
|
|
24
|
+
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
|
25
|
+
position-visibility: anchors-visible;
|
|
26
|
+
inset: unset;
|
|
27
|
+
margin: 6px;
|
|
28
|
+
|
|
29
|
+
/* Visual style */
|
|
30
|
+
background-color: var(--sheerly-foreground);
|
|
31
|
+
color: var(--sheerly-background);
|
|
32
|
+
font-family: "Geist Sans", system-ui, -apple-system, sans-serif;
|
|
33
|
+
font-size: 12px;
|
|
34
|
+
font-weight: 400;
|
|
35
|
+
line-height: 16px;
|
|
36
|
+
text-align: center;
|
|
37
|
+
white-space: normal;
|
|
38
|
+
text-wrap: pretty;
|
|
39
|
+
max-width: 200px;
|
|
40
|
+
padding: 4px 8px;
|
|
41
|
+
border-radius: var(--sheerly-radius-sm);
|
|
42
|
+
border: none;
|
|
43
|
+
overflow: visible;
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
|
|
46
|
+
/* Popover animation */
|
|
47
|
+
opacity: 0;
|
|
48
|
+
transition:
|
|
49
|
+
opacity 0.15s ease,
|
|
50
|
+
display 0.15s allow-discrete,
|
|
51
|
+
overlay 0.15s allow-discrete;
|
|
52
|
+
|
|
53
|
+
&:popover-open {
|
|
54
|
+
opacity: 1;
|
|
55
|
+
|
|
56
|
+
@starting-style {
|
|
57
|
+
opacity: 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/tooltip.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from './cn'
|
|
3
|
+
|
|
4
|
+
export type TooltipProps = HTMLAttributes<HTMLSpanElement>
|
|
5
|
+
|
|
6
|
+
/** Wrapper carrying the anchor. Nest the trigger + content inside. */
|
|
7
|
+
export function Tooltip({ className, ...props }: TooltipProps) {
|
|
8
|
+
return <span className={cn('sheerly-tooltip', className)} {...props} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TooltipContentProps = HTMLAttributes<HTMLSpanElement>
|
|
12
|
+
|
|
13
|
+
/** The floating label. Give it `popover="hint"` and an `id` matched by the trigger's `interestfor`. */
|
|
14
|
+
export function TooltipContent({ className, ...props }: TooltipContentProps) {
|
|
15
|
+
return <span className={cn('sheerly-tooltip-content', className)} {...props} />
|
|
16
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
import 'react'
|
|
4
|
+
|
|
5
|
+
// Sheerly leans on cutting-edge HTML that React's types don't cover yet:
|
|
6
|
+
// - <selectedcontent> (customizable <select> API)
|
|
7
|
+
// - interestfor (invoker for tooltips/popovers)
|
|
8
|
+
// - popover="hint" (React types allow the popover attr but not "hint")
|
|
9
|
+
// - command/commandfor (Invoker Commands API) on buttons
|
|
10
|
+
// - <dialog closedby> (light-dismiss)
|
|
11
|
+
// - focusgroup (arrow-key roving focus)
|
|
12
|
+
// This single declaration file is shared by both the library wrappers (src/)
|
|
13
|
+
// and the docs explorer (docs/) — both are compiled by tsconfig.app.json.
|
|
14
|
+
declare module 'react' {
|
|
15
|
+
interface HTMLAttributes<T> {
|
|
16
|
+
interestfor?: string
|
|
17
|
+
popover?: '' | 'auto' | 'manual' | 'hint'
|
|
18
|
+
// Focusgroup API (arrow-key roving focus) — not yet in React's types.
|
|
19
|
+
focusgroup?: string
|
|
20
|
+
}
|
|
21
|
+
// Invoker Commands API (command/commandfor) used by dialog, sheet, popover.
|
|
22
|
+
interface ButtonHTMLAttributes<T> {
|
|
23
|
+
command?: string
|
|
24
|
+
commandfor?: string
|
|
25
|
+
}
|
|
26
|
+
// <dialog closedby> (light-dismiss) — not yet in React's types.
|
|
27
|
+
interface DialogHTMLAttributes<T> {
|
|
28
|
+
closedby?: 'any' | 'closerequest' | 'none'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare module 'react/jsx-runtime' {
|
|
33
|
+
namespace JSX {
|
|
34
|
+
interface IntrinsicElements {
|
|
35
|
+
// <selectedcontent> from the customizable <select> API.
|
|
36
|
+
selectedcontent: React.DetailedHTMLProps<
|
|
37
|
+
React.HTMLAttributes<HTMLElement>,
|
|
38
|
+
HTMLElement
|
|
39
|
+
>
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|