@boyernick/standard-ui-react 0.1.1-canary.7 → 0.1.1-canary.9
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/package.json +1 -1
- package/src/command.tsx +24 -12
- package/src/date-picker.tsx +7 -2
- package/src/index.ts +7 -0
- package/src/kbd.tsx +43 -0
package/package.json
CHANGED
package/src/command.tsx
CHANGED
|
@@ -4,8 +4,10 @@ import { Dialog as BaseDialog } from "@base-ui/react/dialog"
|
|
|
4
4
|
import {
|
|
5
5
|
createContext,
|
|
6
6
|
useContext,
|
|
7
|
+
useEffect,
|
|
7
8
|
useId,
|
|
8
9
|
useMemo,
|
|
10
|
+
useRef,
|
|
9
11
|
type ButtonHTMLAttributes,
|
|
10
12
|
type ComponentProps,
|
|
11
13
|
type HTMLAttributes,
|
|
@@ -108,7 +110,7 @@ export const CommandBackdrop = ({
|
|
|
108
110
|
export const CommandPopup = ({ className, ...props }: CommandPopupProps) => (
|
|
109
111
|
<BaseDialog.Popup
|
|
110
112
|
className={cn(
|
|
111
|
-
"fixed top-1/2 left-1/2 z-50 flex h-[min(80vh,32rem)] w-full max-w-
|
|
113
|
+
"fixed top-1/2 left-1/2 z-50 flex h-[min(80vh,32rem)] w-full max-w-3xl -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-2xl bg-surface-raised shadow-ambient outline-none max-sm:max-w-[calc(100vw-2rem)]",
|
|
112
114
|
motion.popupCenter,
|
|
113
115
|
className,
|
|
114
116
|
)}
|
|
@@ -147,7 +149,6 @@ export const CommandInput = ({
|
|
|
147
149
|
aria-activedescendant={activeOptionId}
|
|
148
150
|
className={cn(
|
|
149
151
|
"h-9 min-w-0 flex-1 cursor-text rounded-md bg-transparent px-2.5 text-base text-fg-primary outline-none placeholder:text-fg-quaternary",
|
|
150
|
-
"focus-visible:bg-background-tertiary/60 focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
|
|
151
152
|
"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none",
|
|
152
153
|
className,
|
|
153
154
|
)}
|
|
@@ -174,9 +175,9 @@ export const CommandClear = ({
|
|
|
174
175
|
<button
|
|
175
176
|
type="button"
|
|
176
177
|
className={cn(
|
|
177
|
-
"cursor-pointer rounded-full px-3
|
|
178
|
+
"inline-flex h-8 cursor-pointer items-center rounded-full border border-transparent px-3 text-sm text-fg-quaternary outline-none",
|
|
178
179
|
motion.colors,
|
|
179
|
-
"hover:bg-background-tertiary hover:text-fg-primary focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 data-[active=true]:text-fg-primary",
|
|
180
|
+
"hover:bg-background-tertiary hover:text-fg-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 data-[active=true]:text-fg-primary",
|
|
180
181
|
className,
|
|
181
182
|
)}
|
|
182
183
|
{...props}
|
|
@@ -203,9 +204,9 @@ export const CommandClose = ({
|
|
|
203
204
|
}: CommandCloseProps) => (
|
|
204
205
|
<BaseDialog.Close
|
|
205
206
|
className={cn(
|
|
206
|
-
"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full text-fg-primary outline-none",
|
|
207
|
+
"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-transparent text-fg-primary outline-none",
|
|
207
208
|
motion.colors,
|
|
208
|
-
"hover:bg-background-tertiary focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
|
|
209
|
+
"hover:bg-background-tertiary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
|
|
209
210
|
className,
|
|
210
211
|
)}
|
|
211
212
|
aria-label={props["aria-label"] ?? "Close"}
|
|
@@ -223,7 +224,7 @@ export const CommandContent = ({
|
|
|
223
224
|
}: CommandContentProps) => (
|
|
224
225
|
<div
|
|
225
226
|
className={cn(
|
|
226
|
-
"flex min-h-0 flex-1 flex-col gap-3 overflow-
|
|
227
|
+
"flex min-h-0 flex-1 flex-col gap-3 overflow-hidden px-4 pt-1 pb-5",
|
|
227
228
|
className,
|
|
228
229
|
)}
|
|
229
230
|
{...props}
|
|
@@ -256,9 +257,9 @@ export const CommandFilter = ({
|
|
|
256
257
|
aria-pressed={selected}
|
|
257
258
|
data-selected={selected || undefined}
|
|
258
259
|
className={cn(
|
|
259
|
-
"cursor-pointer rounded-full px-4
|
|
260
|
+
"inline-flex h-9 cursor-pointer items-center rounded-full border border-transparent px-4 text-sm text-fg-quaternary outline-none",
|
|
260
261
|
motion.colors,
|
|
261
|
-
"hover:text-fg-primary focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
|
|
262
|
+
"hover:text-fg-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
|
|
262
263
|
"data-selected:bg-background-tertiary data-selected:text-fg-primary",
|
|
263
264
|
className,
|
|
264
265
|
)}
|
|
@@ -277,7 +278,7 @@ export const CommandList = ({ className, ...props }: CommandListProps) => {
|
|
|
277
278
|
id={listboxId}
|
|
278
279
|
role="listbox"
|
|
279
280
|
className={cn(
|
|
280
|
-
"grid min-h-0 flex-1 content-start gap-0 overflow-
|
|
281
|
+
"grid min-h-0 flex-1 content-start gap-0 overflow-y-auto overscroll-contain py-0.5",
|
|
281
282
|
className,
|
|
282
283
|
)}
|
|
283
284
|
{...props}
|
|
@@ -295,12 +296,21 @@ export const CommandItem = ({
|
|
|
295
296
|
onClick,
|
|
296
297
|
...props
|
|
297
298
|
}: CommandItemProps) => {
|
|
299
|
+
const ref = useRef<HTMLAnchorElement & HTMLButtonElement>(null)
|
|
300
|
+
|
|
301
|
+
// Keyboard selection moves the highlight without moving focus, so nothing
|
|
302
|
+
// scrolls the list on its own. `nearest` only acts when the row is actually
|
|
303
|
+
// out of view, which leaves hover-driven selection alone.
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (selected) ref.current?.scrollIntoView({ block: "nearest" })
|
|
306
|
+
}, [selected])
|
|
307
|
+
|
|
298
308
|
const itemClassName = cn(
|
|
299
|
-
"flex h-15 min-h-15 w-full cursor-pointer items-center justify-between gap-4 rounded-lg px-4 text-left text-sm font-medium text-fg-primary outline-none select-none",
|
|
309
|
+
"flex h-15 min-h-15 w-full cursor-pointer items-center justify-between gap-4 rounded-lg border border-transparent px-4 text-left text-sm font-medium text-fg-primary outline-none select-none",
|
|
300
310
|
motion.colors,
|
|
301
311
|
"hover:bg-background-tertiary data-selected:bg-background-tertiary",
|
|
302
312
|
"data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
303
|
-
"focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
|
|
313
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
|
|
304
314
|
className,
|
|
305
315
|
)
|
|
306
316
|
|
|
@@ -308,6 +318,7 @@ export const CommandItem = ({
|
|
|
308
318
|
<li role="presentation" className="m-0">
|
|
309
319
|
{href ? (
|
|
310
320
|
<a
|
|
321
|
+
ref={ref}
|
|
311
322
|
id={id}
|
|
312
323
|
href={disabled ? undefined : href}
|
|
313
324
|
role="option"
|
|
@@ -323,6 +334,7 @@ export const CommandItem = ({
|
|
|
323
334
|
</a>
|
|
324
335
|
) : (
|
|
325
336
|
<button
|
|
337
|
+
ref={ref}
|
|
326
338
|
id={id}
|
|
327
339
|
type="button"
|
|
328
340
|
role="option"
|
package/src/date-picker.tsx
CHANGED
|
@@ -62,11 +62,16 @@ const DatePickerTrigger = ({
|
|
|
62
62
|
render={
|
|
63
63
|
<Button
|
|
64
64
|
type="button"
|
|
65
|
-
|
|
65
|
+
// Default md height, so the trigger lines up with every other field
|
|
66
|
+
// in a form rather than sitting 4px short of them.
|
|
67
|
+
size="md"
|
|
66
68
|
variant="outline"
|
|
67
69
|
disabled={disabled}
|
|
68
70
|
prefix={<IconCalendar1 aria-hidden />}
|
|
69
|
-
|
|
71
|
+
// The outline button fills on hover, which is right for a button and
|
|
72
|
+
// wrong here: this reads as a field, and fields in the system keep
|
|
73
|
+
// their surface. The border and the popover carry the state.
|
|
74
|
+
className={cn("w-56 justify-start hover:bg-surface", className)}
|
|
70
75
|
/>
|
|
71
76
|
}
|
|
72
77
|
>
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export { Button, buttonVariants, type ButtonProps } from "./button";
|
|
2
2
|
export { Input, inputVariants, type InputProps } from "./input";
|
|
3
|
+
export {
|
|
4
|
+
Kbd,
|
|
5
|
+
KbdGroup,
|
|
6
|
+
kbdVariants,
|
|
7
|
+
type KbdGroupProps,
|
|
8
|
+
type KbdProps,
|
|
9
|
+
} from "./kbd";
|
|
3
10
|
export { Badge, badgeVariants, type BadgeProps } from "./badge";
|
|
4
11
|
export { Switch, type SwitchProps } from "./switch";
|
|
5
12
|
export { Checkbox, type CheckboxProps } from "./checkbox";
|
package/src/kbd.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
2
|
+
import type { ComponentProps } from "react"
|
|
3
|
+
import { cn } from "./lib/cn"
|
|
4
|
+
|
|
5
|
+
const kbdVariants = cva(
|
|
6
|
+
// The border is bottom-only on purpose: it reads as the lip of a physical
|
|
7
|
+
// key without boxing the glyph in on all four sides, which at 12px turns a
|
|
8
|
+
// keycap into a cramped badge.
|
|
9
|
+
"inline-flex shrink-0 items-center justify-center border-b px-1 font-sans text-xs whitespace-nowrap select-none",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "border-border-primary bg-background-tertiary text-fg-secondary",
|
|
14
|
+
// The default fill is a light grey that vanishes on a dark surface,
|
|
15
|
+
// so an inverted keycap lifts off the surface rather than tinting it.
|
|
16
|
+
inverted: "border-white/10 bg-white/10 text-fg-inverted",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
sm: "h-4 min-w-3.5 rounded-2xs",
|
|
20
|
+
md: "h-5 min-w-4 rounded-xs",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: { variant: "default", size: "md" },
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
export type KbdProps = ComponentProps<"kbd"> & VariantProps<typeof kbdVariants>
|
|
28
|
+
export type KbdGroupProps = ComponentProps<"span">
|
|
29
|
+
|
|
30
|
+
/** A single keycap. Presentational — map "cmd" to ⌘ before it gets here. */
|
|
31
|
+
export const Kbd = ({ className, variant, size, ...props }: KbdProps) => (
|
|
32
|
+
<kbd className={cn(kbdVariants({ variant, size }), className)} {...props} />
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
/** A full shortcut. Keeps the caps on one line at a consistent gap. */
|
|
36
|
+
export const KbdGroup = ({ className, ...props }: KbdGroupProps) => (
|
|
37
|
+
<span
|
|
38
|
+
className={cn("inline-flex items-center gap-1 align-middle", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export { kbdVariants }
|