From 2e1538770c592cb6dec6245fa63a0f28868dfc20 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Sun, 8 Feb 2026 22:04:36 -0700 Subject: [PATCH] Informational and text templates --- .../TextInput/TextInput.container.tsx | 25 ++-- .../controls/TextInput/TextInput.types.ts | 13 +- .../controls/TextInput/TextInput.view.tsx | 2 +- .../HeaderLockup/HeaderLockup.container.tsx | 35 ++++++ .../type/HeaderLockup/HeaderLockup.types.ts | 30 +++++ .../type/HeaderLockup/HeaderLockup.view.tsx | 56 +++++++++ app/components/type/HeaderLockup/index.tsx | 1 + .../NumberedList/NumberedList.container.tsx | 19 +++ .../type/NumberedList/NumberedList.types.ts | 23 ++++ .../type/NumberedList/NumberedList.view.tsx | 67 +++++++++++ app/components/type/NumberedList/index.tsx | 1 + .../CreateFlowFooter.container.tsx | 3 +- .../CreateFlowFooter.types.ts | 4 + .../CreateFlowFooter.view.tsx | 5 +- app/create/informational/page.tsx | 50 ++++++++ app/create/layout.tsx | 103 +++++++++++++--- app/create/text/page.tsx | 47 ++++++++ lib/propNormalization.ts | 60 ++++++++++ stories/controls/TextInput.stories.js | 113 ++++-------------- stories/type/HeaderLockup.stories.js | 60 ++++++++++ stories/type/NumberedList.stories.js | 63 ++++++++++ tests/components/HeaderLockup.test.tsx | 83 +++++++++++++ tests/components/NumberedList.test.tsx | 84 +++++++++++++ tests/components/TextInput.test.tsx | 26 ++++ 24 files changed, 852 insertions(+), 121 deletions(-) create mode 100644 app/components/type/HeaderLockup/HeaderLockup.container.tsx create mode 100644 app/components/type/HeaderLockup/HeaderLockup.types.ts create mode 100644 app/components/type/HeaderLockup/HeaderLockup.view.tsx create mode 100644 app/components/type/HeaderLockup/index.tsx create mode 100644 app/components/type/NumberedList/NumberedList.container.tsx create mode 100644 app/components/type/NumberedList/NumberedList.types.ts create mode 100644 app/components/type/NumberedList/NumberedList.view.tsx create mode 100644 app/components/type/NumberedList/index.tsx create mode 100644 app/create/informational/page.tsx create mode 100644 app/create/text/page.tsx create mode 100644 stories/type/HeaderLockup.stories.js create mode 100644 stories/type/NumberedList.stories.js create mode 100644 tests/components/HeaderLockup.test.tsx create mode 100644 tests/components/NumberedList.test.tsx diff --git a/app/components/controls/TextInput/TextInput.container.tsx b/app/components/controls/TextInput/TextInput.container.tsx index 45294d4..62a064e 100644 --- a/app/components/controls/TextInput/TextInput.container.tsx +++ b/app/components/controls/TextInput/TextInput.container.tsx @@ -4,12 +4,13 @@ import { memo, forwardRef, useState, useRef } from "react"; import { useComponentId, useFormField } from "../../../hooks"; import { TextInputView } from "./TextInput.view"; import type { TextInputProps } from "./TextInput.types"; -import { normalizeInputState } from "../../../../lib/propNormalization"; +import { normalizeInputState, normalizeTextInputSize } from "../../../../lib/propNormalization"; const TextInputContainer = forwardRef( ( { state: externalStateProp = "default", + inputSize: inputSizeProp = "medium", disabled = false, error = false, label, @@ -31,6 +32,7 @@ const TextInputContainer = forwardRef( ) => { // Normalize props to handle both PascalCase (Figma) and lowercase (codebase) const externalState = normalizeInputState(externalStateProp); + const inputSize = normalizeTextInputSize(inputSizeProp); // Generate unique ID for accessibility if not provided const { id: inputId, labelId } = useComponentId("text-input", id); @@ -59,13 +61,20 @@ const TextInputContainer = forwardRef( // Determine if input is filled (has value) const isFilled = Boolean(value && value.trim().length > 0); - // Fixed size styles (medium only per Figma designs) - const sizeStyles = { - input: "h-[40px] px-[12px] py-[8px] text-[16px]", - label: "text-[14px] leading-[20px] font-medium", - container: "gap-[8px]", - radius: "var(--measures-radius-200,8px)", - }; + // Size styles based on inputSize prop + const sizeStyles = inputSize === "small" + ? { + input: "h-[32px] px-[10px] py-[6px] text-[14px]", + label: "text-[12px] leading-[16px] font-medium", + container: "gap-[6px]", + radius: "var(--measures-radius-200,8px)", + } + : { + input: "h-[40px] px-[12px] py-[8px] text-[16px]", + label: "text-[14px] leading-[20px] font-medium", + container: "gap-[8px]", + radius: "var(--measures-radius-200,8px)", + }; // State styles based on Figma designs const getStateStyles = (): { diff --git a/app/components/controls/TextInput/TextInput.types.ts b/app/components/controls/TextInput/TextInput.types.ts index a09d4ef..b98e653 100644 --- a/app/components/controls/TextInput/TextInput.types.ts +++ b/app/components/controls/TextInput/TextInput.types.ts @@ -1,5 +1,7 @@ import type { InputStateValue } from "../../../../lib/propNormalization"; +export type TextInputSizeValue = "small" | "medium" | "Small" | "Medium"; + export interface TextInputProps extends Omit< React.InputHTMLAttributes, "size" | "onChange" | "onFocus" | "onBlur" @@ -9,6 +11,12 @@ export interface TextInputProps extends Omit< * Figma uses PascalCase, codebase uses lowercase - both are supported. */ state?: InputStateValue; + /** + * Size variant. Accepts both PascalCase (Figma) and lowercase (codebase). + * Figma uses PascalCase, codebase uses lowercase - both are supported. + * @default "medium" + */ + inputSize?: TextInputSizeValue; disabled?: boolean; error?: boolean; label?: string; @@ -21,9 +29,10 @@ export interface TextInputProps extends Omit< showHelpIcon?: boolean; /** * Whether to show hint text below input (Figma prop). + * Can be a boolean or a string to display custom text (e.g., character count). * @default false */ - textHint?: boolean; + textHint?: boolean | string; /** * Whether to show form header (label and help icon) above input (Figma prop). * @default true @@ -55,6 +64,6 @@ export interface TextInputViewProps { isFilled?: boolean; inputWrapperClasses?: string; focusRingClasses?: string; - textHint?: boolean; + textHint?: boolean | string; formHeader?: boolean; } diff --git a/app/components/controls/TextInput/TextInput.view.tsx b/app/components/controls/TextInput/TextInput.view.tsx index 661e0b9..65c0317 100644 --- a/app/components/controls/TextInput/TextInput.view.tsx +++ b/app/components/controls/TextInput/TextInput.view.tsx @@ -80,7 +80,7 @@ export const TextInputView = forwardRef( {textHint && (

- Hint text here + {typeof textHint === "string" ? textHint : "Hint text here"}

)} diff --git a/app/components/type/HeaderLockup/HeaderLockup.container.tsx b/app/components/type/HeaderLockup/HeaderLockup.container.tsx new file mode 100644 index 0000000..23a8183 --- /dev/null +++ b/app/components/type/HeaderLockup/HeaderLockup.container.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { memo } from "react"; +import HeaderLockupView from "./HeaderLockup.view"; +import type { HeaderLockupProps } from "./HeaderLockup.types"; +import { + normalizeHeaderLockupJustification, + normalizeHeaderLockupSize, +} from "../../../../lib/propNormalization"; + +const HeaderLockupContainer = memo( + ({ + title, + description, + justification: justificationProp = "left", + size: sizeProp = "L", + }) => { + // Normalize props to handle both PascalCase (Figma) and lowercase (codebase) + const justification = normalizeHeaderLockupJustification(justificationProp); + const size = normalizeHeaderLockupSize(sizeProp); + + return ( + + ); + }, +); + +HeaderLockupContainer.displayName = "HeaderLockup"; + +export default HeaderLockupContainer; diff --git a/app/components/type/HeaderLockup/HeaderLockup.types.ts b/app/components/type/HeaderLockup/HeaderLockup.types.ts new file mode 100644 index 0000000..556d8f4 --- /dev/null +++ b/app/components/type/HeaderLockup/HeaderLockup.types.ts @@ -0,0 +1,30 @@ +export type HeaderLockupJustificationValue = "left" | "center" | "Left" | "Center"; +export type HeaderLockupSizeValue = "L" | "M" | "l" | "m"; + +export interface HeaderLockupProps { + /** + * Title text (required) + */ + title: string; + /** + * Description text (optional) + */ + description?: string; + /** + * Text justification. Accepts both PascalCase (Figma) and lowercase (codebase). + * Figma uses PascalCase, codebase uses lowercase - both are supported. + */ + justification?: HeaderLockupJustificationValue; + /** + * Size variant. Accepts both PascalCase (Figma) and lowercase (codebase). + * Figma uses PascalCase, codebase uses lowercase - both are supported. + */ + size?: HeaderLockupSizeValue; +} + +export interface HeaderLockupViewProps { + title: string; + description?: string; + justification: "left" | "center"; + size: "L" | "M"; +} diff --git a/app/components/type/HeaderLockup/HeaderLockup.view.tsx b/app/components/type/HeaderLockup/HeaderLockup.view.tsx new file mode 100644 index 0000000..581507f --- /dev/null +++ b/app/components/type/HeaderLockup/HeaderLockup.view.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { memo } from "react"; +import type { HeaderLockupViewProps } from "./HeaderLockup.types"; + +function HeaderLockupView({ + title, + description, + justification, + size, +}: HeaderLockupViewProps) { + const isL = size === "L"; + const isLeft = justification === "left"; + + return ( +
+ {/* Title */} +
+

+ {title} +

+
+ + {/* Description */} + {description && ( +

+ {description} +

+ )} +
+ ); +} + +HeaderLockupView.displayName = "HeaderLockupView"; + +export default memo(HeaderLockupView); diff --git a/app/components/type/HeaderLockup/index.tsx b/app/components/type/HeaderLockup/index.tsx new file mode 100644 index 0000000..db2d3dd --- /dev/null +++ b/app/components/type/HeaderLockup/index.tsx @@ -0,0 +1 @@ +export { default } from "./HeaderLockup.container"; diff --git a/app/components/type/NumberedList/NumberedList.container.tsx b/app/components/type/NumberedList/NumberedList.container.tsx new file mode 100644 index 0000000..e6a652d --- /dev/null +++ b/app/components/type/NumberedList/NumberedList.container.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { memo } from "react"; +import NumberedListView from "./NumberedList.view"; +import type { NumberedListProps } from "./NumberedList.types"; +import { normalizeNumberedListSize } from "../../../../lib/propNormalization"; + +const NumberedListContainer = memo( + ({ items, size: sizeProp = "M" }) => { + // Normalize props to handle both PascalCase (Figma) and lowercase (codebase) + const size = normalizeNumberedListSize(sizeProp); + + return ; + }, +); + +NumberedListContainer.displayName = "NumberedList"; + +export default NumberedListContainer; diff --git a/app/components/type/NumberedList/NumberedList.types.ts b/app/components/type/NumberedList/NumberedList.types.ts new file mode 100644 index 0000000..a65233c --- /dev/null +++ b/app/components/type/NumberedList/NumberedList.types.ts @@ -0,0 +1,23 @@ +export type NumberedListSizeValue = "M" | "S" | "m" | "s"; + +export interface NumberedListItem { + title: string; + description: string; +} + +export interface NumberedListProps { + /** + * Array of list items, each with title and description + */ + items: NumberedListItem[]; + /** + * Size variant. Accepts both PascalCase (Figma) and lowercase (codebase). + * Figma uses PascalCase, codebase uses lowercase - both are supported. + */ + size?: NumberedListSizeValue; +} + +export interface NumberedListViewProps { + items: NumberedListItem[]; + size: "M" | "S"; +} diff --git a/app/components/type/NumberedList/NumberedList.view.tsx b/app/components/type/NumberedList/NumberedList.view.tsx new file mode 100644 index 0000000..0f532fd --- /dev/null +++ b/app/components/type/NumberedList/NumberedList.view.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { memo } from "react"; +import type { NumberedListViewProps } from "./NumberedList.types"; + +function NumberedListView({ items, size }: NumberedListViewProps) { + const isM = size === "M"; + + return ( +
    + {items.map((item, index) => ( +
  1. + {/* Number Indicator */} +
    +
    + {index + 1} +
    +
    + + {/* Content */} +
    + {/* Title */} +
    +

    + {item.title} +

    +
    + + {/* Description */} +

    + {item.description} +

    +
    +
  2. + ))} +
+ ); +} + +NumberedListView.displayName = "NumberedListView"; + +export default memo(NumberedListView); diff --git a/app/components/type/NumberedList/index.tsx b/app/components/type/NumberedList/index.tsx new file mode 100644 index 0000000..fc06cf6 --- /dev/null +++ b/app/components/type/NumberedList/index.tsx @@ -0,0 +1 @@ +export { default } from "./NumberedList.container"; diff --git a/app/components/utility/CreateFlowFooter/CreateFlowFooter.container.tsx b/app/components/utility/CreateFlowFooter/CreateFlowFooter.container.tsx index 1212196..4b964fd 100644 --- a/app/components/utility/CreateFlowFooter/CreateFlowFooter.container.tsx +++ b/app/components/utility/CreateFlowFooter/CreateFlowFooter.container.tsx @@ -5,11 +5,12 @@ import { CreateFlowFooterView } from "./CreateFlowFooter.view"; import type { CreateFlowFooterProps } from "./CreateFlowFooter.types"; const CreateFlowFooterContainer = memo( - ({ secondButton, progressBar = true, className = "" }) => { + ({ secondButton, progressBar = true, onBackClick, className = "" }) => { return ( ); diff --git a/app/components/utility/CreateFlowFooter/CreateFlowFooter.types.ts b/app/components/utility/CreateFlowFooter/CreateFlowFooter.types.ts index 4bf5d0d..0ca62bb 100644 --- a/app/components/utility/CreateFlowFooter/CreateFlowFooter.types.ts +++ b/app/components/utility/CreateFlowFooter/CreateFlowFooter.types.ts @@ -13,6 +13,10 @@ export interface CreateFlowFooterProps { * @default true */ progressBar?: boolean; + /** + * Callback function for Back button click + */ + onBackClick?: () => void; /** * Additional CSS classes */ diff --git a/app/components/utility/CreateFlowFooter/CreateFlowFooter.view.tsx b/app/components/utility/CreateFlowFooter/CreateFlowFooter.view.tsx index 4b9feef..34bc736 100644 --- a/app/components/utility/CreateFlowFooter/CreateFlowFooter.view.tsx +++ b/app/components/utility/CreateFlowFooter/CreateFlowFooter.view.tsx @@ -5,11 +5,12 @@ import type { CreateFlowFooterProps } from "./CreateFlowFooter.types"; export function CreateFlowFooterView({ secondButton, progressBar = true, + onBackClick, className = "", }: CreateFlowFooterProps) { return (