Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions apps/ui/components/ui/date-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Description, FieldError, FieldGroup, Label } from "./field"
import { Popover } from "./popover"
import { composeTailwindRenderProps } from "./primitive"
import { RangeCalendar } from "./range-calendar"
import { Typography } from "./typography"

interface DatePickerOverlayProps
extends Omit<DialogProps, "children" | "className" | "style">,
Expand Down Expand Up @@ -78,30 +79,51 @@ interface DatePickerProps<T extends DateValue> extends DatePickerPrimitiveProps<
label?: string
description?: string
errorMessage?: string | ((validation: ValidationResult) => string)
leftDescription?: React.ReactNode
rightDescription?: React.ReactNode
}

const DatePicker = <T extends DateValue>({
label,
className,
description,
errorMessage,
leftDescription,
rightDescription,
...props
}: DatePickerProps<T>) => {
return (
<DatePickerPrimitive
{...props}
className={composeTailwindRenderProps(className, "group/date-picker flex flex-col gap-y-1")}
className={composeTailwindRenderProps(className, "group flex w-full flex-col gap-y-1")}
>
{label && <Label isRequired={props.isRequired}>{label}</Label>}
<FieldGroup className="min-w-40">
{label &&
<Typography as="div">
<Label isRequired={props.isRequired}>{label}</Label>
</Typography>
}
<FieldGroup className="min-w-40 h-10">
<DateInput className="w-full px-2 text-base sm:text-sm" />
<DatePickerIcon />
</FieldGroup>
{description && <Description>{description}</Description>}
<FieldError>{errorMessage}</FieldError>
{description &&
<Typography as="div">
<Description>{description}</Description>
</Typography>
}

{(leftDescription || errorMessage || rightDescription) && (
<Typography as="div">
<div className="mt-1 flex justify-between text-sm text-muted-fg">
<div>{errorMessage ? <FieldError>{errorMessage}</FieldError> : leftDescription}</div>
<div>{rightDescription}</div>
</div>
</Typography>
)}

<DatePickerOverlay />
</DatePickerPrimitive>
)
}
export type { DatePickerProps, DateValue, ValidationResult }
export { DatePicker, DatePickerIcon, DatePickerOverlay }
export { DatePicker, DatePickerIcon, DatePickerOverlay }
30 changes: 25 additions & 5 deletions apps/ui/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,46 @@ interface SelectProps<T extends object> extends SelectPrimitiveProps<T> {
errorMessage?: string | ((validation: ValidationResult) => string)
items?: Iterable<T>
className?: string
leftDescription?: React.ReactNode
rightDescription?: React.ReactNode
}

const Select = <T extends object>({
label,
description,
errorMessage,
className,
leftDescription,
rightDescription,
...props
}: SelectProps<T>) => {
return (
<SelectPrimitive
{...props}
className={composeTailwindRenderProps(className, "group flex w-full flex-col gap-y-1.5")}
className={composeTailwindRenderProps(className, "group flex w-full flex-col gap-y-1")}
>
{(values) => (
<>
{label && <Label>{label}</Label>}
{label &&
<Typography as="div">
<Label isRequired={props.isRequired}>{label}</Label>
</Typography>
}
{typeof props.children === "function" ? props.children(values) : props.children}
{description && <Description>{description}</Description>}
<FieldError>{errorMessage}</FieldError>
{description &&
<Typography as="div">
<Description>{description}</Description>
</Typography>
}

{(leftDescription || errorMessage || rightDescription) && (
<Typography as="div">
<div className="mt-1 flex justify-between text-sm text-muted-fg">
<div>{errorMessage ? <FieldError>{errorMessage}</FieldError> : leftDescription}</div>
<div>{rightDescription}</div>
</div>
</Typography>
)}
</>
)}
</SelectPrimitive>
Expand Down Expand Up @@ -149,4 +169,4 @@ Select.Trigger = SelectTrigger
Select.List = SelectList

export type { SelectProps, SelectTriggerProps }
export { Select }
export { Select }
51 changes: 39 additions & 12 deletions apps/ui/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import {
composeRenderProps,
} from "react-aria-components"
import { tv } from "tailwind-variants"
import { useState, useEffect } from "react"

import { Description, FieldError, Label } from "./field"
import { composeTailwindRenderProps, focusStyles } from "./primitive"
import { Typography } from "./typography"

const textareaStyles = tv({
extend: focusStyles,
base: "field-sizing-content max-h-96 min-h-16 w-full min-w-0 rounded-lg border border-input px-2.5 py-2 text-base shadow-xs outline-hidden transition duration-200 disabled:opacity-50 sm:text-sm",
base: "field-sizing-content max-h-96 min-h-16 w-full min-w-0 rounded-lg border border-input px-2.5 py-2 pb-6 text-base shadow-xs outline-hidden transition duration-200 disabled:opacity-50 sm:text-sm break-words whitespace-pre-wrap overflow-wrap-anywhere",
})

interface TextareaProps extends TextFieldPrimitiveProps {
Expand All @@ -26,6 +27,8 @@ interface TextareaProps extends TextFieldPrimitiveProps {
errorMessage?: string | ((validation: ValidationResult) => string)
className?: string
ref?: React.Ref<HTMLDivElement>
showCharacterCount?: boolean
maxLength?: number
}

const Textarea = ({
Expand All @@ -35,26 +38,50 @@ const Textarea = ({
description,
errorMessage,
ref,
showCharacterCount,
maxLength,
...props
}: TextareaProps) => {
const [currentLength, setCurrentLength] = useState(props.value?.toString().length || 0);

useEffect(() => {
setCurrentLength(props.value?.toString().length || 0);
}, [props.value]);

const getCountText = () => {
if (maxLength) {
return `${currentLength}/${maxLength}`;
}
return `${currentLength}字`;
};

return (
<TextFieldPrimitive
ref={ref}
{...props}
className={composeTailwindRenderProps(className, "group flex flex-col gap-y-1.5")}
>
{label && <Label isRequired={props.isRequired}>{label}</Label>}
<Typography as="div">
<TextAreaPrimitive
placeholder={placeholder}
className={composeRenderProps(className, (className, renderProps) =>
textareaStyles({
...renderProps,
className,
}),
)}
/>
</Typography>
<div className="relative">
<Typography as="div">
<TextAreaPrimitive
placeholder={placeholder}
maxLength={maxLength}
className={composeRenderProps(className, (className, renderProps) =>
textareaStyles({
...renderProps,
className,
}),
)}
style={{ wordWrap: "break-word", overflowWrap: "break-word" }}
/>
</Typography>
{showCharacterCount && currentLength > 0 && (
<div className="absolute bottom-1 right-2.5 text-xs text-muted-fg select-none pointer-events-none">
{getCountText()}
</div>
)}
</div>
{description && <Description>{description}</Description>}
<FieldError>{errorMessage}</FieldError>
</TextFieldPrimitive>
Expand Down