Skip to content

Commit

Permalink
feat(components): add copyButton functionality and update theme confi…
Browse files Browse the repository at this point in the history
…gurations in accordion component
  • Loading branch information
NeuroNexul committed Sep 26, 2024
1 parent d1d289b commit e05e517
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 42 deletions.
29 changes: 16 additions & 13 deletions apps/www/components/preview/accordion/accordionVariants.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"use client";

import { VariantProps } from "class-variance-authority";
import { Link } from "lucide-react";
import React from "react";
import {
Accordion,
Accordions,
AccordionsVariants,
} from "ruru-ui/components/accordion";
import { Button } from "ruru-ui/components/button";
import { Checkbox } from "ruru-ui/components/checkbox";
import { Label } from "ruru-ui/components/label";
import {
Select,
Expand All @@ -29,6 +28,8 @@ export default function AccordionVariants({}: Props) {
const [theme, setTheme] =
React.useState<VariantProps<typeof AccordionsVariants>["theme"]>("default");

const [showCopyButton, setShowCopyButton] = React.useState(false);

return (
<div className="min-h-[400px] flex flex-col gap-6 justify-center items-center">
<div className="flex flex-row gap-2 items-center">
Expand Down Expand Up @@ -69,30 +70,32 @@ export default function AccordionVariants({}: Props) {
))}
</SelectContent>
</Select>

<div className="border rounded-md py-2 px-3 flex items-center gap-2">
<Checkbox
id="showCopyButton"
checked={showCopyButton}
onCheckedChange={(c) => setShowCopyButton(!!c)}
/>
<Label htmlFor="showCopyButton">Copy Button</Label>
</div>
</div>

<Accordions
className="max-w-[600px]"
type="single"
variant={variant}
theme={theme}
showCopyButton={showCopyButton}
>
{Array.from(new Array(4)).map((_, index) => (
<Accordion
id={`item-${index}`}
key={index}
trigger={
<>
<Button
variant={"tertiary"}
className="text-current hover:opacity-70 hover:bg-transparent transition-all"
>
<Link size={16} />
</Button>
<span className="w-full text-left py-4 text-current">
Accordion Item {index + 1}
</span>
</>
<span className="w-full text-left py-4 text-current">
Accordion Item {index + 1}
</span>
}
>
This is the content of the accordion item {index + 1}
Expand Down
8 changes: 1 addition & 7 deletions apps/www/components/preview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,7 @@ export default {
),
accordion: (
<Wrapper>
<Accordions
type="single"
collapsible
className="w-full max-w-[500px]"
variant="default"
theme="primary"
>
<Accordions type="single" collapsible className="w-full max-w-[500px]">
<Accordion id="item-1" trigger="Is it accessible?" TClassName="py-4">
Yes. It adheres to the WAI-ARIA design pattern.
</Accordion>
Expand Down
21 changes: 6 additions & 15 deletions apps/www/content/docs/components/accordion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ The `Accordion` component is a vertically stacked set of interactive headings th
type="single"
collapsible
className="w-full max-w-[500px]"
variant="default"
theme="primary"
>
<Accordion id="item-1" trigger="Is it accessible?" TClassName="py-4">Yes. It adheres to the WAI-ARIA design pattern.</Accordion>
<Accordion id="item-2" trigger="Is it responsive?" TClassName="py-4">Yes. It is responsive and mobile-friendly.</Accordion>
Expand All @@ -75,8 +73,6 @@ export function Demo() {
type="single"
collapsible
className="w-full max-w-[500px]"
variant="default"
theme="primary"
>
<Accordion id="item-1" trigger="Is it accessible?" TClassName="py-4">
Yes. It adheres to the WAI-ARIA design pattern.
Expand Down Expand Up @@ -178,23 +174,16 @@ export function Demo() {
type="single"
variant="default"
theme="default"
showCopyButton={true}
>
{Array.from(new Array(4)).map((_, index) => (
<Accordion
id={`item-${index}`}
key={index}
trigger={
<>
<Button
variant={"tertiary"}
className="text-current hover:opacity-70 hover:bg-transparent transition-all"
>
<Link size={16} />
</Button>
<span className="w-full text-left py-4 text-current">
Accordion Item {index + 1}
</span>
</>
<span className="w-full text-left py-4 text-current">
Accordion Item {index + 1}
</span>
}
>
This is the content of the accordion item {index + 1}
Expand Down Expand Up @@ -292,6 +281,7 @@ import {
| `collapsible` | Whether the accordion is collapsible. | `boolean` | `false` |
| `variant` | The variant of the accordion. | `"default" \| "primary" \| "none"` | `"default"` |
| `theme` | The theme of the accordion. | `"default" \| "primary" \| "secondary" \| "tertiary"` | `"default"` |
| `showCopyButton` | Weather to show copy button. | `boolean` | `false` |
| `props` | Other props | typeof `AccordionPrimitive.Root` with `ref` | `{}` |

| Data Attribute | Description | Values |
Expand All @@ -308,6 +298,7 @@ import {
| `trigger` | The trigger of the accordion. | `ReactNode` | `undefined` |
| `TClassName` | The class name of the trigger. | `string` | `undefined` |
| `CClassName` | The class name of the content. | `string` | `undefined` |
| `copyText` | Costom text to copy. | `string` | `undefined` |
| `props` | Other props | typeof `AccordionPrimitive.Item` with `ref` | `{}` |

| Data Attribute | Description | Values |
Expand Down
105 changes: 98 additions & 7 deletions packages/ui/src/components/accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { CheckIcon, ChevronDownIcon, Link2Icon } from "@radix-ui/react-icons";
import { cn } from "@/utils/cn";
import { Button } from "./button";

const AccordionRoot = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Root>,
Expand Down Expand Up @@ -43,6 +44,8 @@ const AccordionTrigger = React.forwardRef<
hideChevron?: boolean;
chevronPosition?: "left" | "right";
chevronRotation?: "full" | "half";
before?: React.ReactNode;
after?: React.ReactNode;
}
>(
(
Expand All @@ -52,11 +55,14 @@ const AccordionTrigger = React.forwardRef<
hideChevron = false,
chevronPosition,
chevronRotation = "full",
before = null,
after = null,
...props
},
ref
) => (
<AccordionPrimitive.Header className="flex !m-0 !text-current">
<AccordionPrimitive.Header className="flex !m-0 !text-current items-center">
{before}
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
Expand All @@ -80,6 +86,7 @@ const AccordionTrigger = React.forwardRef<
/>
)}
</AccordionPrimitive.Trigger>
{after}
</AccordionPrimitive.Header>
)
);
Expand Down Expand Up @@ -111,8 +118,8 @@ export const AccordionsVariants = cva(cn("w-full rounded-lg"), {
none: cn("border-none rounded-none"),
},
theme: {
default: cn("text-primary-foreground"),
primary: "border",
default: cn("border"),
primary: cn("text-primary-foreground"),
secondary: cn("border text-foreground"),
tertiary: cn("border-none text-primary-foreground"),
},
Expand All @@ -137,8 +144,8 @@ export const AccordionVariants = cva(
),
},
theme: {
default: cn("bg-primary hover:bg-primary/85 text-primary-foreground"),
primary: cn("bg-none text-foreground"),
default: cn("bg-none text-foreground"),
primary: cn("bg-primary hover:bg-primary/85 text-primary-foreground"),
secondary: cn(
"bg-secondary/55 hover:bg-secondary text-secondary-foreground"
),
Expand Down Expand Up @@ -204,6 +211,20 @@ const Accordions = React.forwardRef<
* </Accordions>
*/
theme?: VariantProps<typeof AccordionsVariants>["theme"];
/**
* To declare weather to show the copy button or not.
* @default false
* @type {boolean}
*
* @example
* ```tsx
* <Accordions showCopyButton>
* <Accordion id="item-1" trigger="Item 1">
* Content 1
* </Accordion>
* </Accordions>
*/
showCopyButton?: boolean;
}
>(
(
Expand All @@ -212,6 +233,7 @@ const Accordions = React.forwardRef<
type = "single",
variant = "default",
theme = "default",
showCopyButton = false,
children,
...props
},
Expand All @@ -220,7 +242,7 @@ const Accordions = React.forwardRef<
const newClildren = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
// @ts-expect-error -- Unknown type
return React.cloneElement(child, { variant, theme });
return React.cloneElement(child, { variant, theme, showCopyButton });
}
return child;
});
Expand Down Expand Up @@ -270,8 +292,21 @@ const Accordion = React.forwardRef<
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>,
"value"
> & {
// These are the props coming from the parent component
variant?: VariantProps<typeof AccordionVariants>["variant"];
theme?: VariantProps<typeof AccordionVariants>["theme"];
showCopyButton?: boolean;
/**
* The text to be copied. `showCopyButton` need to be set true in `Accordions`.
* @type {string}
*
* @example
* ```tsx
* <Accordion id="item-1" trigger="Item 1" copyText="https://example.com">
* Content 1
* </Accordion>
*/
copyText?: string;
/**
* The class name of the trigger.
* @type {string}
Expand Down Expand Up @@ -311,6 +346,8 @@ const Accordion = React.forwardRef<
className,
variant = "default",
theme = "default",
showCopyButton = false,
copyText,
TClassName,
CClassName,
trigger,
Expand All @@ -330,6 +367,7 @@ const Accordion = React.forwardRef<
{...props}
>
<AccordionTrigger
id={id}
className={cn("px-4 py-0", TClassName)}
chevronPosition={
["primary"].includes(variant || "") ? "left" : "right"
Expand All @@ -339,6 +377,7 @@ const Accordion = React.forwardRef<
}
data-variant={variant}
data-theme={theme}
after={showCopyButton && <CopyButton id={id} copyText={copyText} />}
>
{trigger}
</AccordionTrigger>
Expand All @@ -354,6 +393,58 @@ const Accordion = React.forwardRef<
);
Accordion.displayName = "Accordion";

function CopyButton({ id, copyText }: { id: string; copyText?: string }) {
const [checked, setChecked] = React.useState(false);
const timeoutRef = React.useRef<number | null>(null);

const onCopy: React.MouseEventHandler<HTMLButtonElement> = React.useCallback(
(e) => {
e.stopPropagation();

const url = new URL(window.location.href);
url.hash = id;

navigator.clipboard.writeText(copyText || url.toString());
},
[]
);

const onClick: React.MouseEventHandler<HTMLButtonElement> = React.useCallback(
(e) => {
if (timeoutRef.current) window.clearTimeout(timeoutRef.current);
timeoutRef.current = window.setTimeout(() => {
setChecked(false);
}, 1500);
onCopy(e);
setChecked(true);
},
[onCopy]
);

// Avoid updates after being unmounted
React.useEffect(() => {
return () => {
if (timeoutRef.current) window.clearTimeout(timeoutRef.current);
};
}, []);

return (
<Button
variant={"tertiary"}
className={cn(
"text-current hover:opacity-70 hover:bg-transparet transition-all"
)}
onClick={onClick}
>
{checked ? (
<CheckIcon className="size-4" />
) : (
<Link2Icon className="size-4" />
)}
</Button>
);
}

export {
AccordionRoot,
AccordionItem,
Expand Down

0 comments on commit e05e517

Please sign in to comment.