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/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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }