Skip to content

Commit

Permalink
fix & document: Dropdown component (#839)
Browse files Browse the repository at this point in the history
  • Loading branch information
KenzoBenzo authored May 10, 2024
1 parent 1b18dc5 commit abf6adb
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function renderTypeShorthandRoot(
const typeShorthand = renderTypeShorthand(unwrapOptional(shape, types), { nullable: isResponse }, types);
const unaliasedShape = unwrapAlias(shape, types);
return (
<span className="t-muted inline-flex items-baseline gap-2 text-xs">
<span className="text-inherit inline-flex items-baseline gap-2 text-xs">
<span>{typeShorthand}</span>
{unaliasedShape.type === "optional" ? (
<span>{isResponse ? "Optional" : "Optional"}</span>
Expand Down
154 changes: 154 additions & 0 deletions packages/ui/app/src/components/FernDropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { CaretDownIcon } from "@radix-ui/react-icons";
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { FernButton } from "./FernButton";
import { FernDropdown } from "./FernDropdown";

const meta: Meta<typeof FernDropdown> = {
title: "General/FernDropdown",
component: FernDropdown,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
args: {
defaultOpen: true,
usePortal: true,
children: (
<FernButton
text={<span className="t-muted">Select an enum...</span>}
variant="outlined"
rightIcon={<CaretDownIcon />}
className="w-full text-left"
/>
),
options: [
{
type: "value",
label: "Option 1",
value: "option1",
helperText: "This is a helper text",
},
{
type: "value",
label: "Option 2",
value: "option2",
helperText: "This is a helper text",
},
{
type: "separator",
},
{
type: "value",
label: "Option 3",
value: "option3",
tooltip: "This is a tooltip",
helperText: "This is a helper text",
},
{
type: "value",
label: "Option 4",
value: "option4",
helperText: (
<span className="text-inherit inline-flex items-baseline gap-2 text-xs">
<span>helloWorld</span>
<span>Optional</span>
<span>Object</span>
</span>
),
},
],
},
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
defaultOpen: true,
},
render: (args) => {
const [value, setValue] = useState<string>();
return <FernDropdown value={value} onValueChange={setValue} {...args} />;
},
};

export const RealWorldExample: Story = {
args: {
options: [
{
type: "value",
value: "mp3_22050_32",
label: "mp3_22050_32",
helperText: "Output format, mp3 with 22.05kHz sample rate at 32kbps",
},
{
type: "value",
value: "mp3_44100_128",
label: "mp3_44100_128",
helperText: "Default output format, mp3 with 44.1kHz sample rate at 128kbps",
},
{
type: "value",
value: "mp3_44100_192",
label: "mp3_44100_192",
helperText: "Output format, mp3 with 44.1kHz sample rate at 192kbps.",
},
{
type: "value",
value: "mp3_44100_32",
label: "mp3_44100_32",
helperText: "Output format, mp3 with 44.1kHz sample rate at 32kbps",
},
{
type: "value",
value: "mp3_44100_64",
label: "mp3_44100_64",
helperText: "Output format, mp3 with 44.1kHz sample rate at 64kbps",
},
{
type: "value",
value: "mp3_44100_96",
label: "mp3_44100_96",
helperText: "Output format, mp3 with 44.1kHz sample rate at 96kbps",
},
{
type: "value",
value: "pcm_16000",
label: "pcm_16000",
helperText: "PCM format (S16LE) with 16kHz sample rate.",
},
{
type: "value",
value: "pcm_22050",
label: "pcm_22050",
helperText: "PCM format (S16LE) with 22.05kHz sample rate.",
},
{
type: "value",
value: "pcm_24000",
label: "pcm_24000",
helperText: "PCM format (S16LE) with 24kHz sample rate.",
},
{
type: "value",
value: "pcm_44100",
label: "pcm_44100",
helperText:
"PCM format (S16LE) with 44.1kHz sample rate. Requires you to be subscribed to Independent Publisher tier or above.",
},
{
type: "value",
value: "ulaw_8000",
label: "ulaw_8000",
helperText:
"μ-law format (sometimes written mu-law, often approximated as u-law) with 8kHz sample rate. Note that this format is commonly used for Twilio audio inputs.",
},
],
},
render: (args) => {
const [value, setValue] = useState<string>();
return <FernDropdown value={value} onValueChange={setValue} {...args} />;
},
};
50 changes: 27 additions & 23 deletions packages/ui/app/src/components/FernDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export declare namespace FernDropdown {
usePortal?: boolean;
side?: "top" | "right" | "bottom" | "left";
align?: "start" | "center" | "end";
defaultOpen?: boolean;
}
}

Expand All @@ -46,8 +47,9 @@ export function FernDropdown({
usePortal = true,
side,
align,
defaultOpen = false,
}: PropsWithChildren<FernDropdown.Props>): ReactElement {
const [isOpen, setOpen] = useState(false);
const [isOpen, setOpen] = useState(defaultOpen);
const handleOpenChange = useCallback(
(toOpen: boolean) => {
setOpen(toOpen);
Expand Down Expand Up @@ -76,15 +78,15 @@ export function FernDropdown({
);

return (
<DropdownMenu.Root onOpenChange={handleOpenChange} open={isOpen} modal={true}>
<DropdownMenu.Root onOpenChange={handleOpenChange} open={isOpen} modal={true} defaultOpen={defaultOpen}>
<DropdownMenu.Trigger asChild={true}>{children}</DropdownMenu.Trigger>
{usePortal ? <DropdownMenu.Portal>{renderDropdownContent()}</DropdownMenu.Portal> : renderDropdownContent()}
</DropdownMenu.Root>
);
}

function FernDropdownItemValue({ option, value }: { option: FernDropdown.ValueOption; value: string | undefined }) {
const helperTextRef = useRef<HTMLSpanElement>(null);
const helperTextRef = useRef<HTMLDivElement>(null);
const activeRef = useRef<HTMLButtonElement & HTMLAnchorElement>(null);
useEffect(() => {
if (option.value === value) {
Expand Down Expand Up @@ -112,29 +114,31 @@ function FernDropdownItemValue({ option, value }: { option: FernDropdown.ValueOp

function renderButtonContent() {
return (
<>
{value != null && (
<div className="w-60">
<div className="flex items-center">
<span className="fern-dropdown-item-indicator">
<DropdownMenu.ItemIndicator asChild={true}>
<CheckIcon />
</DropdownMenu.ItemIndicator>
{value != null && (
<DropdownMenu.ItemIndicator asChild={true}>
<CheckIcon />
</DropdownMenu.ItemIndicator>
)}
</span>
)}

{option.icon && <span className="mr-2 inline-flex items-center">{option.icon}</span>}
<span className="inline-flex items-baseline">
<span className={option.labelClassName}>{option.label ?? option.value}</span>
{option.helperText != null && (
<span className="ml-2 max-w-[300px] shrink truncate text-xs opacity-60" ref={helperTextRef}>
{option.helperText}
</span>
)}
</span>
<span className="ml-auto space-x-1 pl-2">
{option.rightElement && <span>{option.rightElement}</span>}
{(isEllipsisActive || (option.tooltip != null && option.tooltip !== "")) && <InfoCircledIcon />}
</span>
</>
{option.icon && <span className="mr-2 inline-flex items-center">{option.icon}</span>}

<div className={option.labelClassName}>{option.label ?? option.value}</div>
<span className="ml-auto space-x-1 pl-2">
{option.rightElement && <span>{option.rightElement}</span>}
{(isEllipsisActive || (option.tooltip != null && option.tooltip !== "")) && <InfoCircledIcon />}
</span>
</div>

{option.helperText != null && (
<div className="mt-0.5 ml-5 text-xs opacity-60 text-start leading-snug" ref={helperTextRef}>
{option.helperText}
</div>
)}
</div>
);
}

Expand Down

0 comments on commit abf6adb

Please sign in to comment.