fix: fix paragraph item stacked height overflow issue (#99)

This commit is contained in:
Zhang Minghan 2024-03-11 22:56:04 +08:00
parent 6f5dae5ca9
commit 82d0f3c4b1
7 changed files with 160 additions and 54 deletions

View File

@ -14,6 +14,7 @@
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.18", "@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-context-menu": "^2.1.4", "@radix-ui/react-context-menu": "^2.1.4",

60
app/pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ dependencies:
'@headlessui/tailwindcss': '@headlessui/tailwindcss':
specifier: ^0.2.0 specifier: ^0.2.0
version: 0.2.0(tailwindcss@3.3.5) version: 0.2.0(tailwindcss@3.3.5)
'@radix-ui/react-accordion':
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-alert-dialog': '@radix-ui/react-alert-dialog':
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) version: 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
@ -746,6 +749,35 @@ packages:
'@babel/runtime': 7.23.2 '@babel/runtime': 7.23.2
dev: false dev: false
/@radix-ui/react-accordion@1.1.2(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.2
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@types/react': 18.2.33
'@types/react-dom': 18.2.14
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-alert-dialog@1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-alert-dialog@1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==} resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==}
peerDependencies: peerDependencies:
@ -821,6 +853,34 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.2
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.33)(react@18.2.0)
'@types/react': 18.2.33
'@types/react-dom': 18.2.14
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies: peerDependencies:

View File

@ -152,7 +152,7 @@ input[type="number"] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 0.5rem 0; margin: 0.5rem 0;
padding: 1.5rem; padding: 1rem 1.5rem 0.25rem;
border-radius: var(--radius); border-radius: var(--radius);
background: hsl(var(--background)); background: hsl(var(--background));
color: hsl(var(--text)); color: hsl(var(--text));
@ -191,7 +191,6 @@ input[type="number"] {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
margin-bottom: 1.5rem;
align-items: center; align-items: center;
transform: translateY(-0.25rem); transform: translateY(-0.25rem);
} }
@ -205,6 +204,7 @@ input[type="number"] {
line-height: 1.1rem; line-height: 1.1rem;
color: hsl(var(--text-secondary)); color: hsl(var(--text-secondary));
transition: .25s; transition: .25s;
text-decoration: none !important;
&:before { &:before {
content: ''; content: '';
@ -273,6 +273,10 @@ input[type="number"] {
height: 0.25rem; height: 0.25rem;
} }
.paragraph-content {
transition: 1.5s ease-in-out;
}
.paragraph-footer { .paragraph-footer {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -1,8 +1,13 @@
import React from "react"; import React from "react";
import { ChevronDown, Info } from "lucide-react"; import { Info } from "lucide-react";
import { cn } from "@/components/ui/lib/utils.ts"; import { cn } from "@/components/ui/lib/utils.ts";
import { Button } from "@/components/ui/button.tsx";
import Markdown from "@/components/Markdown.tsx"; import Markdown from "@/components/Markdown.tsx";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion.tsx";
export type ParagraphProps = { export type ParagraphProps = {
title?: string; title?: string;
@ -10,8 +15,6 @@ export type ParagraphProps = {
className?: string; className?: string;
configParagraph?: boolean; configParagraph?: boolean;
isCollapsed?: boolean; isCollapsed?: boolean;
onCollapse?: () => void;
defaultCollapsed?: boolean;
}; };
function Paragraph({ function Paragraph({
@ -20,51 +23,25 @@ function Paragraph({
className, className,
configParagraph, configParagraph,
isCollapsed, isCollapsed,
onCollapse,
defaultCollapsed,
}: ParagraphProps) { }: ParagraphProps) {
const [collapsed, setCollapsed] = React.useState(defaultCollapsed ?? false);
React.useEffect(() => onCollapse && onCollapse(), [collapsed]);
return ( return (
<div <Accordion type={`single`} collapsible={isCollapsed} defaultValue={"item"}>
<AccordionItem
value={`item`}
className={cn( className={cn(
`paragraph`, `paragraph`,
configParagraph && `config-paragraph`, configParagraph && `config-paragraph`,
isCollapsed && `collapsable`,
collapsed && `collapsed`,
className, className,
)} )}
> >
<div <AccordionTrigger className={`paragraph-header`}>
className={`paragraph-header`} <div className={`paragraph-title`}>{title ?? ""}</div>
onClick={() => setCollapsed(!collapsed)} </AccordionTrigger>
> <AccordionContent className={`paragraph-content mt-2`}>
{title && <div className={`paragraph-title`}>{title}</div>}
<div className={`grow`} />
{isCollapsed && (
<Button size={`icon`} variant={`ghost`} className={`w-8 h-8`}>
<ChevronDown
className={cn(
`w-4 h-4 transition-transform duration-300`,
collapsed && `transform rotate-180`,
)}
/>
</Button>
)}
</div>
<div
className={`paragraph-content`}
style={
{
"--max-height": collapsed ? "0px" : "1000px",
} as React.CSSProperties
}
>
{children} {children}
</div> </AccordionContent>
</div> </AccordionItem>
</Accordion>
); );
} }
@ -88,6 +65,7 @@ type ParagraphDescriptionProps = {
children: string; children: string;
border?: boolean; border?: boolean;
}; };
export function ParagraphDescription({ export function ParagraphDescription({
children, children,
border, border,

View File

@ -0,0 +1,56 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "@/components/ui/lib/utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className,
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@ -31,14 +31,21 @@ export interface FlexibleTextareaProps extends TextareaProps {
const FlexibleTextarea = React.forwardRef< const FlexibleTextarea = React.forwardRef<
HTMLTextAreaElement, HTMLTextAreaElement,
FlexibleTextareaProps FlexibleTextareaProps
>(({ rows = 1, ...props }, ref) => { >(({ rows = 1, className, ...props }, ref) => {
const lines = useMemo(() => { const lines = useMemo(() => {
const value = props.value?.toString() || ""; const value = props.value?.toString() || "";
const count = value.split("\n").length + 1; const count = value.split("\n").length + 1;
return Math.max(rows, count); return Math.max(rows, count);
}, [props.value]); }, [props.value]);
return <Textarea ref={ref} rows={lines} {...props} />; return (
<Textarea
className={cn("resize-none no-scrollbar", className)}
ref={ref}
rows={lines}
{...props}
/>
);
}); });
FlexibleTextarea.displayName = "FlexibleTextarea"; FlexibleTextarea.displayName = "FlexibleTextarea";

View File

@ -45,7 +45,7 @@ import {
import { DialogTitle } from "@radix-ui/react-dialog"; import { DialogTitle } from "@radix-ui/react-dialog";
import Require from "@/components/Require.tsx"; import Require from "@/components/Require.tsx";
import { Loader2, Settings2 } from "lucide-react"; import { Loader2, Settings2 } from "lucide-react";
import { Textarea } from "@/components/ui/textarea.tsx"; import { FlexibleTextarea } from "@/components/ui/textarea.tsx";
import Tips from "@/components/Tips.tsx"; import Tips from "@/components/Tips.tsx";
import { cn } from "@/components/ui/lib/utils.ts"; import { cn } from "@/components/ui/lib/utils.ts";
import { Switch } from "@/components/ui/switch.tsx"; import { Switch } from "@/components/ui/switch.tsx";
@ -527,7 +527,7 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
</ParagraphItem> </ParagraphItem>
<ParagraphItem rowLayout={true}> <ParagraphItem rowLayout={true}>
<Label>{t("admin.system.announcement")}</Label> <Label>{t("admin.system.announcement")}</Label>
<Textarea <FlexibleTextarea
value={data.announcement} value={data.announcement}
rows={12} rows={12}
onChange={(e) => onChange={(e) =>
@ -541,7 +541,7 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
</ParagraphItem> </ParagraphItem>
<ParagraphItem rowLayout={true}> <ParagraphItem rowLayout={true}>
<Label>{t("admin.system.contact")}</Label> <Label>{t("admin.system.contact")}</Label>
<Textarea <FlexibleTextarea
value={data.contact} value={data.contact}
rows={6} rows={6}
onChange={(e) => onChange={(e) =>
@ -556,9 +556,9 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
<ParagraphSpace /> <ParagraphSpace />
<ParagraphItem rowLayout={true}> <ParagraphItem rowLayout={true}>
<Label>{t("admin.system.footer")}</Label> <Label>{t("admin.system.footer")}</Label>
<Textarea <FlexibleTextarea
value={data.footer}
rows={6} rows={6}
value={data.footer}
onChange={(e) => onChange={(e) =>
dispatch({ dispatch({
type: "update:site.footer", type: "update:site.footer",