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": {
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-context-menu": "^2.1.4",

60
app/pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ dependencies:
'@headlessui/tailwindcss':
specifier: ^0.2.0
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':
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)
@ -746,6 +749,35 @@ packages:
'@babel/runtime': 7.23.2
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):
resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==}
peerDependencies:
@ -821,6 +853,34 @@ packages:
react-dom: 18.2.0(react@18.2.0)
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):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies:

View File

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

View File

@ -1,8 +1,13 @@
import React from "react";
import { ChevronDown, Info } from "lucide-react";
import { Info } from "lucide-react";
import { cn } from "@/components/ui/lib/utils.ts";
import { Button } from "@/components/ui/button.tsx";
import Markdown from "@/components/Markdown.tsx";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion.tsx";
export type ParagraphProps = {
title?: string;
@ -10,8 +15,6 @@ export type ParagraphProps = {
className?: string;
configParagraph?: boolean;
isCollapsed?: boolean;
onCollapse?: () => void;
defaultCollapsed?: boolean;
};
function Paragraph({
@ -20,51 +23,25 @@ function Paragraph({
className,
configParagraph,
isCollapsed,
onCollapse,
defaultCollapsed,
}: ParagraphProps) {
const [collapsed, setCollapsed] = React.useState(defaultCollapsed ?? false);
React.useEffect(() => onCollapse && onCollapse(), [collapsed]);
return (
<div
className={cn(
`paragraph`,
configParagraph && `config-paragraph`,
isCollapsed && `collapsable`,
collapsed && `collapsed`,
className,
)}
>
<div
className={`paragraph-header`}
onClick={() => setCollapsed(!collapsed)}
>
{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>
<Accordion type={`single`} collapsible={isCollapsed} defaultValue={"item"}>
<AccordionItem
value={`item`}
className={cn(
`paragraph`,
configParagraph && `config-paragraph`,
className,
)}
</div>
<div
className={`paragraph-content`}
style={
{
"--max-height": collapsed ? "0px" : "1000px",
} as React.CSSProperties
}
>
{children}
</div>
</div>
<AccordionTrigger className={`paragraph-header`}>
<div className={`paragraph-title`}>{title ?? ""}</div>
</AccordionTrigger>
<AccordionContent className={`paragraph-content mt-2`}>
{children}
</AccordionContent>
</AccordionItem>
</Accordion>
);
}
@ -88,6 +65,7 @@ type ParagraphDescriptionProps = {
children: string;
border?: boolean;
};
export function ParagraphDescription({
children,
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<
HTMLTextAreaElement,
FlexibleTextareaProps
>(({ rows = 1, ...props }, ref) => {
>(({ rows = 1, className, ...props }, ref) => {
const lines = useMemo(() => {
const value = props.value?.toString() || "";
const count = value.split("\n").length + 1;
return Math.max(rows, count);
}, [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";

View File

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