feat: re-design `<Input />` (#67)

* feat: resign `<Input />`

Signed-off-by: Innei <i@innei.in>

* fix: unify styles and prevent `esc` to trigger input event

Signed-off-by: Innei <i@innei.in>

* fix: adjust button height

Signed-off-by: Innei <i@innei.in>

* fix: unify button style

Signed-off-by: Innei <i@innei.in>

* fix: popover button

Signed-off-by: Innei <i@innei.in>

---------

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-06-17 15:04:32 +08:00 committed by GitHub
parent bf39825425
commit f54df889a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 372 additions and 105 deletions

View File

@ -1,6 +1,6 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {

View File

@ -24,7 +24,7 @@
<div id="root">
<!-- Skeleton -->
<div class="h-full w-full flex">
<div class="w-64 h-full"></div>
<div class="w-64 shrink-0 h-full"></div>
<div class="grow w-full h-full bg-background"></div>
</div>
</div>

View File

@ -205,7 +205,7 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
exit={{ opacity: 0, y: 10 }}
className={clsx(
"pointer-events-auto max-h-48 grow",
"overflow-auto rounded-md border border-zinc-200 bg-zinc-50/90 backdrop-blur dark:border-neutral-800 dark:bg-neutral-900/90",
"overflow-auto rounded-md border border-zinc-200 bg-popover text-popover-foreground",
)}
// FIXME: https://github.com/radix-ui/primitives/issues/2125
onWheel={stopPropagation}
@ -220,7 +220,7 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
}
return (
<li
className="cursor-default px-4 py-2 text-sm hover:bg-zinc-200 dark:hover:bg-neutral-800"
className="cursor-default px-4 py-1.5 text-sm hover:bg-theme-item-hover dark:hover:bg-neutral-800"
key={suggestion.value}
onMouseDown={handleClick}
onClick={handleClick}

View File

@ -97,7 +97,6 @@ export const MotionButtonBase = React.forwardRef<
layout="size"
initial
whileFocus={{ scale: 1.02 }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.95 }}
{...rest}
ref={ref}
@ -145,7 +144,7 @@ export const StyledButton = React.forwardRef<
<LoadingCircle size="small" className="mr-2" />
</m.span>
)}
<m.span>{props.children}</m.span>
<m.span className={cn("center", className)}>{props.children}</m.span>
</m.span>
</MotionButtonBase>
)

View File

@ -31,7 +31,7 @@ export const buttonVariants = cva(
},
)
export const styledButtonVariant = cva(
"inline-flex select-none disabled:cursor-not-allowed cursor-default items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition active:transition-none",
"inline-flex select-none disabled:cursor-not-allowed cursor-default items-center gap-2 justify-center rounded-lg py-1.5 px-3 text-sm outline-offset-2 transition active:transition-none",
{
compoundVariants: [
{

View File

@ -3,16 +3,8 @@ import { Slot } from "@radix-ui/react-slot"
import { Label } from "@renderer/components/ui/label"
import { cn } from "@renderer/lib/utils"
import * as React from "react"
import type {
ControllerProps,
FieldPath,
FieldValues,
} from "react-hook-form"
import {
Controller,
FormProvider,
useFormContext,
} from "react-hook-form"
import type { ControllerProps, FieldPath, FieldValues } from "react-hook-form"
import { Controller, FormProvider, useFormContext } from "react-hook-form"
const Form = FormProvider
@ -92,7 +84,7 @@ const FormLabel = React.forwardRef<
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
className={cn(error && "text-destructive", "font-semibold", className)}
htmlFor={formItemId}
{...props}
/>
@ -104,7 +96,8 @@ const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField()
return (
<Slot
@ -132,7 +125,7 @@ const FormDescription = React.forwardRef<
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-xs text-muted-foreground", className)}
{...props}
/>
)

View File

@ -1,21 +0,0 @@
import { cn } from "@renderer/lib/utils"
import * as React from "react"
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
),
)
Input.displayName = "Input"
export { Input }

View File

@ -0,0 +1,36 @@
import { useInputComposition } from "@renderer/hooks/common/use-input-composition"
import { cn } from "@renderer/lib/utils"
import type { DetailedHTMLProps, InputHTMLAttributes } from "react"
import { forwardRef } from "react"
// This composition handler is not perfect
// @see https://foxact.skk.moe/use-composition-input
export const Input = forwardRef<
HTMLInputElement,
Omit<
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
"ref"
>
>(({ className, ...props }, ref) => {
const inputProps = useInputComposition(props)
return (
<input
ref={ref}
className={cn(
"min-w-0 flex-auto appearance-none rounded-lg text-sm",
"bg-background px-3 py-[calc(theme(spacing.2)-1px)] placeholder:text-zinc-400 dark:bg-zinc-700/[0.15]",
"ring-theme-accent/20 duration-200 focus:border-theme-accent/80 focus:outline-none focus:ring-2",
"border border-border",
"dark:text-zinc-200 dark:placeholder:text-zinc-500",
props.type === "password" ?
"font-mono placeholder:font-sans" :
"font-sans",
"w-full",
className,
)}
{...props}
{...inputProps}
/>
)
})
Input.displayName = "Input"

View File

@ -0,0 +1,114 @@
"use client"
import { useInputComposition } from "@renderer/hooks/common/use-input-composition"
import { cn } from "@renderer/lib/utils"
import clsx from "clsx"
import { useMotionValue } from "framer-motion"
import type {
DetailedHTMLProps,
PropsWithChildren,
TextareaHTMLAttributes,
} from "react"
import { forwardRef, useCallback, useState } from "react"
const roundedMap = {
"sm": "rounded-sm",
"md": "rounded-md",
"lg": "rounded-lg",
"xl": "rounded-xl",
"2xl": "rounded-2xl",
"3xl": "rounded-3xl",
"default": "rounded",
}
export const TextArea = forwardRef<
HTMLTextAreaElement,
DetailedHTMLProps<
TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
> &
PropsWithChildren<{
wrapperClassName?: string
onCmdEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void
rounded?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "default"
bordered?: boolean
}>
>((props, ref) => {
const {
className,
wrapperClassName,
children,
rounded = "xl",
bordered = true,
onCmdEnter,
...rest
} = props
const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
const handleMouseMove = useCallback(
({ clientX, clientY, currentTarget }: React.MouseEvent) => {
const bounds = currentTarget.getBoundingClientRect()
mouseX.set(clientX - bounds.left)
mouseY.set(clientY - bounds.top)
},
[mouseX, mouseY],
)
const inputProps = useInputComposition(props)
const [isFocus, setIsFocus] = useState(false)
return (
<div
className={cn(
"group relative h-full border ring-0 ring-accent/20 duration-200 [--spotlight-color:oklch(var(--a)_/_0.12)]",
roundedMap[rounded],
"border-transparent",
isFocus && "border-accent/80 ring-2",
"dark:text-zinc-200 dark:placeholder:text-zinc-500",
wrapperClassName,
)}
onMouseMove={handleMouseMove}
>
{bordered && (
<div
className={clsx(
"border-border pointer-events-none absolute inset-0 z-0 border",
roundedMap[rounded],
)}
aria-hidden="true"
/>
)}
<textarea
ref={ref}
className={cn(
"size-full resize-none bg-transparent",
"overflow-auto px-3 py-4",
"!outline-none",
"text-neutral-900/80 dark:text-slate-100/80",
roundedMap[rounded],
className,
)}
{...rest}
onFocus={(e) => {
setIsFocus(true)
rest.onFocus?.(e)
}}
onBlur={(e) => {
setIsFocus(false)
rest.onBlur?.(e)
}}
{...inputProps}
onKeyDown={(e) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
onCmdEnter?.(e)
}
rest.onKeyDown?.(e)
inputProps.onKeyDown?.(e)
}}
/>
{children}
</div>
)
})
TextArea.displayName = "TextArea"

View File

@ -0,0 +1,2 @@
export * from "./Input"
export * from "./TextArea"

View File

@ -16,7 +16,9 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"ring-theme-accent/20 duration-200 focus:border-theme-accent/80 focus:outline-none focus:ring-2",
"border border-border",
className,
)}
{...props}
@ -41,7 +43,7 @@ const SelectScrollUpButton = React.forwardRef<
)}
{...props}
>
<ChevronUp className="size-4" />
<ChevronUp />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
@ -58,7 +60,7 @@ const SelectScrollDownButton = React.forwardRef<
)}
{...props}
>
<ChevronDown className="size-4" />
<ChevronDown />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
@ -102,7 +104,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
@ -115,17 +117,16 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-theme-item-active focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))

View File

@ -0,0 +1,60 @@
import type { CompositionEventHandler } from "react"
import { useCallback, useRef } from "react"
export const useInputComposition = (
props: Pick<
| React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>
| React.DetailedHTMLProps<
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
>,
"onKeyDown" | "onCompositionEnd" | "onCompositionStart"
>,
) => {
const { onKeyDown, onCompositionStart, onCompositionEnd } = props
const isCompositionRef = useRef(false)
const handleCompositionStart: CompositionEventHandler<any> = useCallback(
(e) => {
isCompositionRef.current = true
onCompositionStart?.(e)
},
[onCompositionStart],
)
const handleCompositionEnd: CompositionEventHandler<any> = useCallback(
(e) => {
isCompositionRef.current = false
onCompositionEnd?.(e)
},
[onCompositionEnd],
)
const handleKeyDown: React.KeyboardEventHandler<any> = useCallback(
(e) => {
onKeyDown?.(e)
if (isCompositionRef.current) {
e.stopPropagation()
return
}
if (e.key === "Escape") {
e.currentTarget.blur()
e.preventDefault()
e.stopPropagation()
}
},
[onKeyDown],
)
return {
onCompositionEnd: handleCompositionEnd,
onCompositionStart: handleCompositionStart,
onKeyDown: handleKeyDown,
}
}

View File

@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button } from "@renderer/components/ui/button"
import { StyledButton } from "@renderer/components/ui/button"
import { Card, CardContent, CardHeader } from "@renderer/components/ui/card"
import {
Form,
@ -110,9 +110,9 @@ export function DiscoverImport() {
</FormItem>
)}
/>
<Button type="submit" isLoading={mutation.isPending}>
<StyledButton type="submit" isLoading={mutation.isPending}>
Import
</Button>
</StyledButton>
</form>
</Form>
{mutation.isSuccess && (

View File

@ -1,5 +1,8 @@
import { useMainContainerElement } from "@renderer/atoms"
import { ActionButton, Button } from "@renderer/components/ui/button"
import {
ActionButton,
StyledButton,
} from "@renderer/components/ui/button"
import {
Popover,
PopoverClose,
@ -19,7 +22,10 @@ import {
useFeedStore,
} from "@renderer/store"
import { entryActions } from "@renderer/store/entry/entry"
import { useEntry, useEntryIdsByFeedIdOrView } from "@renderer/store/entry/hooks"
import {
useEntry,
useEntryIdsByFeedIdOrView,
} from "@renderer/store/entry/hooks"
import type { HTMLMotionProps } from "framer-motion"
import { m } from "framer-motion"
import hotkeys from "hotkeys-js"
@ -274,7 +280,7 @@ const ListHeader: FC<{
</ActionButton>
<Popover open={markPopoverOpen} onOpenChange={setMarkPopoverOpen}>
<PopoverTrigger>
<ActionButton onClick={() => {}} tooltip="Mark All as Read">
<ActionButton tooltip="Mark All as Read">
<i className="i-mingcute-check-circle-line" />
</ActionButton>
</PopoverTrigger>
@ -282,14 +288,12 @@ const ListHeader: FC<{
<div>Mark all as read?</div>
<div className="space-x-4">
<PopoverClose>
<Button size="sm" variant="outline">
Cancel
</Button>
<StyledButton variant="plain">Cancel</StyledButton>
</PopoverClose>
{/* TODO */}
<Button size="sm" onClick={handleMarkAllAsRead}>
<StyledButton onClick={handleMarkAllAsRead}>
Confirm
</Button>
</StyledButton>
</div>
</PopoverContent>
</Popover>

View File

@ -1,5 +1,5 @@
import { Logo } from "@renderer/components/icons/logo"
import { Button } from "@renderer/components/ui/button"
import { StyledButton } from "@renderer/components/ui/button"
import { UserButton } from "@renderer/components/user-button"
import { apiClient } from "@renderer/lib/api-fetch"
import { APP_NAME } from "@renderer/lib/constants"
@ -49,15 +49,14 @@ export function Component() {
{" "}
and safely close this page.
</h2>
<Button
className="text-lg"
size="xl"
<StyledButton
className="h-14 rounded-lg px-10 text-lg"
onClick={async () => window.open(await getCallbackUrl())}
>
Open
{" "}
{APP_NAME}
</Button>
</StyledButton>
</div>
)
}

View File

@ -3,7 +3,7 @@ import {
AvatarFallback,
AvatarImage,
} from "@renderer/components/ui/avatar"
import { Button } from "@renderer/components/ui/button"
import { StyledButton } from "@renderer/components/ui/button"
import { useSignOut } from "@renderer/hooks"
import { views } from "@renderer/lib/constants"
import { cn } from "@renderer/lib/utils"
@ -15,12 +15,23 @@ export function Component() {
const signOut = useSignOut()
return (
<div className="flex w-full gap-10 px-10 py-16">
<div className="flex min-w-40 flex-1 flex-col gap-8 pt-20">
<div className="flex min-w-40 flex-1 flex-col items-center gap-8 pt-20">
<Avatar className="aspect-square size-32">
<AvatarImage src={session?.user?.image || undefined} />
<AvatarFallback>{session?.user?.name?.slice(0, 2)}</AvatarFallback>
</Avatar>
<div className="text-4xl font-bold">{session?.user?.name}</div>
<div>
<StyledButton
variant="plain"
onClick={() => {
signOut()
}}
>
Sign out
</StyledButton>
</div>
</div>
<div className="flex flex-[2] flex-col gap-8">
@ -45,16 +56,6 @@ export function Component() {
))}
</div>
</div>
<div>
<Button
onClick={() => {
signOut()
}}
>
Sign out
</Button>
</div>
</div>
</div>
)

View File

@ -1,4 +1,4 @@
import { Button } from "@renderer/components/ui/button"
import { StyledButton } from "@renderer/components/ui/button"
import { useBizQuery } from "@renderer/hooks"
import { apiClient } from "@renderer/lib/api-fetch"
import type { ActionsResponse } from "@renderer/models"
@ -9,7 +9,14 @@ import { useMutation } from "@tanstack/react-query"
import { useEffect, useState } from "react"
import { toast } from "sonner"
type Operation = "contains" | "not_contains" | "eq" | "not_eq" | "gt" | "lt" | "regex"
type Operation =
| "contains"
| "not_contains"
| "eq"
| "not_eq"
| "gt"
| "lt"
| "regex"
type EntryField = "all" | "title" | "content" | "author" | "url" | "order"
type FeedField = "view" | "title" | "site_url" | "feed_url"
@ -48,9 +55,15 @@ export function Component() {
const mutation = useMutation({
mutationFn: async () => {
actionsData.forEach((action) => {
action.condition = action.condition.filter((c) => c.field && c.operator && c.value)
action.result.rewriteRules = action.result.rewriteRules?.filter((r) => r.from && r.to)
action.result.blockRules = action.result.blockRules?.filter((r) => r.field && r.operator && r.value)
action.condition = action.condition.filter(
(c) => c.field && c.operator && c.value,
)
action.result.rewriteRules = action.result.rewriteRules?.filter(
(r) => r.from && r.to,
)
action.result.blockRules = action.result.blockRules?.filter(
(r) => r.field && r.operator && r.value,
)
})
await apiClient.actions.$put({
json: {
@ -74,35 +87,45 @@ export function Component() {
data={action}
onChange={(newAction) => {
if (!newAction) {
setActionsData(actionsData.filter((_, idx) => idx !== actionIdx))
setActionsData(
actionsData.filter((_, idx) => idx !== actionIdx),
)
} else {
setActionsData(actionsData.map((a, idx) => idx === actionIdx ? newAction : a))
setActionsData(
actionsData.map((a, idx) =>
idx === actionIdx ? newAction : a,
),
)
}
}}
/>
))}
<Button
variant="outline"
size="sm"
className="w-full gap-1"
<StyledButton
variant="plain"
className="center w-full gap-1"
onClick={() => {
setActionsData([...actionsData, {
name: `Action ${actionsData.length + 1}`,
condition: [],
result: {},
}])
setActionsData([
...actionsData,
{
name: `Action ${actionsData.length + 1}`,
condition: [],
result: {},
},
])
}}
>
<i className="i-mingcute-add-line" />
{" "}
New Rule
</Button>
<Button
isLoading={mutation.isPending}
onClick={() => mutation.mutate()}
>
Save
</Button>
<span> New Rule</span>
</StyledButton>
<div className="text-right">
<StyledButton
variant="primary"
isLoading={mutation.isPending}
onClick={() => mutation.mutate()}
>
Save
</StyledButton>
</div>
</div>
</>
)

View File

@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button } from "@renderer/components/ui/button"
import { StyledButton } from "@renderer/components/ui/button"
import {
Form,
FormControl,
@ -98,7 +98,9 @@ export function Component() {
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
<div className="text-right">
<StyledButton type="submit">Submit</StyledButton>
</div>
</form>
</Form>
</>

View File

@ -21,3 +21,7 @@ a {
[data-theme="dark"] {
color-scheme: dark;
}
::selection {
@apply bg-theme-accent/20;
}

View File

@ -5,7 +5,7 @@
--fo-item-active: theme(colors.native.active/0.4);
--fo-item-hover: theme(colors.native.active/0.2);
--fo-tooltip-background: theme(colors.zinc.50/0.8);
--fo-tooltip-background: theme(colors.zinc.50);
--fo-tooltip-foreground: theme(colors.neutral.950);
--fo-inactive: theme(colors.zinc.200);
@ -24,7 +24,7 @@
--fo-item-active: theme(colors.neutral.700/0.4);
--fo-item-hover: theme(colors.native.active/0.2);
--fo-tooltip-background: theme(colors.zinc.800/0.8);
--fo-tooltip-background: theme(colors.zinc.800);
--fo-tooltip-foreground: theme(colors.zinc.100);
--fo-inactive: theme(colors.zinc.500);

View File

@ -53,4 +53,54 @@
.shadow-modal {
@apply shadow-2xl shadow-stone-300 dark:shadow-stone-800;
}
.radio {
flex-shrink: 0;
--chkbg: var(--bc);
height: 1.5rem;
width: 1.5rem;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 9999px;
border-width: 1px;
border-color: var(--fallback-bc, theme(colors.theme.accent.DEFAULT));
--tw-border-opacity: 0.2;
}
.radio-primary {
--chkbg: theme(colors.theme.accent.DEFAULT);
--fallback-bc: theme(colors.theme.accent.DEFAULT);
}
.radio:checked,
.radio[aria-checked="true"] {
--tw-bg-opacity: 1;
background-color: var(
--fallback-bc,
oklch(theme(colors.theme.accent.DEFAULT) / var(--tw-bg-opacity))
);
background-image: none;
animation: radiomark var(--animation-input, 0.2s) ease-out;
box-shadow: 0 0 0 4px var(--fallback-b1, oklch(var(--b1) / 1)) inset,
0 0 0 4px var(--fallback-b1, oklch(var(--b1) / 1)) inset;
}
@keyframes radiomark {
0% {
box-shadow: 0 0 0 12px var(--fallback-b1, oklch(var(--b1) / 1)) inset,
0 0 0 12px var(--fallback-b1, oklch(var(--b1) / 1)) inset;
}
50% {
box-shadow: 0 0 0 3px var(--fallback-b1, oklch(var(--b1) / 1)) inset,
0 0 0 3px var(--fallback-b1, oklch(var(--b1) / 1)) inset;
}
to {
box-shadow: 0 0 0 4px var(--fallback-b1, oklch(var(--b1) / 1)) inset,
0 0 0 4px var(--fallback-b1, oklch(var(--b1) / 1)) inset;
}
}
}