Skip to content
Closed
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
8 changes: 2 additions & 6 deletions src/components/CircleButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,14 @@ export const CircleButton = ({
onClick={onClick}
aria-label={ariaLabel}
href={href}
className={sharedClassName}
class={sharedClassName}
>
{children}
</a>
);
}
return (
<button
onClick={onClick}
aria-label={ariaLabel}
className={sharedClassName}
>
<button onClick={onClick} aria-label={ariaLabel} class={sharedClassName}>
{children}
</button>
);
Expand Down
31 changes: 21 additions & 10 deletions src/components/CodeEmbed/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from "preact/hooks";
import { useLiveRegion } from '../hooks/useLiveRegion';
import { useLiveRegion } from "../hooks/useLiveRegion";
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { cdnLibraryUrl, cdnSoundUrl } from "@/src/globals/globals";
Expand Down Expand Up @@ -38,14 +38,19 @@ export const CodeEmbed = (props) => {
);

let { previewWidth, previewHeight } = props;
const canvasMatch = /createCanvas\(\s*(\d+),\s*(\d+)\s*(?:,\s*(?:P2D|WEBGL)\s*)?\)/m.exec(initialCode);
const canvasMatch =
/createCanvas\(\s*(\d+),\s*(\d+)\s*(?:,\s*(?:P2D|WEBGL)\s*)?\)/m.exec(
initialCode,
);
if (canvasMatch) {
previewWidth = previewWidth || parseFloat(canvasMatch[1]);
previewHeight = previewHeight || parseFloat(canvasMatch[2]);
}

// Quick hack to make room for DOM that gets added below the canvas by default
const domMatch = /create(Button|Select|P|Div|Input|ColorPicker)/.exec(initialCode);
const domMatch = /create(Button|Select|P|Div|Input|ColorPicker)/.exec(
initialCode,
);
if (domMatch && previewHeight) {
previewHeight += 100;
}
Expand Down Expand Up @@ -87,15 +92,15 @@ export const CodeEmbed = (props) => {
}
}, []);

if (!rendered) return <div className="code-placeholder" />;
if (!rendered) return <div class="code-placeholder" />;

return (
<div
className={`my-md flex w-full flex-col gap-[20px] overflow-hidden ${props.allowSideBySide ? "lg:flex-row" : ""} ${props.fullWidth ? "full-width" : ""}`}
class={`my-md flex w-full flex-col gap-[20px] overflow-hidden ${props.allowSideBySide ? "lg:flex-row" : ""} ${props.fullWidth ? "full-width" : ""}`}
>
{props.previewable ? (
<div
className={`ml-0 flex w-fit gap-[20px] ${largeSketch ? "flex-col" : (props.allowSideBySide ? "" : "flex-col lg:flex-row")}`}
class={`ml-0 flex w-fit gap-[20px] ${largeSketch ? "flex-col" : props.allowSideBySide ? "" : "flex-col lg:flex-row"}`}
>
<div>
<CodeFrame
Expand All @@ -105,10 +110,16 @@ export const CodeEmbed = (props) => {
base={props.base}
frameRef={codeFrameRef}
lazyLoad={props.lazyLoad}
scripts={props.includeSound ? [cdnLibraryUrl, cdnSoundUrl] :[cdnLibraryUrl]}
scripts={
props.includeSound
? [cdnLibraryUrl, cdnSoundUrl]
: [cdnLibraryUrl]
}
/>
</div>
<div className={`flex gap-2.5 ${largeSketch ? "flex-row" : "md:flex-row lg:flex-col"}`}>
<div
class={`flex gap-2.5 ${largeSketch ? "flex-row" : "md:flex-row lg:flex-col"}`}
>
<CircleButton
className="bg-bg-gray-40"
onClick={updateOrReRun}
Expand All @@ -129,7 +140,7 @@ export const CodeEmbed = (props) => {
</div>
</div>
) : null}
<div className="code-editor-container relative w-full">
<div class="code-editor-container relative w-full">
<CodeMirror
value={codeString}
theme="light"
Expand All @@ -155,7 +166,7 @@ export const CodeEmbed = (props) => {
(editorView.contentDOM.ariaLabel = "Code Editor")
}
/>
<div className="absolute right-0 top-0 flex flex-col gap-xs p-xs md:flex-row">
<div class="absolute right-0 top-0 flex flex-col gap-xs p-xs md:flex-row">
<CopyCodeButton textToCopy={codeString || initialCode} />
<CircleButton
onClick={() => {
Expand Down
43 changes: 17 additions & 26 deletions src/components/CopyCodeButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'preact/hooks';
import { useLiveRegion } from '../hooks/useLiveRegion';
import { useState } from "preact/hooks";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To make the component more robust when handling the timeout for resetting the copy state, useEffect should be used. Please import it.

Suggested change
import { useState } from "preact/hooks";
import { useState, useEffect } from "preact/hooks";

import { useLiveRegion } from "../hooks/useLiveRegion";
import CircleButton from "../CircleButton";

interface CopyCodeButtonProps {
Expand All @@ -9,56 +9,47 @@ interface CopyCodeButtonProps {

export const CopyCodeButton = ({
textToCopy,
announceOnCopy = 'Code copied to clipboard'
announceOnCopy = "Code copied to clipboard",
}: CopyCodeButtonProps) => {
const [isCopied, setIsCopied] = useState(false);

const { ref: liveRegionRef, announce } = useLiveRegion<HTMLSpanElement>();

const copyTextToClipboard = async () => {
console.log('Copy button clicked');
console.log('Text to copy:', textToCopy);

try {
console.log('Using Clipboard API');
await navigator.clipboard.writeText(textToCopy);
console.log('Text copied successfully');

announce(announceOnCopy);

setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
console.log('Copy state reset');
}, 2000);
} catch (err) {
console.error('Clipboard API copy failed:', err);
console.error("Clipboard API copy failed:", err);
}
};
Comment on lines 18 to 31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using setTimeout directly within an event handler can lead to issues if the component unmounts before the timeout completes. This can cause a state update on an unmounted component, which is a memory leak and will trigger a warning in development.

A more robust approach is to use the useEffect hook to handle the side effect of resetting the isCopied state. This ensures that the timeout is properly cleaned up if the component unmounts.

After importing useEffect, you can add this hook to your component:

useEffect(() => {
  if (!isCopied) return;

  const timer = setTimeout(() => {
    setIsCopied(false);
  }, 2000);

  return () => clearTimeout(timer);
}, [isCopied]);

And this function should only be responsible for setting isCopied to true.

  const copyTextToClipboard = async () => {
    try {
      await navigator.clipboard.writeText(textToCopy);

      announce(announceOnCopy);

      setIsCopied(true);
    } catch (err) {
      console.error("Clipboard API copy failed:", err);
    }
  };


return (
<>
<CircleButton
onClick={() => {
console.log('CircleButton clicked');
copyTextToClipboard();
}}
onClick={copyTextToClipboard}
ariaLabel="Copy code to clipboard"
className={`bg-white ${isCopied ? 'text-green-600' : 'text-black'} transition-colors duration-200`}
className={`bg-white ${isCopied ? "text-green-600" : "text-black"} transition-colors duration-200`}
>
{isCopied ? (
<svg
width="18"
height="22"
viewBox="0 0 24 24"
fill="none"
<svg
width="18"
height="22"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20 6L9 17L4 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
<path
d="M20 6L9 17L4 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Expand Down Expand Up @@ -89,4 +80,4 @@ export const CopyCodeButton = ({
<span ref={liveRegionRef} aria-live="polite" class="sr-only" />
</>
);
};
};
22 changes: 9 additions & 13 deletions src/components/Dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,37 +113,37 @@ export const Dropdown = ({
// Render the collapsed dropdown button
const renderCollapsedDropdown = () => (
<button
className={styles.selected}
class={styles.selected}
onClick={handleDropdownClick}
aria-haspopup="listbox"
aria-expanded={isOpen}
tabIndex={0}
>
<div className={styles.iconTop}>
<div class={styles.iconTop}>
<Icon kind={iconKind} />
</div>
<span>
{dropdownLabel ||
options.find((option) => isSelected(option))?.label ||
"Select..."}
</span>
<div className={styles.chevron}>
<div class={styles.chevron}>
<Icon kind="chevron-down" />
</div>
</button>
);

// Render the expanded dropdown options
const renderExpandedDropdown = () => (
<ul className={styles.options} role="listbox" tabIndex={-1}>
<ul class={styles.options} role="listbox" tabIndex={-1}>
{options.map((option, index) => (
<li
key={option.value}
className={styles.option}
class={styles.option}
role="option"
aria-selected={isSelected(option)}
>
<div className={styles.icon}>
<div class={styles.icon}>
<Icon
kind={
isSelected(option) ? "option-selected" : "option-unselected"
Expand All @@ -162,23 +162,19 @@ export const Dropdown = ({
</li>
))}
{variant === "radio" ? (
<button onClick={() => setIsOpen(false)} className={styles.chevron}>
<button onClick={() => setIsOpen(false)} class={styles.chevron}>
<Icon kind="chevron-up" />
</button>
) : (
<div className={styles.chevron}>
<div class={styles.chevron}>
<Icon kind="chevron-up" />
</div>
)}
</ul>
);

return (
<div
className={styles.container}
ref={dropdownRef}
onKeyDown={handleKeyDown}
>
<div class={styles.container} ref={dropdownRef} onKeyDown={handleKeyDown}>
{isOpen ? renderExpandedDropdown() : renderCollapsedDropdown()}
</div>
);
Expand Down
Loading
Loading