App reorganization

This commit is contained in:
adilallo
2026-04-18 14:12:49 -06:00
parent f866d11ff8
commit e9dab04b34
288 changed files with 2698 additions and 5029 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
import React, { useState } from "react";
import ContextMenu from "../../app/components/ContextMenu/ContextMenu";
import ContextMenuItem from "../../app/components/ContextMenu/ContextMenuItem";
import ContextMenuSection from "../../app/components/ContextMenu/ContextMenuSection";
import ContextMenuDivider from "../../app/components/ContextMenu/ContextMenuDivider";
import ContextMenu from "../../app/components/modals/ContextMenu/ContextMenu";
import ContextMenuItem from "../../app/components/modals/ContextMenuItem";
import ContextMenuSection from "../../app/components/modals/ContextMenu/ContextMenuSection";
import ContextMenuDivider from "../../app/components/modals/ContextMenu/ContextMenuDivider";
export default {
title: "Components/ContextMenu/ContextMenu",
+1 -1
View File
@@ -1,4 +1,4 @@
import WebVitalsDashboard from "../app/components/WebVitalsDashboard";
import WebVitalsDashboard from "../app/components/sections/WebVitalsDashboard";
export default {
title: "Components/WebVitalsDashboard",
+42
View File
@@ -0,0 +1,42 @@
import { Icon } from "../../app/components/asset";
export default {
title: "Components/Asset/Icon",
component: Icon,
parameters: {
layout: "centered",
},
argTypes: {
name: {
control: "select",
options: ["exclamation"],
description: "Name of the icon to render",
},
size: {
control: { type: "number", min: 12, max: 96, step: 4 },
description: "Width and height in pixels",
},
className: {
control: "text",
description: "Optional className applied to the SVG",
},
"aria-hidden": {
control: "boolean",
description: "Whether to mark the icon as aria-hidden",
},
},
};
export const Default = {
args: {
name: "exclamation",
size: 24,
},
};
export const Large = {
args: {
name: "exclamation",
size: 48,
},
};
+4 -4
View File
@@ -56,7 +56,7 @@ export const Small = {
args: {
number: 1,
text: "Document how your community makes decisions",
size: "Small",
size: "small",
iconShape: "blob",
iconColor: "green",
},
@@ -74,7 +74,7 @@ export const Medium = {
args: {
number: 1,
text: "Document how your community makes decisions",
size: "Medium",
size: "medium",
iconShape: "blob",
iconColor: "green",
},
@@ -92,7 +92,7 @@ export const Large = {
args: {
number: 1,
text: "Document how your community makes decisions",
size: "Large",
size: "large",
iconShape: "blob",
iconColor: "green",
},
@@ -110,7 +110,7 @@ export const XLarge = {
args: {
number: 1,
text: "Document how your community makes decisions",
size: "XLarge",
size: "xlarge",
iconShape: "blob",
iconColor: "green",
},
+26 -26
View File
@@ -82,11 +82,11 @@ export const Expanded = {
{
name: "Values",
chipOptions: [
{ id: "values-1", label: "Consciousness", state: "Unselected" },
{ id: "values-2", label: "Ecology", state: "Unselected" },
{ id: "values-3", label: "Abundance", state: "Unselected" },
{ id: "values-4", label: "Art", state: "Unselected" },
{ id: "values-5", label: "Decisiveness", state: "Unselected" },
{ id: "values-1", label: "Consciousness", state: "unselected" },
{ id: "values-2", label: "Ecology", state: "unselected" },
{ id: "values-3", label: "Abundance", state: "unselected" },
{ id: "values-4", label: "Art", state: "unselected" },
{ id: "values-5", label: "Decisiveness", state: "unselected" },
],
onChipClick: (categoryName, chipId) => {
console.log(`Chip clicked: ${categoryName} - ${chipId}`);
@@ -97,7 +97,7 @@ export const Expanded = {
},
{
name: "Communication",
chipOptions: [{ id: "comm-1", label: "Signal", state: "Unselected" }],
chipOptions: [{ id: "comm-1", label: "Signal", state: "unselected" }],
onChipClick: (categoryName, chipId) => {
console.log(`Chip clicked: ${categoryName} - ${chipId}`);
},
@@ -108,7 +108,7 @@ export const Expanded = {
{
name: "Membership",
chipOptions: [
{ id: "membership-1", label: "Open Admission", state: "Unselected" },
{ id: "membership-1", label: "Open Admission", state: "unselected" },
],
onChipClick: (categoryName, chipId) => {
console.log(`Chip clicked: ${categoryName} - ${chipId}`);
@@ -120,11 +120,11 @@ export const Expanded = {
{
name: "Decision-making",
chipOptions: [
{ id: "decision-1", label: "Lazy Consensus", state: "Unselected" },
{ id: "decision-1", label: "Lazy Consensus", state: "unselected" },
{
id: "decision-2",
label: "Modified Consensus",
state: "Unselected",
state: "unselected",
},
],
onChipClick: (categoryName, chipId) => {
@@ -137,11 +137,11 @@ export const Expanded = {
{
name: "Conflict management",
chipOptions: [
{ id: "conflict-1", label: "Code of Conduct", state: "Unselected" },
{ id: "conflict-1", label: "Code of Conduct", state: "unselected" },
{
id: "conflict-2",
label: "Restorative Justice",
state: "Unselected",
state: "unselected",
},
],
onChipClick: (categoryName, chipId) => {
@@ -246,42 +246,42 @@ export const ExpandedMedium = {
{
name: "Values",
chipOptions: [
{ id: "values-1", label: "Consciousness", state: "Unselected" },
{ id: "values-2", label: "Ecology", state: "Unselected" },
{ id: "values-3", label: "Abundance", state: "Unselected" },
{ id: "values-4", label: "Art", state: "Unselected" },
{ id: "values-5", label: "Decisiveness", state: "Unselected" },
{ id: "values-1", label: "Consciousness", state: "unselected" },
{ id: "values-2", label: "Ecology", state: "unselected" },
{ id: "values-3", label: "Abundance", state: "unselected" },
{ id: "values-4", label: "Art", state: "unselected" },
{ id: "values-5", label: "Decisiveness", state: "unselected" },
],
},
{
name: "Communication",
chipOptions: [{ id: "comm-1", label: "Signal", state: "Unselected" }],
chipOptions: [{ id: "comm-1", label: "Signal", state: "unselected" }],
},
{
name: "Membership",
chipOptions: [
{ id: "membership-1", label: "Open Admission", state: "Unselected" },
{ id: "membership-1", label: "Open Admission", state: "unselected" },
],
},
{
name: "Decision-making",
chipOptions: [
{ id: "decision-1", label: "Lazy Consensus", state: "Unselected" },
{ id: "decision-1", label: "Lazy Consensus", state: "unselected" },
{
id: "decision-2",
label: "Modified Consensus",
state: "Unselected",
state: "unselected",
},
],
},
{
name: "Conflict management",
chipOptions: [
{ id: "conflict-1", label: "Code of Conduct", state: "Unselected" },
{ id: "conflict-1", label: "Code of Conduct", state: "unselected" },
{
id: "conflict-2",
label: "Restorative Justice",
state: "Unselected",
state: "unselected",
},
],
},
@@ -394,9 +394,9 @@ export const InteractiveStates = {
{
name: "Values",
chipOptions: [
{ id: "values-1", label: "Consciousness", state: "Unselected" },
{ id: "values-2", label: "Ecology", state: "Unselected" },
{ id: "values-3", label: "Abundance", state: "Unselected" },
{ id: "values-1", label: "Consciousness", state: "unselected" },
{ id: "values-2", label: "Ecology", state: "unselected" },
{ id: "values-3", label: "Abundance", state: "unselected" },
],
onChipClick: (categoryName, chipId) => {
console.log(`Chip clicked: ${categoryName} - ${chipId}`);
@@ -407,7 +407,7 @@ export const InteractiveStates = {
},
{
name: "Communication",
chipOptions: [{ id: "comm-1", label: "Signal", state: "Unselected" }],
chipOptions: [{ id: "comm-1", label: "Signal", state: "unselected" }],
onChipClick: (categoryName, chipId) => {
console.log(`Chip clicked: ${categoryName} - ${chipId}`);
},
@@ -0,0 +1,66 @@
import { TemplateReviewCard } from "../../app/components/cards/TemplateReviewCard";
const sampleTemplate = {
id: "tmpl-1",
slug: "consensus",
title: "Consensus",
category: null,
description:
"Important decisions require unanimous agreement. Proposals pass only if no serious objections remain.",
body: {
sections: [
{
categoryName: "Decision making",
entries: [
{ title: "How proposals pass", body: "Unanimous agreement is required." },
{ title: "Blocks", body: "Anyone with a serious objection may block." },
],
},
{
categoryName: "Membership",
entries: [
{ title: "Joining", body: "New members are welcomed by consensus." },
],
},
],
},
sortOrder: 1,
featured: true,
};
export default {
title: "Components/Cards/TemplateReviewCard",
component: TemplateReviewCard,
parameters: {
layout: "centered",
},
argTypes: {
template: {
control: false,
description: "RuleTemplateDto used to populate the card",
},
size: {
control: "select",
options: ["XS", "S", "M", "L"],
description: "RuleCard size variant",
},
ruleCardClassName: {
control: "text",
description: "Class names merged onto the inner RuleCard",
},
},
};
export const Default = {
args: {
template: sampleTemplate,
size: "L",
},
};
export const Medium = {
args: {
template: sampleTemplate,
size: "M",
},
};
+9 -11
View File
@@ -46,15 +46,13 @@ export default {
},
mode: {
control: "select",
options: ["standard", "inverse", "Standard", "Inverse"],
description:
"Visual mode of the checkbox (case-insensitive: accepts both lowercase and PascalCase)",
options: ["standard", "inverse"],
description: "Visual mode of the checkbox",
},
state: {
control: "select",
options: ["default", "hover", "focus", "Default", "Hover", "Focus"],
description:
"Interaction state for static display (case-insensitive: accepts both lowercase and PascalCase)",
options: ["default", "hover", "focus"],
description: "Interaction state for static display",
},
disabled: {
control: "boolean",
@@ -228,15 +226,15 @@ export const FigmaPascalCase = () => {
<Checkbox
label="Standard Mode (PascalCase)"
checked={standardChecked}
mode="Standard"
state="Default"
mode="standard"
state="default"
onChange={({ checked }) => setStandardChecked(checked)}
/>
<Checkbox
label="Inverse Mode (PascalCase)"
checked={inverseChecked}
mode="Inverse"
state="Default"
mode="inverse"
state="default"
onChange={({ checked }) => setInverseChecked(checked)}
/>
</div>
@@ -256,7 +254,7 @@ export const FigmaPascalCase = () => {
label="Inverse Mode (mixed) - still works"
checked={false}
mode="inverse"
state="Default"
state="default"
/>
</div>
</div>
+83
View File
@@ -0,0 +1,83 @@
import Chip from "../../app/components/controls/Chip";
export default {
title: "Components/Controls/Chip",
component: Chip,
parameters: {
layout: "centered",
},
argTypes: {
label: {
control: "text",
description: "Text displayed inside the chip",
},
state: {
control: "select",
options: ["unselected", "selected", "disabled", "custom"],
description: "Visual state of the chip",
},
palette: {
control: "select",
options: ["default", "inverse"],
description: "Color palette of the chip",
},
size: {
control: "select",
options: ["s", "m"],
description: "Size of the chip",
},
disabled: {
control: "boolean",
description: "Override the disabled behaviour independently of state",
},
onClick: { action: "clicked" },
onRemove: { action: "removed" },
onCheck: { action: "checked" },
onClose: { action: "closed" },
},
};
export const Default = {
args: {
label: "Worker cooperative",
state: "unselected",
palette: "default",
size: "m",
},
};
export const Selected = {
args: {
label: "Worker cooperative",
state: "selected",
palette: "default",
size: "m",
},
};
export const Disabled = {
args: {
label: "Worker cooperative",
state: "disabled",
palette: "default",
size: "m",
},
};
export const Inverse = {
args: {
label: "Worker cooperative",
state: "selected",
palette: "inverse",
size: "m",
},
};
export const Small = {
args: {
label: "Worker cooperative",
state: "unselected",
palette: "default",
size: "s",
},
};
@@ -0,0 +1,82 @@
import React from "react";
import InputWithCounter from "../../app/components/controls/InputWithCounter";
export default {
title: "Components/Controls/InputWithCounter",
component: InputWithCounter,
parameters: {
layout: "centered",
},
argTypes: {
label: {
control: "text",
description: "Label rendered above the input",
},
placeholder: {
control: "text",
description: "Placeholder text shown when value is empty",
},
value: {
control: "text",
description: "Current value of the input (controlled)",
},
maxLength: {
control: { type: "number", min: 1, max: 500, step: 1 },
description: "Maximum number of characters allowed",
},
showHelpIcon: {
control: "boolean",
description: "Whether to show the help icon next to the label",
},
onChange: { action: "changed" },
},
};
const Template = (args) => {
const [value, setValue] = React.useState(args.value ?? "");
return (
<div style={{ width: 320 }}>
<InputWithCounter
{...args}
value={value}
onChange={(next) => {
setValue(next);
args.onChange?.(next);
}}
/>
</div>
);
};
export const Default = {
render: Template,
args: {
label: "Community name",
placeholder: "Enter a name",
value: "",
maxLength: 50,
showHelpIcon: false,
},
};
export const WithHelpIcon = {
render: Template,
args: {
label: "Community name",
placeholder: "Enter a name",
value: "",
maxLength: 50,
showHelpIcon: true,
},
};
export const WithInitialValue = {
render: Template,
args: {
label: "Community name",
placeholder: "Enter a name",
value: "My community",
maxLength: 30,
showHelpIcon: false,
},
};
+85
View File
@@ -0,0 +1,85 @@
import React from "react";
import MultiSelect from "../../app/components/controls/MultiSelect";
export default {
title: "Components/Controls/MultiSelect",
component: MultiSelect,
parameters: {
layout: "centered",
},
argTypes: {
label: {
control: "text",
description: "Label displayed above the chip set",
},
showHelpIcon: {
control: "boolean",
description: "Whether to show the help icon next to the label",
},
size: {
control: "select",
options: ["s", "m"],
description: "Size variant of the chips",
},
palette: {
control: "select",
options: ["default", "inverse"],
description: "Color palette applied to the chips",
},
addButton: {
control: "boolean",
description: "Whether to show the add button",
},
addButtonText: {
control: "text",
description: "Text rendered on the add button",
},
formHeader: {
control: "boolean",
description: "Whether to show the label/help-icon header",
},
onChipClick: { action: "chip-clicked" },
onAddClick: { action: "add-clicked" },
},
};
const defaultOptions = [
{ id: "1", label: "Worker cooperative", state: "unselected" },
{ id: "2", label: "Consumer cooperative", state: "selected" },
{ id: "3", label: "Housing cooperative", state: "unselected" },
{ id: "4", label: "Producer cooperative", state: "unselected" },
];
export const Default = {
args: {
label: "Organization type",
showHelpIcon: true,
size: "m",
palette: "default",
options: defaultOptions,
addButton: true,
addButtonText: "Add organization type",
formHeader: true,
},
};
export const Small = {
args: {
...Default.args,
size: "s",
},
};
export const Inverse = {
args: {
...Default.args,
palette: "inverse",
},
};
export const NoAddButton = {
args: {
...Default.args,
addButton: false,
},
};
+9 -20
View File
@@ -21,24 +21,13 @@ export default {
},
mode: {
control: "select",
options: ["standard", "inverse", "Standard", "Inverse"],
description:
"Visual mode of the radio button (case-insensitive: accepts both lowercase and PascalCase)",
options: ["standard", "inverse"],
description: "Visual mode of the radio button",
},
state: {
control: "select",
options: [
"default",
"hover",
"focus",
"selected",
"Default",
"Hover",
"Focus",
"Selected",
],
description:
"Interaction state for static display (case-insensitive: accepts both lowercase and PascalCase)",
options: ["default", "hover", "focus", "selected"],
description: "Interaction state for static display",
},
disabled: {
control: "boolean",
@@ -286,15 +275,15 @@ export const FigmaPascalCase = () => {
<RadioButton
label="Standard Mode (PascalCase)"
checked={standardChecked}
mode="Standard"
state="Default"
mode="standard"
state="default"
onChange={({ checked }) => setStandardChecked(checked)}
/>
<RadioButton
label="Inverse Mode (PascalCase)"
checked={inverseChecked}
mode="Inverse"
state="Default"
mode="inverse"
state="default"
onChange={({ checked }) => setInverseChecked(checked)}
/>
</div>
@@ -314,7 +303,7 @@ export const FigmaPascalCase = () => {
label="Inverse Mode (mixed) - still works"
checked={false}
mode="inverse"
state="Default"
state="default"
/>
</div>
</div>
@@ -1,5 +1,5 @@
import React from "react";
import ApplicableScopeField from "../../app/create/components/ApplicableScopeField";
import ApplicableScopeField from "../../app/(app)/create/components/ApplicableScopeField";
export default {
title: "Create Flow/ApplicableScopeField",
@@ -1,5 +1,5 @@
import React from "react";
import ModalTextAreaField from "../../app/create/components/ModalTextAreaField";
import ModalTextAreaField from "../../app/(app)/create/components/ModalTextAreaField";
export default {
title: "Create Flow/ModalTextAreaField",
@@ -0,0 +1,19 @@
import LanguageSwitcher from "../../app/components/localization/LanguageSwitcher";
export default {
title: "Components/Localization/LanguageSwitcher",
component: LanguageSwitcher,
parameters: {
layout: "centered",
},
argTypes: {
className: {
control: "text",
description: "Optional wrapper className",
},
},
};
export const Default = {
args: {},
};
+1 -1
View File
@@ -121,7 +121,7 @@ export const HeaderOverlayBlurred = {
),
};
/** Matches `app/login/page.tsx`: dedicated route, solid yellow, no portal. */
/** Matches `app/(app)/login/page.tsx`: dedicated route, solid yellow, no portal. */
export const FullPageRouteSolid = {
name: "Full-page route (/login — solid)",
parameters: {
@@ -0,0 +1,75 @@
import NavigationItem from "../../app/components/navigation/NavigationItem";
export default {
title: "Components/Navigation/NavigationItem",
component: NavigationItem,
parameters: {
layout: "centered",
},
argTypes: {
href: {
control: "text",
description: "Anchor href",
},
variant: {
control: "select",
options: ["default"],
description: "Visual variant",
},
size: {
control: "select",
options: ["default", "xsmall"],
description: "Size variant",
},
disabled: {
control: "boolean",
description: "Disable interaction (renders as span)",
},
isActive: {
control: "boolean",
description: "Mark the item as currently active",
},
children: {
control: "text",
description: "Item label",
},
},
};
export const Default = {
args: {
children: "Templates",
href: "#",
variant: "default",
size: "default",
},
};
export const Active = {
args: {
children: "Templates",
href: "#",
variant: "default",
size: "default",
isActive: true,
},
};
export const Disabled = {
args: {
children: "Templates",
href: "#",
variant: "default",
size: "default",
disabled: true,
},
};
export const XSmall = {
args: {
children: "Templates",
href: "#",
variant: "default",
size: "xsmall",
},
};
@@ -1,4 +1,4 @@
import { CommunicationMethodsScreen } from "../../app/create/screens/card/CommunicationMethodsScreen";
import { CommunicationMethodsScreen } from "../../app/(app)/create/screens/card/CommunicationMethodsScreen";
export default {
title: "Pages/Create Flow/Communication methods",
+1 -1
View File
@@ -1,4 +1,4 @@
import { CompletedScreen } from "../../app/create/screens/completed/CompletedScreen";
import { CompletedScreen } from "../../app/(app)/create/screens/completed/CompletedScreen";
export default {
title: "Pages/Create Flow/Completed",
@@ -1,4 +1,4 @@
import { ConfirmStakeholdersScreen } from "../../app/create/screens/select/ConfirmStakeholdersScreen";
import { ConfirmStakeholdersScreen } from "../../app/(app)/create/screens/select/ConfirmStakeholdersScreen";
export default {
title: "Pages/Create Flow/Confirm stakeholders",
@@ -1,4 +1,4 @@
import { DecisionApproachesScreen } from "../../app/create/screens/right-rail/DecisionApproachesScreen";
import { DecisionApproachesScreen } from "../../app/(app)/create/screens/right-rail/DecisionApproachesScreen";
export default {
title: "Pages/Create Flow/Decision approaches",
+1 -1
View File
@@ -1,4 +1,4 @@
import { FinalReviewScreen } from "../../app/create/screens/review/FinalReviewScreen";
import { FinalReviewScreen } from "../../app/(app)/create/screens/review/FinalReviewScreen";
export default {
title: "Pages/Create Flow/Final review",
+1 -1
View File
@@ -1,4 +1,4 @@
import { InformationalScreen } from "../../app/create/screens/informational/InformationalScreen";
import { InformationalScreen } from "../../app/(app)/create/screens/informational/InformationalScreen";
export default {
title: "Pages/Create/Informational",
+1 -1
View File
@@ -1,4 +1,4 @@
import { CommunityReviewScreen } from "../../app/create/screens/review/CommunityReviewScreen";
import { CommunityReviewScreen } from "../../app/(app)/create/screens/review/CommunityReviewScreen";
export default {
title: "Pages/Create/Review",
+1 -1
View File
@@ -1,4 +1,4 @@
import { CommunitySizeSelectScreen } from "../../app/create/screens/select/CommunitySizeSelectScreen";
import { CommunitySizeSelectScreen } from "../../app/(app)/create/screens/select/CommunitySizeSelectScreen";
export default {
title: "Pages/Create/CommunitySize",
+1 -1
View File
@@ -1,4 +1,4 @@
import { CreateFlowTextFieldScreen } from "../../app/create/screens/text/CreateFlowTextFieldScreen";
import { CreateFlowTextFieldScreen } from "../../app/(app)/create/screens/text/CreateFlowTextFieldScreen";
export default {
title: "Pages/Create/CommunityName",
+1 -1
View File
@@ -1,4 +1,4 @@
import { CommunityUploadScreen } from "../../app/create/screens/upload/CommunityUploadScreen";
import { CommunityUploadScreen } from "../../app/(app)/create/screens/upload/CommunityUploadScreen";
export default {
title: "Pages/Create/CommunityUpload",
+1 -1
View File
@@ -15,7 +15,7 @@ export default {
argTypes: {
variant: {
control: { type: "select" },
options: ["default", "segmented", "Default", "Segmented"],
options: ["default", "segmented"],
description:
"Segmented: pill-shaped partial fills (create-flow footer / Figma).",
},
@@ -0,0 +1,58 @@
import CommunityRuleDocument from "../../app/components/sections/CommunityRuleDocument";
const sampleSections = [
{
categoryName: "Decision making",
entries: [
{
title: "How proposals pass",
body: "Important decisions require unanimous agreement. Proposals pass only if no serious objections remain.",
},
{
title: "Blocks",
body: "Anyone with a serious objection may block consensus and require further discussion.",
},
],
},
{
categoryName: "Membership",
entries: [
{
title: "Joining",
body: "New members are welcomed by consensus of existing members.",
},
],
},
];
export default {
title: "Components/Sections/CommunityRuleDocument",
component: CommunityRuleDocument,
parameters: {
layout: "padded",
},
argTypes: {
sections: {
control: false,
description: "Document sections, each with a categoryName and entries.",
},
useCardStyle: {
control: "boolean",
description: "When true, wraps the document in a white card with a teal bar",
},
},
};
export const Default = {
args: {
sections: sampleSections,
useCardStyle: false,
},
};
export const CardStyle = {
args: {
sections: sampleSections,
useCardStyle: true,
},
};
@@ -0,0 +1,29 @@
import { GovernanceTemplateGrid } from "../../app/components/sections/GovernanceTemplateGrid";
import { GOVERNANCE_TEMPLATE_CATALOG } from "../../lib/templates/governanceTemplateCatalog";
export default {
title: "Components/Sections/GovernanceTemplateGrid",
component: GovernanceTemplateGrid,
parameters: {
layout: "fullscreen",
},
argTypes: {
entries: {
control: false,
description: "Catalog entries to render as a 2-column grid of RuleCards",
},
onTemplateClick: { action: "template-clicked" },
},
};
export const Default = {
args: {
entries: GOVERNANCE_TEMPLATE_CATALOG.slice(0, 4),
},
};
export const SingleEntry = {
args: {
entries: GOVERNANCE_TEMPLATE_CATALOG.slice(0, 1),
},
};
-40
View File
@@ -1,40 +0,0 @@
import ErrorBoundary from "../../app/components/utility/ErrorBoundary";
export default {
title: "Components/Utility/ErrorBoundary",
component: ErrorBoundary,
parameters: {
layout: "centered",
docs: {
description: {
component:
"An error boundary component that catches JavaScript errors in its child component tree. Displays a fallback UI when errors occur and logs error information for debugging.",
},
},
},
argTypes: {
children: {
control: { type: "text" },
description: "Child components to wrap with error boundary",
},
},
};
export const Default = {
args: {
children: <div>Normal content</div>,
},
};
export const WithError = {
render: () => {
const ThrowError = () => {
throw new Error("Test error for ErrorBoundary");
};
return (
<ErrorBoundary>
<ThrowError />
</ErrorBoundary>
);
},
};
+71
View File
@@ -0,0 +1,71 @@
import ModalFooter from "../../app/components/utility/ModalFooter";
export default {
title: "Components/Utility/ModalFooter",
component: ModalFooter,
parameters: {
layout: "fullscreen",
},
argTypes: {
showBackButton: {
control: "boolean",
description: "Whether to render the back button on the left",
},
showNextButton: {
control: "boolean",
description: "Whether to render the next button on the right",
},
backButtonText: {
control: "text",
description: "Override text for the back button",
},
nextButtonText: {
control: "text",
description: "Override text for the next button",
},
nextButtonDisabled: {
control: "boolean",
description: "Whether the next button is disabled",
},
currentStep: {
control: { type: "number", min: 1, max: 10, step: 1 },
description: "Current step (used by the centered Stepper)",
},
totalSteps: {
control: { type: "number", min: 1, max: 10, step: 1 },
description: "Total number of steps",
},
stepper: {
control: "boolean",
description: "Whether to render the centered stepper",
},
onBack: { action: "back-clicked" },
onNext: { action: "next-clicked" },
},
};
export const Default = {
args: {
showBackButton: true,
showNextButton: true,
currentStep: 2,
totalSteps: 4,
},
};
export const NextDisabled = {
args: {
showBackButton: true,
showNextButton: true,
nextButtonDisabled: true,
currentStep: 1,
totalSteps: 4,
},
};
export const NextOnly = {
args: {
showBackButton: false,
showNextButton: true,
},
};
+42
View File
@@ -0,0 +1,42 @@
import ModalHeader from "../../app/components/utility/ModalHeader";
export default {
title: "Components/Utility/ModalHeader",
component: ModalHeader,
parameters: {
layout: "fullscreen",
},
argTypes: {
showCloseButton: {
control: "boolean",
description: "Whether to render the close button on the left",
},
showMoreOptionsButton: {
control: "boolean",
description: "Whether to render the more-options button on the right",
},
onClose: { action: "close-clicked" },
onMoreOptions: { action: "more-options-clicked" },
},
};
export const Default = {
args: {
showCloseButton: true,
showMoreOptionsButton: true,
},
};
export const CloseOnly = {
args: {
showCloseButton: true,
showMoreOptionsButton: false,
},
};
export const MoreOptionsOnly = {
args: {
showCloseButton: false,
showMoreOptionsButton: true,
},
};
+28
View File
@@ -0,0 +1,28 @@
import Separator from "../../app/components/utility/Separator";
export default {
title: "Components/Utility/Separator",
component: Separator,
parameters: {
layout: "padded",
},
argTypes: {},
};
export const Default = {
render: () => (
<div style={{ width: 320 }}>
<Separator />
</div>
),
};
export const InContext = {
render: () => (
<div style={{ width: 320, display: "flex", flexDirection: "column", gap: 12 }}>
<p>Above the separator</p>
<Separator />
<p>Below the separator</p>
</div>
),
};