feat: resizeable setting panel and adjust action tab

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-08-01 13:33:11 +08:00
parent 05a4c870b8
commit 5fd9b23f03
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
7 changed files with 225 additions and 119 deletions

View File

@ -98,6 +98,7 @@
"path-to-regexp": "7.1.0",
"posthog-js": "1.150.0",
"posthog-node": "4.0.1",
"re-resizable": "6.9.17",
"react-error-boundary": "4.0.13",
"react-fast-marquee": "1.6.5",
"react-hook-form": "7.52.1",

View File

@ -220,6 +220,9 @@ importers:
posthog-node:
specifier: 4.0.1
version: 4.0.1
re-resizable:
specifier: 6.9.17
version: 6.9.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-error-boundary:
specifier: 4.0.13
version: 4.0.13(react@18.3.1)
@ -5525,6 +5528,12 @@ packages:
random-path@0.1.2:
resolution: {integrity: sha512-4jY0yoEaQ5v9StCl5kZbNIQlg1QheIDBrdkDn53EynpPb9FgO6//p3X/tgMnrC45XN6QZCzU1Xz/+pSSsJBpRw==}
re-resizable@6.9.17:
resolution: {integrity: sha512-OBqd1BwVXpEJJn/yYROG+CbeqIDBWIp6wathlpB0kzZWWZIY1gPTsgK2yJEui5hOvkCdC2mcexF2V3DZVfLq2g==}
peerDependencies:
react: ^16.13.1 || ^17.0.0 || ^18.0.0
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
@ -12833,6 +12842,11 @@ snapshots:
murmur-32: 0.2.0
optional: true
re-resizable@6.9.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0

View File

@ -0,0 +1,89 @@
import { cn } from "@renderer/lib/utils"
import type { Variants } from "framer-motion"
import { AnimatePresence, m } from "framer-motion"
import * as React from "react"
import { microReboundPreset } from "../constants/spring"
interface CollapseProps {
title: React.ReactNode
}
export const Collapse: Component<CollapseProps> = (props) => {
const [isOpened, setIsOpened] = React.useState(false)
return (
<CollapseControlled
isOpened={isOpened}
onOpenChange={setIsOpened}
{...props}
/>
)
}
export const CollapseControlled: Component<
{
isOpened: boolean
onOpenChange: (v: boolean) => void
} & CollapseProps
> = (props) => (
<div
className={cn("flex flex-col", props.className)}
data-state={props.isOpened ? "open" : "hidden"}
>
<div
className="relative flex w-full cursor-pointer items-center justify-between"
onClick={() => props.onOpenChange(!props.isOpened)}
>
<span className="w-0 shrink grow truncate">{props.title}</span>
<div className="shrink-0 text-gray-400">
<i
className={cn(
"i-mingcute-down-line duration-200",
props.isOpened ? "rotate-180" : "",
)}
/>
</div>
</div>
<CollapseContent isOpened={props.isOpened}>
{props.children}
</CollapseContent>
</div>
)
export const CollapseContent: Component<{
isOpened: boolean
withBackground?: boolean
}> = ({ isOpened, className, children }) => {
const variants = React.useMemo(() => {
const v = {
open: {
opacity: 1,
height: "auto",
transition: microReboundPreset,
},
collapsed: {
opacity: 0,
height: 0,
overflow: "hidden",
},
} satisfies Variants
return v
}, [])
return (
<>
<AnimatePresence initial={false}>
{isOpened && (
<m.div
key="content"
initial="collapsed"
animate="open"
exit="collapsed"
variants={variants}
className={className}
>
{children}
</m.div>
)}
</AnimatePresence>
</>
)
}

View File

@ -0,0 +1 @@
export * from "./Collapse"

View File

@ -1,13 +1,8 @@
import { Button } from "@renderer/components/ui/button"
import { Card, CardHeader } from "@renderer/components/ui/card"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@renderer/components/ui/collapsible"
import { Collapse, CollapseControlled } from "@renderer/components/ui/collapse"
import { Divider } from "@renderer/components/ui/divider"
import { Input } from "@renderer/components/ui/input"
import { Label } from "@renderer/components/ui/label"
import { Radio } from "@renderer/components/ui/radio-group"
import { RadioGroup } from "@renderer/components/ui/radio-group/RadioGroup"
import {
@ -27,6 +22,7 @@ import {
TableRow,
} from "@renderer/components/ui/table"
import { ViewSelectContent } from "@renderer/components/view-select-content"
import { stopPropagation } from "@renderer/lib/dom"
import { cn } from "@renderer/lib/utils"
import type { SupportedLanguages } from "@renderer/models"
@ -251,25 +247,33 @@ const OperationTableCell = ({
const SettingCollapsible = ({
title,
children,
open,
onOpenChange,
open,
children,
}: {
title: string
children: React.ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}) => (
<Collapsible open={open} onOpenChange={onOpenChange}>
<div className="flex items-center">
<Label className="flex-1">{title}</Label>
<CollapsibleTrigger className="-m-3 flex items-center [&_i]:data-[state=open]:rotate-45">
<i className="i-mgc-add-cute-re m-3 transition-transform" />
</CollapsibleTrigger>
</div>
<CollapsibleContent className="mt-2">{children}</CollapsibleContent>
</Collapsible>
)
}) => {
if (typeof open === "boolean" && typeof onOpenChange === "function") {
return (
<CollapseControlled
isOpened={open}
onOpenChange={onOpenChange}
title={title}
>
<div className="pt-2">{children}</div>
</CollapseControlled>
)
}
return (
<Collapse title={title}>
<div className="pt-2">{children}</div>
</Collapse>
)
}
const CommonSelectTrigger = ({ className }: { className?: string }) => (
<SelectTrigger className={cn("h-8", className)}>
@ -287,33 +291,36 @@ export function ActionCard({
return (
<Card>
<CardHeader className="space-y-4 px-6 py-4">
<Collapsible className="[&_.name-placeholder]:data-[state=open]:hidden [&_input.name-input]:data-[state=open]:block">
<div className="flex w-full items-center gap-2">
<Button
variant="ghost"
size="sm"
className="-ml-2"
onClick={() => {
onChange(null)
}}
>
<i className="i-mgc-delete-2-cute-re text-zinc-600" />
</Button>
<p className="shrink-0 font-medium text-zinc-500">Name</p>
<Input
value={data.name}
className="name-input hidden h-8"
onChange={(e) => {
data.name = e.target.value
onChange(data)
}}
/>
<CollapsibleTrigger className="flex w-14 flex-1 shrink-0 items-center pl-3 text-left [&_i]:data-[state=open]:rotate-90">
<Collapse
className="[&_.name-placeholder]:data-[state=open]:hidden [&_input.name-input]:data-[state=open]:block"
title={(
<div className="flex w-full items-center gap-2">
<Button
variant="ghost"
size="sm"
className="-ml-2"
onClick={() => {
onChange(null)
}}
>
<i className="i-mgc-delete-2-cute-re text-zinc-600" />
</Button>
<p className="shrink-0 font-medium text-zinc-500">Name</p>
<Input
value={data.name}
className="name-input hidden h-8"
onClick={stopPropagation}
onChange={(e) => {
data.name = e.target.value
onChange(data)
}}
/>
<div className="name-placeholder flex-1 text-sm">{data.name}</div>
<i className="i-mgc-right-cute-re h-8 shrink-0 text-xl transition-transform" />
</CollapsibleTrigger>
</div>
<CollapsibleContent className="mt-4 space-y-4">
</div>
)}
>
<div className="mt-4 space-y-4">
<div className="space-y-3">
<p className="font-medium text-zinc-500">When feeds match</p>
<div className="flex flex-col gap-2">
@ -421,22 +428,26 @@ export function ActionCard({
)}
</div>
<div className="space-y-4">
<p className="font-medium text-zinc-500">
Then do
</p>
<p className="font-medium text-zinc-500">Then do</p>
<div className="w-full space-y-4">
<SettingCollapsible
title="Translate into"
open={!!data.result.translation}
onOpenChange={(open) => {
if (open) {
data.result.translation = TransitionOptions[0].value
} else {
delete data.result.translation
}
onChange(data)
}}
>
<div className="flex w-full cursor-pointer items-center justify-between">
<span className="w-0 shrink grow truncate">
Generate summary using AI
</span>
<Switch
checked={data.result.summary}
onCheckedChange={(checked) => {
data.result.summary = checked
onChange(data)
}}
/>
</div>
<Divider />
<div className="flex w-full cursor-pointer items-center justify-between">
<span className="w-0 shrink grow truncate">
Translate into
</span>
<Select
value={data.result.translation}
onValueChange={(value) => {
@ -453,29 +464,10 @@ export function ActionCard({
))}
</SelectContent>
</Select>
</SettingCollapsible>
<Divider />
<SettingCollapsible
title="Generate summary using AI"
open={data.result.summary !== undefined}
onOpenChange={(open) => {
if (open) {
data.result.summary = true
} else {
delete data.result.summary
}
onChange(data)
}}
>
<Switch
checked={data.result.summary}
onCheckedChange={(checked) => {
data.result.summary = checked
onChange(data)
}}
/>
</SettingCollapsible>
</div>
<Divider />
<SettingCollapsible
title="Rewrite Rules"
open={!!data.result.rewriteRules}
@ -661,8 +653,8 @@ export function ActionCard({
</SettingCollapsible>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</div>
</Collapse>
</CardHeader>
</Card>
)

View File

@ -4,6 +4,7 @@ import { Logo } from "@renderer/components/icons/logo"
import { preventDefault } from "@renderer/lib/dom"
import { cn } from "@renderer/lib/utils"
import { useDragControls } from "framer-motion"
import { Resizable } from "re-resizable"
import type { PointerEventHandler, PropsWithChildren } from "react"
import { useCallback, useEffect } from "react"
@ -51,7 +52,7 @@ export function SettingModalLayout(
scale: 0.96,
}}
className={cn(
"relative flex h-[500px] max-h-[80vh] w-[calc(170px+55ch)] max-w-full flex-col overflow-hidden rounded-xl border border-border",
"relative flex overflow-hidden rounded-xl border border-border",
!overlay && "shadow-perfect",
)}
onContextMenu={preventDefault}
@ -64,40 +65,48 @@ export function SettingModalLayout(
cursor: "grabbing",
}}
>
{draggable && (
<div
className="absolute inset-x-0 top-0 z-[1] h-8"
onPointerDown={handleDrag}
/>
)}
<div className="flex h-0 flex-1 bg-theme-modal-background-opaque">
<div className="w-44 border-r px-2 py-6">
<div className="mb-4 flex h-8 items-center gap-2 px-2 font-bold">
<Logo className="mr-1 size-6" />
{APP_NAME}
<Resizable
defaultSize={{
width: 800,
height: 700,
}}
className="flex max-h-[80vh] max-w-[95vw] flex-col"
>
{draggable && (
<div
className="absolute inset-x-0 top-0 z-[1] h-8"
onPointerDown={handleDrag}
/>
)}
<div className="flex h-0 flex-1 bg-theme-modal-background-opaque">
<div className="w-44 border-r px-2 py-6">
<div className="mb-4 flex h-8 items-center gap-2 px-2 font-bold">
<Logo className="mr-1 size-6" />
{APP_NAME}
</div>
{settings.map((t) => (
<button
key={t.path}
className={`my-1 flex w-full items-center rounded-lg px-2.5 py-0.5 leading-loose text-theme-foreground/70 transition-colors ${
tab === t.path ?
"bg-theme-item-active text-theme-foreground/90" :
""
}`}
type="button"
onClick={() => setTab(t.path)}
>
<SettingsSidebarTitle
path={t.path}
className="text-[0.94rem] font-medium"
/>
</button>
))}
</div>
<div className="relative flex h-full flex-1 flex-col bg-theme-background pt-1">
{children}
</div>
{settings.map((t) => (
<button
key={t.path}
className={`my-1 flex w-full items-center rounded-lg px-2.5 py-0.5 leading-loose text-theme-foreground/70 transition-colors ${
tab === t.path ?
"bg-theme-item-active text-theme-foreground/90" :
""
}`}
type="button"
onClick={() => setTab(t.path)}
>
<SettingsSidebarTitle
path={t.path}
className="text-[0.94rem] font-medium"
/>
</button>
))}
</div>
<div className="relative flex h-full flex-1 flex-col bg-theme-background pt-1">
{children}
</div>
</div>
</Resizable>
</m.div>
)
}

View File

@ -46,8 +46,8 @@ export const TransactionsSection = () => {
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center" size="sm">
<TableRow className="[&_*]:!font-semibold">
<TableHead className="w-16 text-center" size="sm">
Type
</TableHead>
<TableHead className="text-center" size="sm">