Update text input component

This commit is contained in:
adilallo
2026-02-04 11:29:51 -07:00
parent d8fa525514
commit 255f16477c
20 changed files with 589 additions and 949 deletions
+152 -595
View File
@@ -1,29 +1,14 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import Tooltip from "../components/Tooltip"; import TextInput from "../components/TextInput";
import Alert from "../components/Alert"; import SelectInput from "../components/SelectInput";
import Button from "../components/Button";
import Stepper from "../components/Stepper";
import Progress from "../components/Progress";
import Create from "../components/Create";
import Input from "../components/Input";
import InputWithCounter from "../components/InputWithCounter";
import IconCard from "../components/IconCard";
import { getAssetPath } from "../../lib/assetUtils";
export default function ComponentsPreview() { export default function ComponentsPreview() {
const [alertVisible, setAlertVisible] = useState({ const [defaultInputValue, setDefaultInputValue] = useState("");
default: true, const [activeInputValue, setActiveInputValue] = useState("");
positive: true, const [errorInputValue, setErrorInputValue] = useState("");
warning: true, const [selectValue, setSelectValue] = useState("");
danger: true,
banner: true,
});
const [createOpen, setCreateOpen] = useState(false);
const [createStep, setCreateStep] = useState(1);
const [policyName, setPolicyName] = useState("");
return ( return (
<div className="min-h-screen bg-[var(--color-surface-default-primary)] p-[var(--spacing-scale-032)]"> <div className="min-h-screen bg-[var(--color-surface-default-primary)] p-[var(--spacing-scale-032)]">
@@ -37,600 +22,172 @@ export default function ComponentsPreview() {
</p> </p>
</header> </header>
{/* Button Section */} {/* Text Input Section */}
<section className="space-y-[var(--spacing-scale-024)]"> <section className="space-y-[var(--spacing-scale-024)]">
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]"> <h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
Button Component Text Input Component
</h2> </h2>
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]"> <div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]">
<div className="space-y-[var(--spacing-scale-016)]"> <div className="space-y-[var(--spacing-scale-016)]">
<div> <div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]"> <h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
All Variants States
</h3> </h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)]">
<Button variant="filled" size="medium">
Filled
</Button>
<Button variant="filled-inverse" size="medium">
Filled Inverse
</Button>
<Button variant="outline" size="medium">
Outline
</Button>
<Button variant="outline-inverse" size="medium">
Outline Inverse
</Button>
<Button variant="ghost" size="medium">
Ghost
</Button>
<Button variant="ghost-inverse" size="medium">
Ghost Inverse
</Button>
<Button variant="danger" size="medium">
Danger
</Button>
<Button variant="danger-inverse" size="medium">
Danger Inverse
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
All Sizes - Danger Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)] items-center">
<Button variant="danger" size="xsmall">
XSmall
</Button>
<Button variant="danger" size="small">
Small
</Button>
<Button variant="danger" size="medium">
Medium
</Button>
<Button variant="danger" size="large">
Large
</Button>
<Button variant="danger" size="xlarge">
XLarge
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
All Sizes - Danger Inverse Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)] items-center">
<Button variant="danger-inverse" size="xsmall">
XSmall
</Button>
<Button variant="danger-inverse" size="small">
Small
</Button>
<Button variant="danger-inverse" size="medium">
Medium
</Button>
<Button variant="danger-inverse" size="large">
Large
</Button>
<Button variant="danger-inverse" size="xlarge">
XLarge
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
All Sizes - Ghost Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)] items-center">
<Button variant="ghost" size="xsmall">
XSmall
</Button>
<Button variant="ghost" size="small">
Small
</Button>
<Button variant="ghost" size="medium">
Medium
</Button>
<Button variant="ghost" size="large">
Large
</Button>
<Button variant="ghost" size="xlarge">
XLarge
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
All Sizes - Ghost Inverse Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)] items-center">
<Button variant="ghost-inverse" size="xsmall">
XSmall
</Button>
<Button variant="ghost-inverse" size="small">
Small
</Button>
<Button variant="ghost-inverse" size="medium">
Medium
</Button>
<Button variant="ghost-inverse" size="large">
Large
</Button>
<Button variant="ghost-inverse" size="xlarge">
XLarge
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
States - Danger Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)]">
<Button variant="danger" size="medium">
Normal
</Button>
<Button variant="danger" size="medium" disabled>
Disabled
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
States - Danger Inverse Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)]">
<Button variant="danger-inverse" size="medium">
Normal
</Button>
<Button variant="danger-inverse" size="medium" disabled>
Disabled
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
States - Ghost Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)]">
<Button variant="ghost" size="medium">
Normal
</Button>
<Button variant="ghost" size="medium" disabled>
Disabled
</Button>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
States - Ghost Inverse Variant
</h3>
<div className="flex flex-wrap gap-[var(--spacing-scale-012)]">
<Button variant="ghost-inverse" size="medium">
Normal
</Button>
<Button variant="ghost-inverse" size="medium" disabled>
Disabled
</Button>
</div>
</div>
</div>
</div>
</section>
{/* Tooltip Section */}
<section className="space-y-[var(--spacing-scale-024)]">
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
Tooltip Component
</h2>
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]">
<div className="flex flex-wrap gap-[var(--spacing-scale-024)] items-center">
<Tooltip text="Tooltip positioned at top" position="top">
<Button variant="filled" size="medium">
Hover me (Top)
</Button>
</Tooltip>
<Tooltip text="Tooltip positioned at bottom" position="bottom">
<Button variant="filled-inverse" size="medium">
Hover me (Bottom)
</Button>
</Tooltip>
<Tooltip text="Disabled tooltip" disabled>
<Button variant="ghost" size="medium">
Disabled Tooltip
</Button>
</Tooltip>
<Tooltip text="Tooltip with icon button" position="top">
<button className="p-[var(--spacing-scale-012)] rounded-full hover:bg-[var(--color-surface-default-tertiary)] transition-colors">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 9V11M10 15H10.01M19 10C19 14.9706 14.9706 19 10 19C5.02944 19 1 14.9706 1 10C1 5.02944 5.02944 1 10 1C14.9706 1 19 5.02944 19 10Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</Tooltip>
</div>
</div>
</section>
{/* Alert Section */}
<section className="space-y-[var(--spacing-scale-024)]">
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
Alert Component
</h2>
<div className="space-y-[var(--spacing-scale-024)]">
{/* Toast Alerts */}
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-016)]">
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)]">
Toast Alerts
</h3>
{alertVisible.default && (
<Alert
title="Short alert banner message goes here"
description="Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse."
status="default"
type="toast"
onClose={() =>
setAlertVisible({ ...alertVisible, default: false })
}
/>
)}
{alertVisible.positive && (
<Alert
title="Short alert banner message goes here"
description="Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse."
status="positive"
type="toast"
onClose={() =>
setAlertVisible({ ...alertVisible, positive: false })
}
/>
)}
{alertVisible.warning && (
<Alert
title="Short alert banner message goes here"
description="Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse."
status="warning"
type="toast"
onClose={() =>
setAlertVisible({ ...alertVisible, warning: false })
}
/>
)}
{alertVisible.danger && (
<Alert
title="Short alert banner message goes here"
description="Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse."
status="danger"
type="toast"
onClose={() =>
setAlertVisible({ ...alertVisible, danger: false })
}
/>
)}
</div>
{/* Banner Alerts */}
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-016)]">
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)]">
Banner Alerts
</h3>
{alertVisible.banner && (
<Alert
title="Short alert banner message goes here"
description="Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse."
status="default"
type="banner"
onClose={() =>
setAlertVisible({ ...alertVisible, banner: false })
}
/>
)}
<Alert
title="Positive banner alert"
description="This is a positive banner message"
status="positive"
type="banner"
/>
<Alert
title="Warning banner alert"
description="This is a warning banner message"
status="warning"
type="banner"
/>
<Alert
title="Danger banner alert"
description="This is a danger banner message"
status="danger"
type="banner"
/>
</div>
</div>
</section>
{/* Stepper Section */}
<section className="space-y-[var(--spacing-scale-024)]">
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
Stepper Component
</h2>
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]">
<div className="space-y-[var(--spacing-scale-016)]">
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Step 1 of 5
</p>
<Stepper active={1} totalSteps={5} />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Step 2 of 5
</p>
<Stepper active={2} totalSteps={5} />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Step 3 of 5
</p>
<Stepper active={3} totalSteps={5} />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Step 4 of 5
</p>
<Stepper active={4} totalSteps={5} />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Step 5 of 5
</p>
<Stepper active={5} totalSteps={5} />
</div>
</div>
</div>
</section>
{/* Progress Section */}
<section className="space-y-[var(--spacing-scale-024)]">
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
Progress Component
</h2>
<div className="bg-white rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]">
<div className="space-y-[var(--spacing-scale-016)]">
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 1-0
</p>
<Progress progress="1-0" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 1-1
</p>
<Progress progress="1-1" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 1-2
</p>
<Progress progress="1-2" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 1-3
</p>
<Progress progress="1-3" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 1-4
</p>
<Progress progress="1-4" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 1-5
</p>
<Progress progress="1-5" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 2-0
</p>
<Progress progress="2-0" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 2-1
</p>
<Progress progress="2-1" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 2-2
</p>
<Progress progress="2-2" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 3-0
</p>
<Progress progress="3-0" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 3-1
</p>
<Progress progress="3-1" />
</div>
<div>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-008)]">
Progress: 3-2
</p>
<Progress progress="3-2" />
</div>
</div>
</div>
</section>
{/* Create Component Section */}
<section className="space-y-[var(--spacing-scale-024)]">
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
Create Component
</h2>
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]">
<div className="space-y-[var(--spacing-scale-016)]">
<Button
variant="filled-inverse"
size="medium"
onClick={() => setCreateOpen(true)}
>
Open Create Dialog
</Button>
<div className="space-y-[var(--spacing-scale-008)]">
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)]">
Step {createStep} of 3
</p>
<Button
variant="ghost"
size="small"
onClick={() => setCreateStep((prev) => Math.max(1, prev - 1))}
disabled={createStep === 1}
>
Previous Step
</Button>
<Button
variant="ghost"
size="small"
onClick={() => setCreateStep((prev) => Math.min(3, prev + 1))}
disabled={createStep === 3}
>
Next Step
</Button>
</div>
</div>
</div>
<Create
isOpen={createOpen}
onClose={() => setCreateOpen(false)}
title={
createStep === 1
? "What do you call your group's new policy?"
: createStep === 2
? "How should conflicts be resolved?"
: "Review your policy"
}
description="You can also combine or add new approaches to the list"
showBackButton={true}
showNextButton={true}
onBack={() => setCreateStep((prev) => Math.max(1, prev - 1))}
onNext={() => setCreateStep((prev) => Math.min(3, prev + 1))}
backButtonText="Back"
nextButtonText={createStep === 3 ? "Finish" : "Next"}
nextButtonDisabled={createStep === 1 && !policyName.trim()}
currentStep={createStep}
totalSteps={3}
>
<div className="space-y-[var(--spacing-scale-024)]">
{createStep === 1 && (
<InputWithCounter
label="Label"
placeholder="Policy name"
value={policyName}
onChange={setPolicyName}
maxLength={48}
showHelpIcon
/>
)}
{createStep === 2 && (
<div className="space-y-[var(--spacing-scale-008)]">
<Input
label="Conflict Resolution Method"
placeholder="Enter method"
value=""
/>
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-primary)]">
Select how conflicts should be resolved in your group.
</p>
</div>
)}
{createStep === 3 && (
<div className="space-y-[var(--spacing-scale-016)]"> <div className="space-y-[var(--spacing-scale-016)]">
<p className="font-inter text-[16px] leading-[24px] text-[var(--color-content-default-primary)]"> <TextInput
Review your policy configuration before finalizing. label="Default Text Input"
</p> placeholder="Enter text"
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-200,8px)] p-[var(--spacing-scale-016)]"> value={defaultInputValue}
<p className="font-inter text-[14px] leading-[20px] text-[var(--color-content-default-secondary)]"> onChange={(e) => setDefaultInputValue(e.target.value)}
Policy details will appear here />
</p> <TextInput
</div> label="Interactive Text Input (click = active, tab = focus)"
placeholder="Enter text"
value={activeInputValue}
onChange={(e) => setActiveInputValue(e.target.value)}
/>
<TextInput
label="Disabled Text Input"
placeholder="Enter text"
value=""
disabled
/>
<TextInput
label="Error Text Input"
placeholder="Enter text"
value={errorInputValue}
onChange={(e) => setErrorInputValue(e.target.value)}
error
/>
</div> </div>
)} </div>
</div> </div>
</Create> </div>
</section> </section>
{/* IconCard Component Section */} {/* Select Input Section */}
<section className="space-y-[var(--spacing-scale-024)]"> <section className="space-y-[var(--spacing-scale-024)]">
<h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]"> <h2 className="font-bricolage-grotesque text-[32px] leading-[40px] font-bold text-[var(--color-content-default-primary)]">
IconCard Component Select Input Component
</h2> </h2>
<div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]"> <div className="bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300,12px)] p-[var(--spacing-scale-032)] space-y-[var(--spacing-scale-024)]">
<div className="flex flex-wrap gap-[var(--spacing-scale-024)]"> <div className="space-y-[var(--spacing-scale-016)]">
<IconCard <div>
icon={ <h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
<img All Sizes
src={getAssetPath("assets/Vector_WorkerCoop.svg")} </h3>
alt="" <div className="space-y-[var(--spacing-scale-016)]">
className="w-[36px] h-[36px]" <SelectInput
width="36" label="Small Select Input"
height="36" placeholder="Choose an option"
size="small"
value={selectValue}
onChange={(data) => setSelectValue(data.target.value)}
options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/> />
} <SelectInput
title="Worker's cooperatives" label="Medium Select Input"
description="Employee-owned businesses often need to clarify how power is shared, decisions are made, and how processes operate within their organizations." placeholder="Choose an option"
onClick={() => { size="medium"
// IconCard clicked handler value={selectValue}
}} onChange={(data) => setSelectValue(data.target.value)}
/> options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/>
<SelectInput
label="Large Select Input"
placeholder="Choose an option"
size="large"
value={selectValue}
onChange={(data) => setSelectValue(data.target.value)}
options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
States
</h3>
<div className="space-y-[var(--spacing-scale-016)]">
<SelectInput
label="Default Select Input"
placeholder="Choose an option"
value=""
options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/>
<SelectInput
label="Disabled Select Input"
placeholder="Choose an option"
value=""
disabled
options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/>
<SelectInput
label="Error Select Input"
placeholder="Choose an option"
value=""
error
options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/>
</div>
</div>
<div>
<h3 className="font-inter text-[20px] leading-[24px] font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-012)]">
Label Variants
</h3>
<div className="space-y-[var(--spacing-scale-016)]">
<SelectInput
label="Default Label"
placeholder="Choose an option"
value=""
labelVariant="default"
options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/>
<SelectInput
label="Horizontal Label"
placeholder="Choose an option"
value=""
labelVariant="horizontal"
options={[
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
]}
/>
</div>
</div>
</div> </div>
</div> </div>
</section> </section>
-176
View File
@@ -1,176 +0,0 @@
"use client";
import { memo, forwardRef } from "react";
import { useComponentId, useFormField } from "../../hooks";
import { InputView } from "./Input.view";
import type { InputProps } from "./Input.types";
const InputContainer = forwardRef<HTMLInputElement, InputProps>(
(
{
size = "medium",
labelVariant = "default",
state = "default",
disabled = false,
error = false,
label,
placeholder,
value,
onChange,
onFocus,
onBlur,
id,
name,
type = "text",
className = "",
...props
},
ref,
) => {
// Generate unique ID for accessibility if not provided
const { id: inputId, labelId } = useComponentId("input", id);
// Size variants
const sizeStyles: Record<
string,
{
input: string;
label: string;
container: string;
radius: string;
}
> = {
small: {
input:
labelVariant === "horizontal"
? "h-[30px] px-[12px] py-[8px] text-[10px]"
: "h-[32px] px-[12px] py-[8px] text-[10px]",
label: "text-[12px] leading-[14px] font-medium",
container: "gap-[4px]",
radius: "var(--measures-radius-small)",
},
medium: {
input: "h-[36px] px-[12px] py-[8px] text-[14px] leading-[20px]",
label: "text-[14px] leading-[16px] font-medium",
container: "gap-[8px]",
radius: "var(--measures-radius-medium)",
},
large: {
input: "h-[40px] px-[12px] py-[8px] text-[16px] leading-[24px]",
label: "text-[16px] leading-[20px] font-medium",
container: "gap-[12px]",
radius: "var(--measures-radius-large)",
},
};
// State styles
const getStateStyles = (): {
input: string;
label: string;
} => {
if (disabled) {
return {
input:
"bg-[var(--color-content-default-secondary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)] cursor-not-allowed",
label: "text-[var(--color-content-default-secondary)]",
};
}
if (error) {
return {
input:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-utility-negative)]",
label: "text-[var(--color-content-default-secondary)]",
};
}
switch (state) {
case "active":
return {
input:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)]",
label: "text-[var(--color-content-default-secondary)]",
};
case "hover":
return {
input:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)] shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
label: "text-[var(--color-content-default-secondary)]",
};
case "focus":
return {
input:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-utility-info)] shadow-[0_0_5px_3px_#3281F8]",
label: "text-[var(--color-content-default-secondary)]",
};
default:
return {
input:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)] hover:shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
label: "text-[var(--color-content-default-secondary)]",
};
}
};
const stateStyles = getStateStyles();
const currentSize = sizeStyles[size];
// Container classes based on label variant
const containerClasses =
labelVariant === "horizontal"
? `flex items-center gap-[12px]`
: `flex flex-col ${currentSize.container}`;
const labelClasses =
labelVariant === "horizontal"
? `${currentSize.label} font-inter min-w-fit`
: `${currentSize.label} font-inter`;
const inputClasses = `
w-full border transition-all duration-200 ease-in-out
focus:outline-none focus:ring-0
${currentSize.input}
${stateStyles.input}
${className}
`.trim();
// Form field handlers with disabled state handling
const { handleChange, handleFocus, handleBlur } =
useFormField<HTMLInputElement>(disabled, {
onChange,
onFocus,
onBlur,
});
return (
<InputView
ref={ref}
inputId={inputId}
labelId={labelId}
size={size}
labelVariant={labelVariant}
state={state}
disabled={disabled}
error={error}
label={label}
placeholder={placeholder}
value={value}
name={name}
type={type}
className={className}
containerClasses={containerClasses}
labelClasses={labelClasses}
inputClasses={inputClasses}
borderRadius={currentSize.radius}
handleChange={handleChange}
handleFocus={handleFocus}
handleBlur={handleBlur}
{...props}
/>
);
},
);
InputContainer.displayName = "Input";
export default memo(InputContainer);
-62
View File
@@ -1,62 +0,0 @@
import { forwardRef } from "react";
import type { InputViewProps } from "./Input.types";
export const InputView = forwardRef<HTMLInputElement, InputViewProps>(
(
{
inputId,
labelId,
label,
placeholder,
value,
name,
type,
disabled,
size: _size,
labelVariant: _labelVariant,
state: _state,
error: _error,
className: _className,
containerClasses,
labelClasses,
inputClasses,
borderRadius,
handleChange,
handleFocus,
handleBlur,
},
ref,
) => {
return (
<div className={containerClasses}>
{label && (
<label
id={labelId}
htmlFor={inputId}
className={`${labelClasses} font-inter font-medium text-[var(--color-content-default-secondary)]`}
>
{label}
</label>
)}
<div className={disabled ? "opacity-40" : ""}>
<input
ref={ref}
id={inputId}
name={name}
type={type}
value={value}
placeholder={placeholder}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
disabled={disabled}
className={inputClasses}
style={{ borderRadius }}
/>
</div>
</div>
);
},
);
InputView.displayName = "InputView";
-2
View File
@@ -1,2 +0,0 @@
export { default } from "./Input.container";
export type { InputProps } from "./Input.types";
-2
View File
@@ -1,2 +0,0 @@
export { default } from "./Select.container";
export type { SelectProps, SelectOptionData } from "./Select.types";
@@ -14,10 +14,10 @@ import React, {
useEffect, useEffect,
} from "react"; } from "react";
import { useClickOutside } from "../../hooks"; import { useClickOutside } from "../../hooks";
import { SelectView } from "./Select.view"; import { SelectInputView } from "./SelectInput.view";
import type { SelectProps } from "./Select.types"; import type { SelectInputProps } from "./SelectInput.types";
const SelectContainer = forwardRef<HTMLButtonElement, SelectProps>( const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
( (
{ {
id, id,
@@ -38,7 +38,7 @@ const SelectContainer = forwardRef<HTMLButtonElement, SelectProps>(
ref, ref,
) => { ) => {
const generatedId = useId(); const generatedId = useId();
const selectId = id || `select-${generatedId}`; const selectId = id || `select-input-${generatedId}`;
const labelId = `${selectId}-label`; const labelId = `${selectId}-label`;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(value || ""); const [selectedValue, setSelectedValue] = useState(value || "");
@@ -267,7 +267,7 @@ const SelectContainer = forwardRef<HTMLButtonElement, SelectProps>(
}; };
return ( return (
<SelectView <SelectInputView
label={label} label={label}
placeholder={placeholder} placeholder={placeholder}
size={size} size={size}
@@ -299,6 +299,6 @@ const SelectContainer = forwardRef<HTMLButtonElement, SelectProps>(
}, },
); );
SelectContainer.displayName = "Select"; SelectInputContainer.displayName = "SelectInput";
export default memo(SelectContainer); export default memo(SelectInputContainer);
@@ -5,7 +5,7 @@ export interface SelectOptionData {
label: string; label: string;
} }
export interface SelectProps { export interface SelectInputProps {
id?: string; id?: string;
label?: string; label?: string;
labelVariant?: "default" | "horizontal"; labelVariant?: "default" | "horizontal";
@@ -1,9 +1,9 @@
import React, { Children, type ReactNode } from "react"; import React, { Children, type ReactNode } from "react";
import SelectDropdown from "../SelectDropdown"; import SelectDropdown from "../SelectDropdown";
import SelectOption from "../SelectOption"; import SelectOption from "../SelectOption";
import type { SelectOptionData } from "./Select.types"; import type { SelectOptionData } from "./SelectInput.types";
export interface SelectViewProps { export interface SelectInputViewProps {
label?: string; label?: string;
placeholder: string; placeholder: string;
size: "small" | "medium" | "large"; size: "small" | "medium" | "large";
@@ -36,7 +36,7 @@ export interface SelectViewProps {
ariaInvalid?: boolean; ariaInvalid?: boolean;
} }
export function SelectView({ export function SelectInputView({
label, label,
placeholder: _placeholder, placeholder: _placeholder,
size, size,
@@ -62,7 +62,7 @@ export function SelectView({
ariaLabelledby, ariaLabelledby,
ariaInvalid, ariaInvalid,
...props ...props
}: SelectViewProps) { }: SelectInputViewProps) {
return ( return (
<div className={containerClasses}> <div className={containerClasses}>
{label && ( {label && (
+2
View File
@@ -0,0 +1,2 @@
export { default } from "./SelectInput.container";
export type { SelectInputProps, SelectOptionData } from "./SelectInput.types";
@@ -0,0 +1,227 @@
"use client";
import { memo, forwardRef, useState, useRef } from "react";
import { useComponentId, useFormField } from "../../hooks";
import { TextInputView } from "./TextInput.view";
import type { TextInputProps } from "./TextInput.types";
const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
(
{
state: externalState = "default",
disabled = false,
error = false,
label,
placeholder,
value,
onChange,
onFocus,
onBlur,
id,
name,
type = "text",
className = "",
showHelpIcon = true,
...props
},
ref,
) => {
// Generate unique ID for accessibility if not provided
const { id: inputId, labelId } = useComponentId("text-input", id);
// Internal state management: track if focused and how (mouse vs keyboard)
const [isFocused, setIsFocused] = useState(false);
const [focusMethod, setFocusMethod] = useState<"mouse" | "keyboard" | null>(null);
const wasMouseDownRef = useRef(false);
// Determine if we should auto-manage focus (only when state is "default" or undefined)
// If state is "active", "hover", or "focus", respect it and don't override
const shouldAutoManageFocus = externalState === "default" || externalState === undefined;
// Determine actual state:
// - Active: when clicked (mouse focus)
// - Focus: when tabbed (keyboard focus)
// - Default: when not focused
const actualState = shouldAutoManageFocus
? isFocused
? focusMethod === "mouse"
? "active"
: "focus"
: "default"
: externalState;
// 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)",
};
// State styles based on Figma designs
const getStateStyles = (): {
input: string;
label: string;
inputWrapper: string;
focusRing: string;
} => {
if (disabled) {
return {
input:
"bg-[var(--color-surface-default-secondary)] text-[var(--color-content-inverse-tertiary,#2d2d2d)] border border-solid border-[var(--color-border-default-primary)] cursor-not-allowed",
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing: "",
};
}
if (error) {
const filledStyles = isFilled
? "font-medium leading-[20px]"
: "font-normal leading-[24px]";
return {
input: `bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border-2 border-solid border-[var(--color-border-default-utility-negative)] ${filledStyles}`,
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing: "",
};
}
switch (actualState) {
case "active": {
const filledStyles = isFilled
? "font-medium leading-[20px]"
: "font-normal leading-[24px]";
return {
input: `bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border-2 border-solid border-[var(--color-border-default-tertiary)] ${filledStyles}`,
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing: "",
};
}
case "focus": {
const filledStyles = isFilled
? "font-medium leading-[20px]"
: "font-normal leading-[24px]";
return {
input: `bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-tertiary)] ${filledStyles}`,
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing:
"absolute border-2 border-solid border-[var(--color-border-inverse-primary)] inset-0 rounded-[var(--measures-radius-200,8px)] shadow-[0px_0px_0px_2px_var(--color-border-default-primary)] pointer-events-none",
};
}
default: {
const filledStyles = isFilled
? "font-medium leading-[20px]"
: "font-normal leading-[24px]";
// Default state uses primary border (matches Figma - border color same as background, so border is subtle)
return {
input: `bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-primary)] ${filledStyles}`,
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing: "",
};
}
}
};
const stateStyles = getStateStyles();
// Container classes (default label variant only)
const containerClasses = `flex flex-col ${sizeStyles.container}`;
const labelClasses = `${sizeStyles.label} font-inter`;
// Base classes without border (border is added in state styles)
const inputClasses = `
w-full transition-all duration-200 ease-in-out
focus:outline-none focus:ring-0
placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)]
${sizeStyles.input}
${stateStyles.input}
${className}
`.trim();
// Text color for filled text (placeholder color is handled above)
const textColorClass = isFilled
? "text-[var(--color-content-default-primary)]"
: "text-[var(--color-content-default-tertiary,#b4b4b4)]";
// Form field handlers with disabled state handling
const { handleChange, handleBlur } = useFormField<HTMLInputElement>(disabled, {
onChange,
onBlur: (e) => {
if (shouldAutoManageFocus) {
setIsFocused(false);
setFocusMethod(null);
wasMouseDownRef.current = false;
}
onBlur?.(e);
},
});
// Handle mouse down to detect mouse clicks
const handleMouseDown = () => {
if (!disabled && shouldAutoManageFocus) {
wasMouseDownRef.current = true;
}
};
// Custom focus handler to detect mouse vs keyboard
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
if (disabled) return;
// Detect if focus came from keyboard (Tab) or mouse (click)
// If mouseDown was detected before focus, it's a mouse click (active)
// Otherwise, it's keyboard navigation (focus)
const method = wasMouseDownRef.current ? "mouse" : "keyboard";
if (shouldAutoManageFocus) {
setIsFocused(true);
setFocusMethod(method);
// Reset mouse down flag after focus is processed
wasMouseDownRef.current = false;
}
onFocus?.(e);
};
return (
<TextInputView
ref={ref}
inputId={inputId}
labelId={labelId}
state={actualState}
disabled={disabled}
error={error}
label={label}
placeholder={placeholder}
value={value}
name={name}
type={type}
className={className}
containerClasses={containerClasses}
labelClasses={labelClasses}
inputClasses={`${inputClasses} ${textColorClass}`}
borderRadius={sizeStyles.radius}
handleChange={handleChange}
handleFocus={handleFocus}
handleBlur={handleBlur}
handleMouseDown={handleMouseDown}
showHelpIcon={showHelpIcon}
isFilled={isFilled}
inputWrapperClasses={stateStyles.inputWrapper}
focusRingClasses={stateStyles.focusRing}
{...props}
/>
);
},
);
TextInputContainer.displayName = "TextInput";
export default memo(TextInputContainer);
@@ -1,9 +1,7 @@
export interface InputProps extends Omit< export interface TextInputProps extends Omit<
React.InputHTMLAttributes<HTMLInputElement>, React.InputHTMLAttributes<HTMLInputElement>,
"size" | "onChange" | "onFocus" | "onBlur" "size" | "onChange" | "onFocus" | "onBlur"
> { > {
size?: "small" | "medium" | "large";
labelVariant?: "default" | "horizontal";
state?: "default" | "active" | "hover" | "focus"; state?: "default" | "active" | "hover" | "focus";
disabled?: boolean; disabled?: boolean;
error?: boolean; error?: boolean;
@@ -14,13 +12,12 @@ export interface InputProps extends Omit<
onFocus?: (_e: React.FocusEvent<HTMLInputElement>) => void; onFocus?: (_e: React.FocusEvent<HTMLInputElement>) => void;
onBlur?: (_e: React.FocusEvent<HTMLInputElement>) => void; onBlur?: (_e: React.FocusEvent<HTMLInputElement>) => void;
className?: string; className?: string;
showHelpIcon?: boolean;
} }
export interface InputViewProps { export interface TextInputViewProps {
inputId: string; inputId: string;
labelId: string; labelId: string;
size: "small" | "medium" | "large";
labelVariant: "default" | "horizontal";
state: "default" | "active" | "hover" | "focus"; state: "default" | "active" | "hover" | "focus";
disabled: boolean; disabled: boolean;
error: boolean; error: boolean;
@@ -37,4 +34,9 @@ export interface InputViewProps {
handleChange: (_e: React.ChangeEvent<HTMLInputElement>) => void; handleChange: (_e: React.ChangeEvent<HTMLInputElement>) => void;
handleFocus: (_e: React.FocusEvent<HTMLInputElement>) => void; handleFocus: (_e: React.FocusEvent<HTMLInputElement>) => void;
handleBlur: (_e: React.FocusEvent<HTMLInputElement>) => void; handleBlur: (_e: React.FocusEvent<HTMLInputElement>) => void;
handleMouseDown?: () => void;
showHelpIcon?: boolean;
isFilled?: boolean;
inputWrapperClasses?: string;
focusRingClasses?: string;
} }
@@ -0,0 +1,83 @@
import { forwardRef } from "react";
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
import type { TextInputViewProps } from "./TextInput.types";
export const TextInputView = forwardRef<HTMLInputElement, TextInputViewProps>(
(
{
inputId,
labelId,
label,
placeholder,
value,
name,
type,
disabled,
error: _error,
className: _className,
containerClasses,
labelClasses,
inputClasses,
borderRadius,
handleChange,
handleFocus,
handleBlur,
handleMouseDown,
showHelpIcon = true,
inputWrapperClasses = "relative",
focusRingClasses = "",
},
ref,
) => {
return (
<div className={containerClasses}>
{label && (
<div className="flex flex-wrap gap-[var(--measures-spacing-200,4px_8px)] items-baseline pr-[var(--measures-spacing-100,4px)] relative shrink-0 w-full">
<div className="flex gap-[var(--measures-spacing-050,2px)] items-center relative shrink-0">
<label
id={labelId}
htmlFor={inputId}
className={`${labelClasses} font-inter font-medium text-[var(--color-content-default-primary)]`}
>
{label}
</label>
{showHelpIcon && (
<div className="relative shrink-0 size-[12px]">
<img
src={getAssetPath(ASSETS.ICON_HELP)}
alt="Help"
className="block max-w-none size-full"
/>
</div>
)}
</div>
</div>
)}
<div className={inputWrapperClasses}>
<div className={disabled ? "opacity-40" : ""}>
<input
ref={ref}
id={inputId}
name={name}
type={type}
value={value}
placeholder={placeholder}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
onMouseDown={handleMouseDown}
disabled={disabled}
className={inputClasses}
style={{ borderRadius }}
/>
</div>
{focusRingClasses && (
<div className={focusRingClasses} aria-hidden="true" />
)}
</div>
</div>
);
},
);
TextInputView.displayName = "TextInputView";
+2
View File
@@ -0,0 +1,2 @@
export { default } from "./TextInput.container";
export type { TextInputProps } from "./TextInput.types";
+3
View File
@@ -62,4 +62,7 @@ export const ASSETS = {
// Tooltip icons // Tooltip icons
ICON_POINTER: "assets/Icon_Pointer.svg", ICON_POINTER: "assets/Icon_Pointer.svg",
// Help icon
ICON_HELP: "assets/Icon_Help.svg",
} as const; } as const;
+4 -4
View File
@@ -1,5 +1,5 @@
import Create from "../app/components/Create"; import Create from "../app/components/Create";
import Input from "../app/components/Input"; import TextInput from "../app/components/TextInput";
export default { export default {
title: "Components/Create", title: "Components/Create",
@@ -57,7 +57,7 @@ Default.args = {
description: "You can also combine or add new approaches to the list", description: "You can also combine or add new approaches to the list",
children: ( children: (
<div className="space-y-4"> <div className="space-y-4">
<Input label="Label" placeholder="Policy name" value="" /> <TextInput label="Label" placeholder="Policy name" value="" />
<p className="text-[12px] text-[var(--color-content-default-tertiary)]"> <p className="text-[12px] text-[var(--color-content-default-tertiary)]">
0/48 0/48
</p> </p>
@@ -77,7 +77,7 @@ WithStepper.args = {
description: "You can also combine or add new approaches to the list", description: "You can also combine or add new approaches to the list",
children: ( children: (
<div className="space-y-4"> <div className="space-y-4">
<Input label="Label" placeholder="Policy name" value="" /> <TextInput label="Label" placeholder="Policy name" value="" />
<p className="text-[12px] text-[var(--color-content-default-tertiary)]"> <p className="text-[12px] text-[var(--color-content-default-tertiary)]">
0/48 0/48
</p> </p>
@@ -155,7 +155,7 @@ NextButtonDisabled.args = {
description: "You can also combine or add new approaches to the list", description: "You can also combine or add new approaches to the list",
children: ( children: (
<div className="space-y-4"> <div className="space-y-4">
<Input label="Label" placeholder="Policy name" value="" /> <TextInput label="Label" placeholder="Policy name" value="" />
<p className="text-[12px] text-[var(--color-content-default-tertiary)]"> <p className="text-[12px] text-[var(--color-content-default-tertiary)]">
0/48 0/48
</p> </p>
@@ -1,9 +1,9 @@
import React, { useState } from "react"; import React, { useState } from "react";
import Select from "../app/components/Select"; import SelectInput from "../app/components/SelectInput";
export default { export default {
title: "Forms/Select", title: "Forms/SelectInput",
component: Select, component: SelectInput,
argTypes: { argTypes: {
size: { size: {
control: { type: "select" }, control: { type: "select" },
@@ -35,10 +35,10 @@ export default {
const Template = (args) => { const Template = (args) => {
const [value, setValue] = useState(""); const [value, setValue] = useState("");
return ( return (
<Select <SelectInput
{...args} {...args}
value={value} value={value}
onChange={setValue} onChange={(data) => setValue(data.target.value)}
options={[ options={[
{ value: "option1", label: "Option 1" }, { value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" }, { value: "option2", label: "Option 2" },
@@ -50,27 +50,27 @@ const Template = (args) => {
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
label: "Default Select", label: "Default Select Input",
placeholder: "Select", placeholder: "Select",
}; };
export const Small = Template.bind({}); export const Small = Template.bind({});
Small.args = { Small.args = {
label: "Small Select", label: "Small Select Input",
size: "small", size: "small",
placeholder: "Select", placeholder: "Select",
}; };
export const Medium = Template.bind({}); export const Medium = Template.bind({});
Medium.args = { Medium.args = {
label: "Medium Select", label: "Medium Select Input",
size: "medium", size: "medium",
placeholder: "Select", placeholder: "Select",
}; };
export const Large = Template.bind({}); export const Large = Template.bind({});
Large.args = { Large.args = {
label: "Large Select", label: "Large Select Input",
size: "large", size: "large",
placeholder: "Select", placeholder: "Select",
}; };
@@ -126,7 +126,7 @@ Disabled.args = {
export const Interactive = Template.bind({}); export const Interactive = Template.bind({});
Interactive.args = { Interactive.args = {
label: "Interactive Select", label: "Interactive Select Input",
placeholder: "Choose an option", placeholder: "Choose an option",
}; };
@@ -138,39 +138,42 @@ export const AllSizes = () => {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Select <SelectInput
label="Small" label="Small"
size="small" size="small"
value={smallValue} value={smallValue}
onChange={(e) => setSmallValue(e.target.value)} onChange={(data) => setSmallValue(data.target.value)}
placeholder="Select" placeholder="Select"
> options={[
<option value="item1">Context Menu Item 1</option> { value: "item1", label: "Context Menu Item 1" },
<option value="item2">Context Menu Item 2</option> { value: "item2", label: "Context Menu Item 2" },
<option value="item3">Context Menu Item 3</option> { value: "item3", label: "Context Menu Item 3" },
</Select> ]}
<Select />
<SelectInput
label="Medium" label="Medium"
size="medium" size="medium"
value={mediumValue} value={mediumValue}
onChange={(e) => setMediumValue(e.target.value)} onChange={(data) => setMediumValue(data.target.value)}
placeholder="Select" placeholder="Select"
> options={[
<option value="item1">Context Menu Item 1</option> { value: "item1", label: "Context Menu Item 1" },
<option value="item2">Context Menu Item 2</option> { value: "item2", label: "Context Menu Item 2" },
<option value="item3">Context Menu Item 3</option> { value: "item3", label: "Context Menu Item 3" },
</Select> ]}
<Select />
<SelectInput
label="Large" label="Large"
size="large" size="large"
value={largeValue} value={largeValue}
onChange={(e) => setLargeValue(e.target.value)} onChange={(data) => setLargeValue(data.target.value)}
placeholder="Select" placeholder="Select"
> options={[
<option value="item1">Context Menu Item 1</option> { value: "item1", label: "Context Menu Item 1" },
<option value="item2">Context Menu Item 2</option> { value: "item2", label: "Context Menu Item 2" },
<option value="item3">Context Menu Item 3</option> { value: "item3", label: "Context Menu Item 3" },
</Select> ]}
/>
</div> </div>
); );
}; };
@@ -182,38 +185,41 @@ export const AllStates = () => {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Select <SelectInput
label="Default State" label="Default State"
value={defaultValue} value={defaultValue}
onChange={(e) => setDefaultValue(e.target.value)} onChange={(data) => setDefaultValue(data.target.value)}
placeholder="Select" placeholder="Select"
> options={[
<option value="item1">Context Menu Item 1</option> { value: "item1", label: "Context Menu Item 1" },
<option value="item2">Context Menu Item 2</option> { value: "item2", label: "Context Menu Item 2" },
<option value="item3">Context Menu Item 3</option> { value: "item3", label: "Context Menu Item 3" },
</Select> ]}
<Select />
<SelectInput
label="Error State" label="Error State"
error={true} error={true}
value={errorValue} value={errorValue}
onChange={(e) => setErrorValue(e.target.value)} onChange={(data) => setErrorValue(data.target.value)}
placeholder="Select" placeholder="Select"
> options={[
<option value="item1">Context Menu Item 1</option> { value: "item1", label: "Context Menu Item 1" },
<option value="item2">Context Menu Item 2</option> { value: "item2", label: "Context Menu Item 2" },
<option value="item3">Context Menu Item 3</option> { value: "item3", label: "Context Menu Item 3" },
</Select> ]}
<Select />
<SelectInput
label="Disabled State" label="Disabled State"
disabled={true} disabled={true}
value={disabledValue} value={disabledValue}
onChange={(e) => setDisabledValue(e.target.value)} onChange={(data) => setDisabledValue(data.target.value)}
placeholder="Select" placeholder="Select"
> options={[
<option value="item1">Context Menu Item 1</option> { value: "item1", label: "Context Menu Item 1" },
<option value="item2">Context Menu Item 2</option> { value: "item2", label: "Context Menu Item 2" },
<option value="item3">Context Menu Item 3</option> { value: "item3", label: "Context Menu Item 3" },
</Select> ]}
/>
</div> </div>
); );
}; };
@@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import Input from "../app/components/Input"; import TextInput from "../app/components/TextInput";
export default { export default {
title: "Forms/Input", title: "Forms/TextInput",
component: Input, component: TextInput,
parameters: { parameters: {
layout: "centered", layout: "centered",
}, },
@@ -38,12 +38,12 @@ export default {
}, },
}; };
const Template = (args) => <Input {...args} />; const Template = (args) => <TextInput {...args} />;
// Default story // Default story
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
label: "Default Input", label: "Default Text Input",
placeholder: "Enter text...", placeholder: "Enter text...",
size: "medium", size: "medium",
labelVariant: "default", labelVariant: "default",
@@ -53,7 +53,7 @@ Default.args = {
// Size variants // Size variants
export const Small = Template.bind({}); export const Small = Template.bind({});
Small.args = { Small.args = {
label: "Small Input", label: "Small Text Input",
placeholder: "Small size", placeholder: "Small size",
size: "small", size: "small",
labelVariant: "default", labelVariant: "default",
@@ -62,7 +62,7 @@ Small.args = {
export const Medium = Template.bind({}); export const Medium = Template.bind({});
Medium.args = { Medium.args = {
label: "Medium Input", label: "Medium Text Input",
placeholder: "Medium size", placeholder: "Medium size",
size: "medium", size: "medium",
labelVariant: "default", labelVariant: "default",
@@ -71,7 +71,7 @@ Medium.args = {
export const Large = Template.bind({}); export const Large = Template.bind({});
Large.args = { Large.args = {
label: "Large Input", label: "Large Text Input",
placeholder: "Large size", placeholder: "Large size",
size: "large", size: "large",
labelVariant: "default", labelVariant: "default",
@@ -151,7 +151,7 @@ export const Interactive = (args) => {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Input <TextInput
{...args} {...args}
value={value} value={value}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
@@ -161,7 +161,7 @@ export const Interactive = (args) => {
); );
}; };
Interactive.args = { Interactive.args = {
label: "Interactive Input", label: "Interactive Text Input",
placeholder: "Type something...", placeholder: "Type something...",
size: "medium", size: "medium",
labelVariant: "default", labelVariant: "default",
@@ -174,7 +174,7 @@ export const AllSizes = () => (
<div> <div>
<h3 className="text-lg font-semibold mb-4">Small Size</h3> <h3 className="text-lg font-semibold mb-4">Small Size</h3>
<div className="space-y-4"> <div className="space-y-4">
<Input <TextInput
label="Small Default" label="Small Default"
placeholder="Small with top label" placeholder="Small with top label"
size="small" size="small"
@@ -186,13 +186,13 @@ export const AllSizes = () => (
<div> <div>
<h3 className="text-lg font-semibold mb-4">Medium Size</h3> <h3 className="text-lg font-semibold mb-4">Medium Size</h3>
<div className="space-y-4"> <div className="space-y-4">
<Input <TextInput
label="Medium Default" label="Medium Default"
placeholder="Medium with top label" placeholder="Medium with top label"
size="medium" size="medium"
labelVariant="default" labelVariant="default"
/> />
<Input <TextInput
label="Medium Horizontal" label="Medium Horizontal"
placeholder="Medium with left label" placeholder="Medium with left label"
size="medium" size="medium"
@@ -204,13 +204,13 @@ export const AllSizes = () => (
<div> <div>
<h3 className="text-lg font-semibold mb-4">Large Size</h3> <h3 className="text-lg font-semibold mb-4">Large Size</h3>
<div className="space-y-4"> <div className="space-y-4">
<Input <TextInput
label="Large Default" label="Large Default"
placeholder="Large with top label" placeholder="Large with top label"
size="large" size="large"
labelVariant="default" labelVariant="default"
/> />
<Input <TextInput
label="Large Horizontal" label="Large Horizontal"
placeholder="Large with left label" placeholder="Large with left label"
size="large" size="large"
@@ -225,39 +225,39 @@ export const AllSizes = () => (
export const AllStates = () => ( export const AllStates = () => (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h3 className="text-lg font-semibold mb-4">Input States</h3> <h3 className="text-lg font-semibold mb-4">Text Input States</h3>
<div className="space-y-4"> <div className="space-y-4">
<Input <TextInput
label="Default State" label="Default State"
placeholder="Default input" placeholder="Default input"
size="medium" size="medium"
state="default" state="default"
/> />
<Input <TextInput
label="Active State" label="Active State"
placeholder="Active input" placeholder="Active input"
size="medium" size="medium"
state="active" state="active"
/> />
<Input <TextInput
label="Hover State" label="Hover State"
placeholder="Hover input" placeholder="Hover input"
size="medium" size="medium"
state="hover" state="hover"
/> />
<Input <TextInput
label="Focus State" label="Focus State"
placeholder="Focused input" placeholder="Focused input"
size="medium" size="medium"
state="focus" state="focus"
/> />
<Input <TextInput
label="Error State" label="Error State"
placeholder="Error input" placeholder="Error input"
size="medium" size="medium"
error={true} error={true}
/> />
<Input <TextInput
label="Disabled State" label="Disabled State"
placeholder="Disabled input" placeholder="Disabled input"
size="medium" size="medium"
+2 -2
View File
@@ -4,7 +4,7 @@ import { screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom/vitest"; import "@testing-library/jest-dom/vitest";
import { renderWithProviders } from "../utils/test-utils"; import { renderWithProviders } from "../utils/test-utils";
import Create from "../../app/components/Create"; import Create from "../../app/components/Create";
import Input from "../../app/components/Input"; import TextInput from "../../app/components/TextInput";
type CreateProps = React.ComponentProps<typeof Create>; type CreateProps = React.ComponentProps<typeof Create>;
@@ -167,7 +167,7 @@ describe("Create", () => {
it("traps focus within create dialog", async () => { it("traps focus within create dialog", async () => {
renderWithProviders( renderWithProviders(
<Create {...defaultProps}> <Create {...defaultProps}>
<Input label="Test Input" /> <TextInput label="Test Input" />
</Create>, </Create>,
); );
@@ -1,20 +1,20 @@
import React from "react"; import React from "react";
import Select from "../../app/components/Select"; import SelectInput from "../../app/components/SelectInput";
import { componentTestSuite } from "../utils/componentTestSuite"; import { componentTestSuite } from "../utils/componentTestSuite";
type SelectProps = React.ComponentProps<typeof Select>; type SelectInputProps = React.ComponentProps<typeof SelectInput>;
componentTestSuite<SelectProps>({ componentTestSuite<SelectInputProps>({
component: Select, component: SelectInput,
name: "Select", name: "SelectInput",
props: { props: {
label: "Test Select", label: "Test Select Input",
placeholder: "Select an option", placeholder: "Select an option",
options: [ options: [
{ value: "option1", label: "Option 1" }, { value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" }, { value: "option2", label: "Option 2" },
], ],
} as SelectProps, } as SelectInputProps,
requiredProps: ["options"], requiredProps: ["options"],
optionalProps: { optionalProps: {
size: "medium", size: "medium",
@@ -1,15 +1,15 @@
import React from "react"; import React from "react";
import Input from "../../app/components/Input"; import TextInput from "../../app/components/TextInput";
import { componentTestSuite } from "../utils/componentTestSuite"; import { componentTestSuite } from "../utils/componentTestSuite";
type InputProps = React.ComponentProps<typeof Input>; type TextInputProps = React.ComponentProps<typeof TextInput>;
componentTestSuite<InputProps>({ componentTestSuite<TextInputProps>({
component: Input, component: TextInput,
name: "Input", name: "TextInput",
props: { props: {
label: "Test input", label: "Test text input",
} as InputProps, } as TextInputProps,
requiredProps: ["label"], requiredProps: ["label"],
optionalProps: { optionalProps: {
placeholder: "Enter value", placeholder: "Enter value",