Start organizational migration

This commit is contained in:
adilallo
2026-02-05 18:21:56 -07:00
parent 69074b23f3
commit db3c0274f6
161 changed files with 145 additions and 145 deletions
@@ -0,0 +1,39 @@
import { memo } from "react";
import { normalizeSize } from "../../../lib/propNormalization";
export type AvatarContainerSizeValue = "small" | "medium" | "large" | "xlarge" | "Small" | "Medium" | "Large" | "XLarge";
interface AvatarContainerProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
/**
* Avatar container size. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported.
*/
size?: AvatarContainerSizeValue;
className?: string;
}
const AvatarContainer = memo<AvatarContainerProps>(
({ children, size: sizeProp = "small", className = "", ...props }) => {
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
const size = normalizeSize(sizeProp, "small");
const sizeStyles: Record<string, string> = {
small: "flex -space-x-[var(--spacing-scale-008)]",
medium: "flex -space-x-[9px]",
large: "flex -space-x-[var(--spacing-scale-010)]",
xlarge: "flex -space-x-[13px]",
};
const baseStyles = `items-center ${sizeStyles[size]} ${className}`;
return (
<div className={baseStyles} {...props}>
{children}
</div>
);
},
);
AvatarContainer.displayName = "AvatarContainer";
export default AvatarContainer;
+58
View File
@@ -0,0 +1,58 @@
"use client";
import React, { Component, type ReactNode } from "react";
import { logger } from "../../../lib/logger";
interface ErrorBoundaryProps {
children: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
// Update state so the next render will show the fallback UI
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log the error to an error reporting service
logger.error("ErrorBoundary caught an error:", error, errorInfo);
}
render() {
if (this.state.hasError) {
// Fallback UI using design tokens
return (
<div className="min-h-[200px] flex items-center justify-center p-[var(--spacing-scale-016)]">
<div className="text-center">
<h2 className="text-xl font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-008)]">
Something went wrong
</h2>
<p className="text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-016)]">
We&apos;re sorry, but something unexpected happened.
</p>
<button
onClick={() => this.setState({ hasError: false, error: null })}
className="px-[var(--spacing-scale-016)] py-[var(--spacing-scale-008)] bg-[var(--color-surface-default-brand-royal)] text-[var(--color-content-inverse-primary)] rounded-[var(--radius-measures-radius-small)] hover:bg-[var(--color-surface-hover-brand-royal)] transition-colors"
>
Try again
</button>
</div>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
@@ -0,0 +1,70 @@
"use client";
import { memo } from "react";
import { normalizeImagePlaceholderColor } from "../../../lib/propNormalization";
export type ImagePlaceholderColorValue =
| "blue"
| "green"
| "purple"
| "red"
| "orange"
| "teal"
| "Blue"
| "Green"
| "Purple"
| "Red"
| "Orange"
| "Teal";
interface ImagePlaceholderProps {
width?: number;
height?: number;
text?: string;
/**
* Image placeholder color. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported.
*/
color?: ImagePlaceholderColorValue;
className?: string;
}
/**
* Simple image placeholder component for testing
* Generates colored backgrounds with text overlays
*/
const ImagePlaceholder = memo<ImagePlaceholderProps>(
({
width = 260,
height = 390,
text = "Blog Image",
color: colorProp = "blue",
className = "",
}) => {
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
const color = normalizeImagePlaceholderColor(colorProp);
const colors: Record<string, string> = {
blue: "bg-blue-500",
green: "bg-green-500",
purple: "bg-purple-500",
red: "bg-red-500",
orange: "bg-orange-500",
teal: "bg-teal-500",
};
const bgColor = colors[color] || colors.blue;
return (
<div
className={`${bgColor} flex items-center justify-center text-white font-bold text-lg ${className}`}
style={{ width: `${width}px`, height: `${height}px` }}
>
{text}
</div>
);
},
);
ImagePlaceholder.displayName = "ImagePlaceholder";
export default ImagePlaceholder;
@@ -0,0 +1,19 @@
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;
footerContent?: React.ReactNode;
className?: string;
}
@@ -0,0 +1,64 @@
"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,
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");
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 variant="outline" size="medium" onClick={onBack}>
{defaultBackText}
</Button>
</div>
)}
{/* Stepper (Centered) */}
{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
variant="filled"
size="medium"
onClick={onNext}
disabled={nextButtonDisabled}
>
{defaultNextText}
</Button>
</div>
)}
{/* Custom Footer Content */}
{footerContent}
</div>
);
}
@@ -0,0 +1,2 @@
export { ModalFooterView as default } from "./ModalFooter.view";
export type { ModalFooterProps } from "./ModalFooter.types";
@@ -0,0 +1,7 @@
export interface ModalHeaderProps {
onClose?: () => void;
onMoreOptions?: () => void;
showCloseButton?: boolean;
showMoreOptionsButton?: boolean;
className?: string;
}
@@ -0,0 +1,55 @@
import { getAssetPath } from "../../../../lib/assetUtils";
import type { ModalHeaderProps } from "./ModalHeader.types";
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
onClick={onClose}
className="absolute bg-[var(--color-surface-default-secondary)] h-[24px] w-[24px] rounded-full left-[24px] top-[12px] flex items-center justify-center cursor-pointer"
aria-label="Close dialog"
>
<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
onClick={onMoreOptions}
className="absolute bg-[var(--color-surface-default-secondary)] h-[24px] w-[24px] rounded-full right-[24px] top-[12px] flex items-center justify-center cursor-pointer"
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 { ModalHeaderView as default } from "./ModalHeader.view";
export type { ModalHeaderProps } from "./ModalHeader.types";
+13
View File
@@ -0,0 +1,13 @@
import { memo } from "react";
const Separator = memo(() => {
return (
<div className="flex flex-col items-center self-stretch">
<div className="flex items-start self-stretch h-px w-full bg-[var(--border-color-default-secondary)]" />
</div>
);
});
Separator.displayName = "Separator";
export default Separator;