diff --git a/components.json b/components.json
index fcf21ac23..793d5be3d 100644
--- a/components.json
+++ b/components.json
@@ -1,6 +1,6 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
- "style": "default",
+ "style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 5090dec0b..23e3031f3 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -24,7 +24,7 @@
diff --git a/src/renderer/src/components/ui/auto-completion/AutoCompletion.tsx b/src/renderer/src/components/ui/auto-completion/AutoCompletion.tsx
index a213dd7fc..ecc7bb9ca 100644
--- a/src/renderer/src/components/ui/auto-completion/AutoCompletion.tsx
+++ b/src/renderer/src/components/ui/auto-completion/AutoCompletion.tsx
@@ -205,7 +205,7 @@ export const Autocomplete = forwardRef(
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(
}
return (
)}
- {props.children}
+ {props.children}
)
diff --git a/src/renderer/src/components/ui/button/variants.tsx b/src/renderer/src/components/ui/button/variants.tsx
index 899899c5d..c84a6888b 100644
--- a/src/renderer/src/components/ui/button/variants.tsx
+++ b/src/renderer/src/components/ui/button/variants.tsx
@@ -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: [
{
diff --git a/src/renderer/src/components/ui/form.tsx b/src/renderer/src/components/ui/form.tsx
index bf7659fb4..0cf563bee 100644
--- a/src/renderer/src/components/ui/form.tsx
+++ b/src/renderer/src/components/ui/form.tsx
@@ -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 (
@@ -104,7 +96,8 @@ const FormControl = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField()
return (
)
diff --git a/src/renderer/src/components/ui/input.tsx b/src/renderer/src/components/ui/input.tsx
deleted file mode 100644
index 2bf5e1344..000000000
--- a/src/renderer/src/components/ui/input.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { cn } from "@renderer/lib/utils"
-import * as React from "react"
-
-export type InputProps = React.InputHTMLAttributes
-
-const Input = React.forwardRef(
- ({ className, type, ...props }, ref) => (
-
- ),
-)
-Input.displayName = "Input"
-
-export { Input }
diff --git a/src/renderer/src/components/ui/input/Input.tsx b/src/renderer/src/components/ui/input/Input.tsx
new file mode 100644
index 000000000..c216f9a62
--- /dev/null
+++ b/src/renderer/src/components/ui/input/Input.tsx
@@ -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, HTMLInputElement>,
+ "ref"
+ >
+>(({ className, ...props }, ref) => {
+ const inputProps = useInputComposition(props)
+ return (
+
+ )
+})
+Input.displayName = "Input"
diff --git a/src/renderer/src/components/ui/input/TextArea.tsx b/src/renderer/src/components/ui/input/TextArea.tsx
new file mode 100644
index 000000000..38ac73de1
--- /dev/null
+++ b/src/renderer/src/components/ui/input/TextArea.tsx
@@ -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
+ > &
+ PropsWithChildren<{
+ wrapperClassName?: string
+ onCmdEnter?: (e: React.KeyboardEvent) => 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 (
+
+ {bordered && (
+
+ )}
+
+ )
+ })
+TextArea.displayName = "TextArea"
diff --git a/src/renderer/src/components/ui/input/index.ts b/src/renderer/src/components/ui/input/index.ts
new file mode 100644
index 000000000..34bbfe8f0
--- /dev/null
+++ b/src/renderer/src/components/ui/input/index.ts
@@ -0,0 +1,2 @@
+export * from "./Input"
+export * from "./TextArea"
diff --git a/src/renderer/src/components/ui/select.tsx b/src/renderer/src/components/ui/select.tsx
index 1a70ead69..edf6c1ba6 100644
--- a/src/renderer/src/components/ui/select.tsx
+++ b/src/renderer/src/components/ui/select.tsx
@@ -16,7 +16,9 @@ const SelectTrigger = React.forwardRef<
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}
>
-
+
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
@@ -58,7 +60,7 @@ const SelectScrollDownButton = React.forwardRef<
)}
{...props}
>
-
+
))
SelectScrollDownButton.displayName =
@@ -102,7 +104,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
))
@@ -115,17 +117,16 @@ const SelectItem = React.forwardRef<
-
+
-
{children}
))
diff --git a/src/renderer/src/hooks/common/use-input-composition.ts b/src/renderer/src/hooks/common/use-input-composition.ts
new file mode 100644
index 000000000..63afc3b65
--- /dev/null
+++ b/src/renderer/src/hooks/common/use-input-composition.ts
@@ -0,0 +1,60 @@
+import type { CompositionEventHandler } from "react"
+import { useCallback, useRef } from "react"
+
+export const useInputComposition = (
+ props: Pick<
+ | React.DetailedHTMLProps<
+ React.InputHTMLAttributes,
+ HTMLInputElement
+ >
+ | React.DetailedHTMLProps<
+ React.TextareaHTMLAttributes,
+ HTMLTextAreaElement
+ >,
+ "onKeyDown" | "onCompositionEnd" | "onCompositionStart"
+ >,
+) => {
+ const { onKeyDown, onCompositionStart, onCompositionEnd } = props
+
+ const isCompositionRef = useRef(false)
+
+ const handleCompositionStart: CompositionEventHandler = useCallback(
+ (e) => {
+ isCompositionRef.current = true
+ onCompositionStart?.(e)
+ },
+ [onCompositionStart],
+ )
+
+ const handleCompositionEnd: CompositionEventHandler = useCallback(
+ (e) => {
+ isCompositionRef.current = false
+ onCompositionEnd?.(e)
+ },
+ [onCompositionEnd],
+ )
+
+ const handleKeyDown: React.KeyboardEventHandler = 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,
+ }
+}
diff --git a/src/renderer/src/modules/discover/import.tsx b/src/renderer/src/modules/discover/import.tsx
index c5982f611..7199e455b 100644
--- a/src/renderer/src/modules/discover/import.tsx
+++ b/src/renderer/src/modules/discover/import.tsx
@@ -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() {
)}
/>
-
+
{mutation.isSuccess && (
diff --git a/src/renderer/src/modules/entry-column/index.tsx b/src/renderer/src/modules/entry-column/index.tsx
index 465f10530..f68f4fbe6 100644
--- a/src/renderer/src/modules/entry-column/index.tsx
+++ b/src/renderer/src/modules/entry-column/index.tsx
@@ -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<{
- {}} tooltip="Mark All as Read">
+
@@ -282,14 +288,12 @@ const ListHeader: FC<{
Mark all as read?
-
+ Cancel
{/* TODO */}
-
+
diff --git a/src/renderer/src/pages/(external)/redirect.tsx b/src/renderer/src/pages/(external)/redirect.tsx
index 71863b93a..5184c5510 100644
--- a/src/renderer/src/pages/(external)/redirect.tsx
+++ b/src/renderer/src/pages/(external)/redirect.tsx
@@ -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.
-
+
)
}
diff --git a/src/renderer/src/pages/(main)/(context)/(nonhome)/profile/index.tsx b/src/renderer/src/pages/(main)/(context)/(nonhome)/profile/index.tsx
index de3cee537..2165e0b68 100644
--- a/src/renderer/src/pages/(main)/(context)/(nonhome)/profile/index.tsx
+++ b/src/renderer/src/pages/(main)/(context)/(nonhome)/profile/index.tsx
@@ -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 (
-
+
{session?.user?.name?.slice(0, 2)}
{session?.user?.name}
+
+
+ {
+ signOut()
+ }}
+ >
+ Sign out
+
+
@@ -45,16 +56,6 @@ export function Component() {
))}
-
-
-
-
)
diff --git a/src/renderer/src/pages/settings/actions.tsx b/src/renderer/src/pages/settings/actions.tsx
index 6af28e470..715dbfc5a 100644
--- a/src/renderer/src/pages/settings/actions.tsx
+++ b/src/renderer/src/pages/settings/actions.tsx
@@ -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,
+ ),
+ )
}
}}
/>
))}
-
-
+ New Rule
+
+
+ mutation.mutate()}
+ >
+ Save
+
+
>
)
diff --git a/src/renderer/src/pages/settings/profile.tsx b/src/renderer/src/pages/settings/profile.tsx
index 57d5f0eca..498504881 100644
--- a/src/renderer/src/pages/settings/profile.tsx
+++ b/src/renderer/src/pages/settings/profile.tsx
@@ -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() {
)}
/>
-
+
+ Submit
+
>
diff --git a/src/renderer/src/styles/base.css b/src/renderer/src/styles/base.css
index fce12fe18..c844846a3 100644
--- a/src/renderer/src/styles/base.css
+++ b/src/renderer/src/styles/base.css
@@ -21,3 +21,7 @@ a {
[data-theme="dark"] {
color-scheme: dark;
}
+
+::selection {
+ @apply bg-theme-accent/20;
+}
diff --git a/src/renderer/src/styles/colors.css b/src/renderer/src/styles/colors.css
index 98afa5b80..12680fad5 100644
--- a/src/renderer/src/styles/colors.css
+++ b/src/renderer/src/styles/colors.css
@@ -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);
diff --git a/src/renderer/src/styles/tailwind-extend.css b/src/renderer/src/styles/tailwind-extend.css
index c1579de6d..44fc3b1b1 100644
--- a/src/renderer/src/styles/tailwind-extend.css
+++ b/src/renderer/src/styles/tailwind-extend.css
@@ -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;
+ }
+ }
}