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:
parent
bf39825425
commit
f54df889a3
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./Input"
|
||||
export * from "./TextArea"
|
||||
|
|
@ -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>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -21,3 +21,7 @@ a {
|
|||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
::selection {
|
||||
@apply bg-theme-accent/20;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue