App reorganization
This commit is contained in:
+2
-16
@@ -1,30 +1,16 @@
|
||||
import { memo } from "react";
|
||||
import { normalizeSize } from "../../../lib/propNormalization";
|
||||
|
||||
export type AvatarContainerSizeValue =
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "xlarge"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large"
|
||||
| "XLarge";
|
||||
export type AvatarContainerSizeValue = "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 size = sizeProp;
|
||||
const sizeStyles: Record<string, string> = {
|
||||
small: "flex -space-x-[var(--spacing-scale-008)]",
|
||||
medium: "flex -space-x-[9px]",
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./AvatarContainer";
|
||||
export type { AvatarContainerSizeValue } from "./AvatarContainer";
|
||||
@@ -7,6 +7,10 @@ import type { CardStackProps } from "./CardStack.types";
|
||||
const DEFAULT_TOGGLE_LABEL = "See all communication approaches";
|
||||
const DEFAULT_SHOW_LESS_LABEL = "Show less";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / CardStack" (TODO(figma)). Selectable stack of cards with
|
||||
* an optional "see all"/"show less" expand toggle.
|
||||
*/
|
||||
const CardStackContainer = memo<CardStackProps>(
|
||||
({
|
||||
cards,
|
||||
|
||||
@@ -4,6 +4,10 @@ import { memo } from "react";
|
||||
import { CreateFlowFooterView } from "./CreateFlowFooter.view";
|
||||
import type { CreateFlowFooterProps } from "./CreateFlowFooter.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / CreateFlowFooter" (TODO(figma)). Sticky footer for the
|
||||
* create flow with a back action, optional secondary button, and progress bar.
|
||||
*/
|
||||
const CreateFlowFooterContainer = memo<CreateFlowFooterProps>(
|
||||
({
|
||||
secondButton,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { normalizeProportionBarVariant } from "../../../../lib/propNormalization";
|
||||
import ProportionBar from "../../progress/ProportionBar";
|
||||
import Button from "../../buttons/Button";
|
||||
import type { CreateFlowFooterProps } from "./CreateFlowFooter.types";
|
||||
@@ -11,9 +10,7 @@ export function CreateFlowFooterView({
|
||||
onBackClick,
|
||||
className = "",
|
||||
}: CreateFlowFooterProps) {
|
||||
const proportionBarVariant = normalizeProportionBarVariant(
|
||||
proportionBarVariantProp,
|
||||
);
|
||||
const proportionBarVariant = proportionBarVariantProp ?? "default";
|
||||
return (
|
||||
<footer
|
||||
className={`bg-black w-full ${className}`}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { useRouter } from "next/navigation";
|
||||
import { CreateFlowTopNavView } from "./CreateFlowTopNav.view";
|
||||
import type { CreateFlowTopNavProps } from "./CreateFlowTopNav.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / CreateFlowTopNav" (TODO(figma)). Top navigation bar for
|
||||
* the create flow with exit, share, export, and edit actions.
|
||||
*/
|
||||
const CreateFlowTopNavContainer = memo<CreateFlowTopNavProps>(
|
||||
({
|
||||
hasShare = false,
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { memo } from "react";
|
||||
import DecisionMakingSidebarView from "./DecisionMakingSidebar.view";
|
||||
import type { DecisionMakingSidebarProps } from "./DecisionMakingSidebar.types";
|
||||
import {
|
||||
normalizeHeaderLockupJustification,
|
||||
normalizeHeaderLockupSize,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / DecisionMakingSidebar" (TODO(figma)). Sidebar pairing a
|
||||
* header lockup with an `InfoMessageBox` checklist for decision-making screens.
|
||||
*/
|
||||
const DecisionMakingSidebarContainer = memo<DecisionMakingSidebarProps>(
|
||||
({
|
||||
title,
|
||||
@@ -20,8 +20,8 @@ const DecisionMakingSidebarContainer = memo<DecisionMakingSidebarProps>(
|
||||
justification: justificationProp = "left",
|
||||
className = "",
|
||||
}) => {
|
||||
const size = normalizeHeaderLockupSize(sizeProp);
|
||||
const justification = normalizeHeaderLockupJustification(justificationProp);
|
||||
const size = sizeProp;
|
||||
const justification = justificationProp;
|
||||
|
||||
return (
|
||||
<DecisionMakingSidebarView
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
"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'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;
|
||||
@@ -1,70 +0,0 @@
|
||||
"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;
|
||||
@@ -4,6 +4,10 @@ import { memo, useCallback, useState } from "react";
|
||||
import InfoMessageBoxView from "./InfoMessageBox.view";
|
||||
import type { InfoMessageBoxProps } from "./InfoMessageBox.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / InfoMessageBox" (TODO(figma)). Bordered message box that
|
||||
* lists checkbox items under a title with an optional leading icon.
|
||||
*/
|
||||
const InfoMessageBoxContainer = memo<InfoMessageBoxProps>(
|
||||
({
|
||||
title,
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
import { memo } from "react";
|
||||
import InputLabelView from "./InputLabel.view";
|
||||
import type { InputLabelProps } from "./InputLabel.types";
|
||||
import {
|
||||
normalizeInputLabelSize,
|
||||
normalizeInputLabelPalette,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / InputLabel" (TODO(figma)). Reusable form-input label with
|
||||
* optional asterisk, help icon, and helper text.
|
||||
*/
|
||||
const InputLabelContainer = memo<InputLabelProps>(
|
||||
({
|
||||
label,
|
||||
helpIcon = false,
|
||||
asterisk = false,
|
||||
helperText = false,
|
||||
size: sizeProp = "S",
|
||||
palette: paletteProp = "Default",
|
||||
size: sizeProp = "s",
|
||||
palette: paletteProp = "default",
|
||||
className = "",
|
||||
}) => {
|
||||
const size = normalizeInputLabelSize(sizeProp);
|
||||
const palette = normalizeInputLabelPalette(paletteProp);
|
||||
const size = sizeProp;
|
||||
const palette = paletteProp;
|
||||
|
||||
return (
|
||||
<InputLabelView
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export type InputLabelSizeValue = "S" | "M" | "s" | "m";
|
||||
export type InputLabelPaletteValue =
|
||||
| "Default"
|
||||
| "Inverse"
|
||||
| "default"
|
||||
| "inverse";
|
||||
export type InputLabelSizeValue = "s" | "m";
|
||||
export type InputLabelPaletteValue = "default" | "inverse";
|
||||
|
||||
export interface InputLabelProps {
|
||||
/**
|
||||
@@ -25,13 +21,11 @@ export interface InputLabelProps {
|
||||
*/
|
||||
helperText?: boolean | string;
|
||||
/**
|
||||
* Size variant: "S" (small) or "M" (medium)
|
||||
* Accepts both uppercase (Figma) and lowercase values.
|
||||
* Size variant: "s" (small) or "m" (medium)
|
||||
*/
|
||||
size?: InputLabelSizeValue;
|
||||
/**
|
||||
* Palette variant: "Default" or "Inverse"
|
||||
* Accepts both PascalCase (Figma) and lowercase values.
|
||||
* Palette variant: "default" or "inverse"
|
||||
*/
|
||||
palette?: InputLabelPaletteValue;
|
||||
className?: 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" (TODO(figma)).
|
||||
* 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;
|
||||
@@ -1,2 +1,2 @@
|
||||
export { ModalFooterView as default } from "./ModalFooter.view";
|
||||
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" (TODO(figma)).
|
||||
* 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;
|
||||
@@ -1,2 +1,2 @@
|
||||
export { ModalHeaderView as default } from "./ModalHeader.view";
|
||||
export { default } from "./ModalHeader.container";
|
||||
export type { ModalHeaderProps } from "./ModalHeader.types";
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { ScrollbarView } from "./Scrollbar.view";
|
||||
import type { ScrollbarProps } from "./Scrollbar.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / Scrollbar" (TODO(figma)).
|
||||
* Custom-styled scrollable wrapper. Most surfaces should attach
|
||||
* `SCROLLBAR_DESIGN_CLASS` directly instead of nesting through this view.
|
||||
*/
|
||||
const ScrollbarContainer = memo<ScrollbarProps>((props) => {
|
||||
return <ScrollbarView {...props} />;
|
||||
});
|
||||
|
||||
ScrollbarContainer.displayName = "Scrollbar";
|
||||
|
||||
export default ScrollbarContainer;
|
||||
@@ -1,4 +1,4 @@
|
||||
export { ScrollbarView as default } from "./Scrollbar.view";
|
||||
export { default } from "./Scrollbar.container";
|
||||
export type { ScrollbarProps } from "./Scrollbar.types";
|
||||
|
||||
/** Class name to apply the design system scrollbar to any scrollable element (e.g. textarea, div). */
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./Separator";
|
||||
@@ -9,6 +9,10 @@ const DEFAULT_LABELS: Record<TagProps["variant"], string> = {
|
||||
selected: "SELECTED",
|
||||
};
|
||||
|
||||
/**
|
||||
* Figma: "Utility / Tag" (TODO(figma)). Small status pill (e.g. "RECOMMENDED"
|
||||
* or "SELECTED") used to annotate cards and options.
|
||||
*/
|
||||
const TagContainer = memo<TagProps>(({ variant, children, className = "" }) => {
|
||||
const content = children ?? DEFAULT_LABELS[variant];
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user