Component cleanup

This commit is contained in:
adilallo
2026-04-29 07:20:16 -06:00
parent 252848eba9
commit e6127f1a3f
267 changed files with 2087 additions and 2196 deletions
@@ -1,41 +0,0 @@
"use client";
import { forwardRef, memo } from "react";
interface ContextMenuProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
children?: React.ReactNode;
}
const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>(
({ className = "", children, ...props }, ref) => {
const menuClasses = `
bg-black
border border-[var(--color-border-default-tertiary)]
rounded-[var(--measures-radius-medium)]
shadow-lg
p-[4px]
min-w-[200px]
max-w-[300px]
${className}
`
.trim()
.replace(/\s+/g, " ");
return (
<div
ref={ref}
className={menuClasses}
role="menu"
style={{ backgroundColor: "#000000" }}
{...props}
>
{children}
</div>
);
},
);
ContextMenu.displayName = "ContextMenu";
export default memo(ContextMenu);
@@ -1,27 +0,0 @@
"use client";
import { forwardRef, memo } from "react";
interface ContextMenuDividerProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
}
const ContextMenuDivider = forwardRef<HTMLDivElement, ContextMenuDividerProps>(
({ className = "", ...props }, ref) => {
const dividerClasses = `
border-t border-[var(--color-border-default-tertiary)]
my-1
${className}
`
.trim()
.replace(/\s+/g, " ");
return (
<div ref={ref} className={dividerClasses} role="separator" {...props} />
);
},
);
ContextMenuDivider.displayName = "ContextMenuDivider";
export default memo(ContextMenuDivider);
@@ -1,36 +0,0 @@
"use client";
import { forwardRef, memo } from "react";
interface ContextMenuSectionProps extends React.HTMLAttributes<HTMLDivElement> {
title?: string;
children?: React.ReactNode;
className?: string;
}
const ContextMenuSection = forwardRef<HTMLDivElement, ContextMenuSectionProps>(
({ title, children, className = "", ...props }, ref) => {
const sectionClasses = `
${className}
`
.trim()
.replace(/\s+/g, " ");
return (
<div ref={ref} className={sectionClasses} role="group" {...props}>
{title && (
<div className="px-3 py-2">
<div className="text-[var(--color-content-default-primary)] text-sm font-medium">
{title}
</div>
</div>
)}
{children}
</div>
);
},
);
ContextMenuSection.displayName = "ContextMenuSection";
export default memo(ContextMenuSection);
@@ -1,101 +0,0 @@
"use client";
import { forwardRef, memo, useCallback } from "react";
import { ContextMenuItemView } from "./ContextMenuItem.view";
import type { ContextMenuItemProps } from "./ContextMenuItem.types";
const ContextMenuItemContainer = forwardRef<
HTMLDivElement,
ContextMenuItemProps
>(
(
{
children,
selected = false,
hasSubmenu = false,
disabled = false,
className = "",
onClick,
size: sizeProp = "medium",
...props
},
ref,
) => {
const size = sizeProp;
const getTextSize = (): string => {
switch (size) {
case "small":
return "text-[10px] leading-[14px]";
case "medium":
return "text-[14px] leading-[20px]";
case "large":
return "text-[16px] leading-[24px]";
default:
return "text-[14px] leading-[20px]";
}
};
const itemClasses = `
flex items-center justify-between
px-[8px] py-[4px]
text-[var(--color-content-default-brand-primary)]
${getTextSize()}
cursor-pointer
transition-colors duration-150
${
selected
? "bg-[var(--color-surface-default-secondary)] rounded-[var(--measures-radius-small)]"
: ""
}
${
disabled
? "opacity-50 cursor-not-allowed"
: "hover:!bg-[var(--color-surface-default-secondary)] hover:!rounded-[var(--measures-radius-small)]"
}
${className}
`
.trim()
.replace(/\s+/g, " ");
const handleClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
if (!disabled && onClick) {
onClick(e);
}
},
[disabled, onClick],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
if (!disabled && onClick) {
onClick(e);
}
}
},
[disabled, onClick],
);
return (
<ContextMenuItemView
ref={ref}
selected={selected}
hasSubmenu={hasSubmenu}
disabled={disabled}
className={className}
itemClasses={itemClasses}
handleClick={handleClick}
handleKeyDown={handleKeyDown}
{...props}
>
{children}
</ContextMenuItemView>
);
},
);
ContextMenuItemContainer.displayName = "ContextMenuItem";
export default memo(ContextMenuItemContainer);
@@ -1,27 +0,0 @@
export type ContextMenuItemSizeValue = "small" | "medium" | "large";
export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
selected?: boolean;
hasSubmenu?: boolean;
disabled?: boolean;
className?: string;
onClick?: (
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
) => void;
/**
* Context menu item size.
*/
size?: ContextMenuItemSizeValue;
}
export interface ContextMenuItemViewProps {
children?: React.ReactNode;
selected: boolean;
hasSubmenu: boolean;
disabled: boolean;
className: string;
itemClasses: string;
handleClick: (_e: React.MouseEvent<HTMLDivElement>) => void;
handleKeyDown: (_e: React.KeyboardEvent<HTMLDivElement>) => void;
}
@@ -1,71 +0,0 @@
import { forwardRef } from "react";
import type { ContextMenuItemViewProps } from "./ContextMenuItem.types";
export const ContextMenuItemView = forwardRef<
HTMLDivElement,
ContextMenuItemViewProps
>(
(
{
children,
selected,
hasSubmenu,
disabled,
itemClasses,
handleClick,
handleKeyDown,
...props
},
ref,
) => {
return (
<div
ref={ref}
className={itemClasses}
role="menuitem"
tabIndex={disabled ? -1 : 0}
aria-current={selected ? "true" : undefined}
aria-disabled={disabled}
onClick={handleClick}
onKeyDown={handleKeyDown}
{...props}
>
<div className="flex items-center gap-[8px]">
{selected && (
<svg
className="w-4 h-4 text-[var(--color-content-default-brand-primary)]"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
)}
<span>{children}</span>
</div>
{hasSubmenu && (
<svg
className="w-4 h-4 text-[var(--color-content-default-brand-primary)]"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
)}
</div>
);
},
);
ContextMenuItemView.displayName = "ContextMenuItemView";
@@ -1,2 +0,0 @@
export { default } from "./ContextMenuItem.container";
export type { ContextMenuItemProps } from "./ContextMenuItem.types";
+2 -2
View File
@@ -1,8 +1,8 @@
"use client";
import ContentLockup from "../../type/ContentLockup";
import ModalFooter from "../../utility/ModalFooter";
import ModalHeader from "../../utility/ModalHeader";
import ModalFooter from "../ModalFooter";
import ModalHeader from "../ModalHeader";
import { CreateModalFrameView } from "./CreateModalFrame.view";
import type { CreateViewProps } from "./Create.types";
@@ -78,5 +78,5 @@ export function useCreateModalA11y(
document.removeEventListener("keydown", handleTab);
previousActiveElementRef.current?.focus();
};
}, [isOpen]);
}, [dialogRef, isOpen]);
}
@@ -32,7 +32,6 @@ const DialogContainer = memo<DialogProps>(
title={title}
description={description}
footer={footer}
children={children}
className={className}
ariaLabel={ariaLabel}
ariaLabelledBy={titleId}
@@ -40,7 +39,9 @@ const DialogContainer = memo<DialogProps>(
backdropVariant={backdropVariant}
overlayRef={overlayRef}
dialogRef={dialogRef}
/>
>
{children}
</DialogView>
);
},
);
+2 -2
View File
@@ -2,8 +2,8 @@
import { memo } from "react";
import ContentLockup from "../../type/ContentLockup";
import ModalFooter from "../../utility/ModalFooter";
import ModalHeader from "../../utility/ModalHeader";
import ModalFooter from "../ModalFooter";
import ModalHeader from "../ModalHeader";
import { CreateModalFrameView } from "../Create/CreateModalFrame.view";
import type { DialogViewProps } from "./Dialog.types";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { createPortal } from "react-dom";
import ModalHeader from "../../utility/ModalHeader";
import ModalHeader from "../ModalHeader";
import type { LoginBackdropVariant, LoginViewProps } from "./Login.types";
const backdropClasses: Record<LoginBackdropVariant, string> = {
@@ -0,0 +1,18 @@
"use client";
import { memo } from "react";
import { ModalFooterView } from "./ModalFooter.view";
import type { ModalFooterProps } from "./ModalFooter.types";
/**
* Figma: "Utility / ModalFooter". Lives under `modals/` with other composed modal chrome.
* Sticky modal footer slot used by the create-flow + login modals to host
* primary/secondary actions.
*/
const ModalFooterContainer = memo<ModalFooterProps>((props) => {
return <ModalFooterView {...props} />;
});
ModalFooterContainer.displayName = "ModalFooter";
export default ModalFooterContainer;
@@ -0,0 +1,25 @@
export interface ModalFooterProps {
showBackButton?: boolean;
showNextButton?: boolean;
onBack?: () => void;
onNext?: () => void;
/**
* Custom back button text. If not provided, uses localized "Back" from common.json
*/
backButtonText?: string;
/**
* Custom next button text. If not provided, uses localized "Next" from common.json
*/
nextButtonText?: string;
nextButtonDisabled?: boolean;
currentStep?: number;
totalSteps?: number;
/**
* Whether to show the stepper component in the footer (Figma prop).
* Defaults to true if currentStep and totalSteps are provided.
* @default true
*/
stepper?: boolean;
footerContent?: React.ReactNode;
className?: string;
}
@@ -0,0 +1,79 @@
"use client";
import { useTranslation } from "../../../contexts/MessagesContext";
import Button from "../../buttons/Button";
import Stepper from "../../progress/Stepper";
import type { ModalFooterProps } from "./ModalFooter.types";
export function ModalFooterView({
showBackButton = false,
showNextButton = false,
onBack,
onNext,
backButtonText,
nextButtonText,
nextButtonDisabled = false,
currentStep,
totalSteps,
stepper: stepperProp,
footerContent,
className = "",
}: ModalFooterProps) {
const t = useTranslation("common");
// Use localized defaults if text not provided
const defaultBackText = backButtonText || t("buttons.back");
const defaultNextText = nextButtonText || t("buttons.next");
// Determine if stepper should be shown
// Defaults to true if currentStep and totalSteps are provided, unless explicitly set to false
const shouldShowStepper =
stepperProp !== undefined
? stepperProp
: currentStep !== undefined && totalSteps !== undefined;
return (
<div
className={`h-[64px] bg-[var(--color-surface-default-primary)] rounded-bl-[var(--radius-300,12px)] rounded-br-[var(--radius-300,12px)] shrink-0 relative ${className}`}
>
{/* Back Button - Absolutely positioned bottom left */}
{showBackButton && (
<div className="absolute left-[16px] top-[12px]">
<Button
buttonType="outline"
palette="default"
size="medium"
onClick={onBack}
>
{defaultBackText}
</Button>
</div>
)}
{/* Stepper (Centered) */}
{shouldShowStepper && currentStep && totalSteps && (
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<Stepper active={currentStep} totalSteps={totalSteps} />
</div>
)}
{/* Next Button - Absolutely positioned bottom right */}
{showNextButton && (
<div className="absolute right-[16px] top-[12px]">
<Button
buttonType="filled"
palette="default"
size="medium"
onClick={onNext}
disabled={nextButtonDisabled}
>
{defaultNextText}
</Button>
</div>
)}
{/* Custom Footer Content */}
{footerContent}
</div>
);
}
@@ -0,0 +1,2 @@
export { default } from "./ModalFooter.container";
export type { ModalFooterProps } from "./ModalFooter.types";
@@ -0,0 +1,18 @@
"use client";
import { memo } from "react";
import { ModalHeaderView } from "./ModalHeader.view";
import type { ModalHeaderProps } from "./ModalHeader.types";
/**
* Figma: "Utility / ModalHeader". Lives under `modals/` with other composed modal chrome.
* Sticky 48px modal header with optional close (left) and more-options
* (right) icon buttons.
*/
const ModalHeaderContainer = memo<ModalHeaderProps>((props) => {
return <ModalHeaderView {...props} />;
});
ModalHeaderContainer.displayName = "ModalHeader";
export default ModalHeaderContainer;
@@ -0,0 +1,7 @@
export interface ModalHeaderProps {
onClose?: () => void;
onMoreOptions?: () => void;
showCloseButton?: boolean;
showMoreOptionsButton?: boolean;
className?: string;
}
@@ -0,0 +1,61 @@
import { getAssetPath } from "../../../../lib/assetUtils";
import type { ModalHeaderProps } from "./ModalHeader.types";
const iconButtonClass =
"absolute bg-[var(--color-surface-default-secondary)] h-[24px] w-[24px] rounded-full flex items-center justify-center cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-default-primary)]";
export function ModalHeaderView({
onClose,
onMoreOptions,
showCloseButton = true,
showMoreOptionsButton = true,
className = "",
}: ModalHeaderProps) {
return (
<div
className={`border-b border-[var(--color-border-default-secondary)] h-[48px] shrink-0 sticky top-0 bg-[var(--color-surface-default-primary)] z-[2] ${className}`}
>
{/* Close Button - Left */}
{showCloseButton && (
<button
type="button"
onClick={onClose}
className={`${iconButtonClass} left-[24px] top-[12px]`}
aria-label="Close dialog"
>
{/* eslint-disable-next-line @next/next/no-img-element -- icon asset */}
<img
src={getAssetPath("assets/Icon_Close.svg")}
alt=""
className="w-[16px] h-[16px]"
style={{
filter: "brightness(0) invert(1)",
}}
/>
</button>
)}
{/* More Options Button - Right */}
{showMoreOptionsButton && (
<button
type="button"
onClick={onMoreOptions}
className={`${iconButtonClass} right-[24px] top-[12px]`}
aria-label="More options"
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="4" cy="8" r="1.5" fill="white" />
<circle cx="8" cy="8" r="1.5" fill="white" />
<circle cx="12" cy="8" r="1.5" fill="white" />
</svg>
</button>
)}
</div>
);
}
@@ -0,0 +1,2 @@
export { default } from "./ModalHeader.container";
export type { ModalHeaderProps } from "./ModalHeader.types";