feat: resizeable setting panel and adjust action tab
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
05a4c870b8
commit
5fd9b23f03
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./Collapse"
|
||||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Reference in New Issue