Component cleanup
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import WebVitalsDashboard from "../../components/sections/WebVitalsDashboard";
|
||||
import TopNav from "../../components/navigation/TopNav";
|
||||
import WebVitalsDashboard from "./_components/WebVitalsDashboard";
|
||||
import Top from "../../components/navigation/Top";
|
||||
import Footer from "../../components/navigation/Footer";
|
||||
import { useMessages } from "../../contexts/MessagesContext";
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function MonitorPageContent() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--color-surface-default-primary)]">
|
||||
<TopNav folderTop={false} />
|
||||
<Top folderTop={false} />
|
||||
|
||||
<main className="container mx-auto px-[var(--spacing-scale-024)] py-[var(--spacing-scale-032)]">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
|
||||
+2
-3
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { useMessages } from "../../../contexts/MessagesContext";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import { useMessages } from "../../../../contexts/MessagesContext";
|
||||
import { logger } from "../../../../../lib/logger";
|
||||
import WebVitalsDashboardView from "./WebVitalsDashboard.view";
|
||||
import type { Metrics, Vitals, VitalData } from "./WebVitalsDashboard.types";
|
||||
|
||||
@@ -78,7 +78,6 @@ const WebVitalsDashboardContainer = memo(() => {
|
||||
fetchVitals();
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
// web-vitals v4+ exposes onLCP / onCLS / … — legacy getLCP was removed.
|
||||
import("web-vitals").then(
|
||||
({ onCLS, onFID, onFCP, onLCP, onTTFB }) => {
|
||||
onLCP((metric) => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type messages from "../../../../messages/en/index";
|
||||
import type messages from "../../../../../messages/en/index";
|
||||
|
||||
export interface VitalData {
|
||||
value: number;
|
||||
@@ -13,14 +13,14 @@ import { useCreateFlowNavigation } from "./hooks/useCreateFlowNavigation";
|
||||
import { useCreateFlowExit } from "./hooks/useCreateFlowExit";
|
||||
import { useCreateFlowFinalize } from "./hooks/useCreateFlowFinalize";
|
||||
import { useTemplateReviewActions } from "./hooks/useTemplateReviewActions";
|
||||
import CreateFlowTopNav from "../../components/utility/CreateFlowTopNav";
|
||||
import CreateFlowFooter from "../../components/navigation/CreateFlowFooter";
|
||||
import CreateFlowTopNav from "../../components/navigation/CreateFlowTopNav";
|
||||
import { getNextStep, getStepIndex } from "./utils/flowSteps";
|
||||
import { getProportionBarProgressForCreateFlowStep } from "./utils/createFlowProportionProgress";
|
||||
import {
|
||||
createFlowStepUsesCenteredTextLayout,
|
||||
createFlowStepUsesCardLayout,
|
||||
} from "./utils/createFlowScreenRegistry";
|
||||
import CreateFlowFooter from "../../components/utility/CreateFlowFooter";
|
||||
import Button from "../../components/buttons/Button";
|
||||
import { isValidCreateFlowSaveEmail } from "../../../lib/create/isValidCreateFlowSaveEmail";
|
||||
import {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { memo, useState } from "react";
|
||||
import Chip from "../../../components/controls/Chip";
|
||||
import InputLabel from "../../../components/utility/InputLabel";
|
||||
import InputLabel from "../../../components/type/InputLabel";
|
||||
|
||||
export interface ApplicableScopeFieldProps {
|
||||
/** Label rendered above the capsule row. */
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS,
|
||||
} from "./createFlowLayoutTokens";
|
||||
|
||||
/** Shared `RuleCard` / template card chrome: width + radius; padding comes from `RuleCard` (L+expanded = 24px). */
|
||||
export const CREATE_FLOW_REVIEW_RULE_CARD_LAYOUT_CLASS =
|
||||
/** Shared `Rule` / template card chrome: width + radius; padding comes from `Rule` (L+expanded = 24px). */
|
||||
export const CREATE_FLOW_REVIEW_RULE_LAYOUT_CLASS =
|
||||
"w-full min-w-0 rounded-[12px] md:rounded-[24px] md:max-w-[640px]";
|
||||
|
||||
type CreateFlowLockupCardStepShellProps = {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { memo, useId } from "react";
|
||||
import TextArea from "../../../components/controls/TextArea";
|
||||
import InputLabel from "../../../components/utility/InputLabel";
|
||||
import InputLabel from "../../../components/type/InputLabel";
|
||||
|
||||
export interface ModalTextAreaFieldProps {
|
||||
/** Label rendered above the text area. */
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import messages from "../../../../../messages/en/index";
|
||||
import Alert from "../../../../components/modals/Alert";
|
||||
import {
|
||||
CREATE_FLOW_REVIEW_RULE_CARD_LAYOUT_CLASS,
|
||||
CREATE_FLOW_REVIEW_RULE_LAYOUT_CLASS,
|
||||
CreateFlowLockupCardStepShell,
|
||||
} from "../../components/CreateFlowLockupCardStepShell";
|
||||
import { CreateFlowStepShell } from "../../components/CreateFlowStepShell";
|
||||
@@ -118,7 +118,7 @@ export default function ReviewTemplatePage({ params }: PageProps) {
|
||||
>
|
||||
<TemplateReviewCard
|
||||
template={template}
|
||||
ruleCardClassName={CREATE_FLOW_REVIEW_RULE_CARD_LAYOUT_CLASS}
|
||||
ruleCardClassName={CREATE_FLOW_REVIEW_RULE_LAYOUT_CLASS}
|
||||
size={mdUp ? "L" : "M"}
|
||||
/>
|
||||
</CreateFlowLockupCardStepShell>
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
useFacetRecommendations,
|
||||
} from "../../hooks/useFacetRecommendations";
|
||||
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
|
||||
import CardStack from "../../../../components/utility/CardStack";
|
||||
import CardStack from "../../../../components/cards/CardStack";
|
||||
import Create from "../../../../components/modals/Create";
|
||||
import InlineTextButton from "../../../../components/buttons/InlineTextButton";
|
||||
import { CreateFlowStepShell } from "../../components/CreateFlowStepShell";
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
useFacetRecommendations,
|
||||
} from "../../hooks/useFacetRecommendations";
|
||||
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
|
||||
import CardStack from "../../../../components/utility/CardStack";
|
||||
import CardStack from "../../../../components/cards/CardStack";
|
||||
import Create from "../../../../components/modals/Create";
|
||||
import InlineTextButton from "../../../../components/buttons/InlineTextButton";
|
||||
import { CreateFlowStepShell } from "../../components/CreateFlowStepShell";
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
useFacetRecommendations,
|
||||
} from "../../hooks/useFacetRecommendations";
|
||||
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
|
||||
import CardStack from "../../../../components/utility/CardStack";
|
||||
import CardStack from "../../../../components/cards/CardStack";
|
||||
import Create from "../../../../components/modals/Create";
|
||||
import InlineTextButton from "../../../../components/buttons/InlineTextButton";
|
||||
import { CreateFlowStepShell } from "../../components/CreateFlowStepShell";
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import CommunityRuleDocument from "../../../../components/sections/CommunityRuleDocument";
|
||||
import type { CommunityRuleDocumentSection } from "../../../../components/sections/CommunityRuleDocument/CommunityRuleDocument.types";
|
||||
import CommunityRule from "../../../../components/type/CommunityRule";
|
||||
import type { CommunityRuleSection } from "../../../../components/type/CommunityRule/CommunityRule.types";
|
||||
import Alert from "../../../../components/modals/Alert";
|
||||
import { useMessages } from "../../../../contexts/MessagesContext";
|
||||
import { fetchPublishedRuleDetail } from "../../../../../lib/create/api";
|
||||
@@ -24,7 +24,7 @@ function initialCompletedUi(
|
||||
): {
|
||||
headerTitle: string;
|
||||
headerDescription: string | undefined;
|
||||
documentSections: CommunityRuleDocumentSection[];
|
||||
documentSections: CommunityRuleSection[];
|
||||
} {
|
||||
if (ruleIdFromUrl) {
|
||||
return {
|
||||
@@ -80,7 +80,7 @@ export function CompletedScreen() {
|
||||
string | undefined
|
||||
>(initial.headerDescription);
|
||||
const [documentSections, setDocumentSections] =
|
||||
useState<CommunityRuleDocumentSection[]>(initial.documentSections);
|
||||
useState<CommunityRuleSection[]>(initial.documentSections);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ruleIdParam) return;
|
||||
@@ -170,7 +170,7 @@ export function CompletedScreen() {
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="w-full min-w-0 py-0 md:pb-8">
|
||||
<CommunityRuleDocument
|
||||
<CommunityRule
|
||||
sections={documentSections}
|
||||
useCardStyle={!mdUp}
|
||||
className={mdUp ? "min-w-0" : "w-full min-w-0 p-4"}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import RuleCard from "../../../../components/cards/RuleCard";
|
||||
import Rule from "../../../../components/cards/Rule";
|
||||
import { useTranslation } from "../../../../contexts/MessagesContext";
|
||||
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
|
||||
import { useCreateFlow } from "../../context/CreateFlowContext";
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
CREATE_FLOW_MD_UP_GRID_CELL_CLASS,
|
||||
CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS,
|
||||
} from "../../components/createFlowLayoutTokens";
|
||||
import {
|
||||
getAssetPath,
|
||||
vectorMarkPath,
|
||||
} from "../../../../../lib/assetUtils";
|
||||
|
||||
/**
|
||||
* Targets for a `pendingTemplateAction` redirect. Customize resumes the
|
||||
@@ -90,13 +94,13 @@ export function CommunityReviewScreen() {
|
||||
/>
|
||||
</div>
|
||||
<div className={CREATE_FLOW_MD_UP_GRID_CELL_CLASS}>
|
||||
<RuleCard
|
||||
<Rule
|
||||
title={cardTitle}
|
||||
description={cardDescription}
|
||||
size={lgUp ? "L" : "M"}
|
||||
expanded={false}
|
||||
backgroundColor="bg-[var(--color-teal-teal50,#c9fef9)]"
|
||||
logoUrl="/assets/Vector_MutualAid.svg"
|
||||
logoUrl={getAssetPath(vectorMarkPath("mutual-aid"))}
|
||||
logoAlt={cardTitle}
|
||||
className="rounded-[24px]"
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import RuleCard from "../../../../components/cards/RuleCard";
|
||||
import type { Category } from "../../../../components/cards/RuleCard/RuleCard.types";
|
||||
import Rule, { type Category } from "../../../../components/cards/Rule";
|
||||
import { TemplateChipDetailModal } from "../../../../components/cards/TemplateReviewCard/TemplateChipDetailModal";
|
||||
import { useMessages, useTranslation } from "../../../../contexts/MessagesContext";
|
||||
import { useCreateFlow } from "../../context/CreateFlowContext";
|
||||
import { useCreateFlowMdUp } from "../../hooks/useCreateFlowMdUp";
|
||||
import {
|
||||
CREATE_FLOW_REVIEW_RULE_CARD_LAYOUT_CLASS,
|
||||
CREATE_FLOW_REVIEW_RULE_LAYOUT_CLASS,
|
||||
CreateFlowLockupCardStepShell,
|
||||
} from "../../components/CreateFlowLockupCardStepShell";
|
||||
import {
|
||||
@@ -22,12 +21,16 @@ import {
|
||||
type FinalReviewChipEditPatch,
|
||||
type FinalReviewChipEditTarget,
|
||||
} from "../../components/FinalReviewChipEditModal";
|
||||
import {
|
||||
getAssetPath,
|
||||
vectorMarkPath,
|
||||
} from "../../../../../lib/assetUtils";
|
||||
|
||||
/**
|
||||
* `finalReview.json.categories` ships a demo ordering + localized names
|
||||
* (Values / Communication / Membership / Decision-making / Conflict
|
||||
* management). We reuse that ordering for the state-derived rows so the
|
||||
* RuleCard layout stays stable across customize / use-without-changes /
|
||||
* Rule layout stays stable across customize / use-without-changes /
|
||||
* plain-custom flows, and fall back to the demo chips when state resolves
|
||||
* to nothing selected.
|
||||
*/
|
||||
@@ -183,16 +186,16 @@ export function FinalReviewScreen() {
|
||||
lockupTitle={t("title")}
|
||||
lockupDescription={t("description")}
|
||||
>
|
||||
<RuleCard
|
||||
<Rule
|
||||
title={ruleCardTitle}
|
||||
description={ruleCardDescription}
|
||||
size={mdUp ? "L" : "M"}
|
||||
expanded={true}
|
||||
backgroundColor="bg-[#c9fef9]"
|
||||
logoUrl="/assets/Vector_MutualAid.svg"
|
||||
logoUrl={getAssetPath(vectorMarkPath("mutual-aid"))}
|
||||
logoAlt={ruleCardTitle}
|
||||
categories={finalReviewCategories}
|
||||
className={CREATE_FLOW_REVIEW_RULE_CARD_LAYOUT_CLASS}
|
||||
className={CREATE_FLOW_REVIEW_RULE_LAYOUT_CLASS}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
<FinalReviewChipEditModal
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import DecisionMakingSidebar from "../../../../components/utility/DecisionMakingSidebar";
|
||||
import CardStack from "../../../../components/utility/CardStack";
|
||||
import CardStack from "../../../../components/cards/CardStack";
|
||||
import HeaderLockup from "../../../../components/type/HeaderLockup";
|
||||
import Create from "../../../../components/modals/Create";
|
||||
import InlineTextButton from "../../../../components/buttons/InlineTextButton";
|
||||
import type { InfoMessageBoxItem } from "../../../../components/utility/InfoMessageBox/InfoMessageBox.types";
|
||||
import type { CardStackItem } from "../../../../components/utility/CardStack/CardStack.types";
|
||||
import InfoMessageBox from "../../../../components/controls/InfoMessageBox";
|
||||
import type { InfoMessageBoxItem } from "../../../../components/controls/InfoMessageBox/InfoMessageBox.types";
|
||||
import type { CardStackItem } from "../../../../components/cards/CardStack/CardStack.types";
|
||||
import { useMessages } from "../../../../contexts/MessagesContext";
|
||||
import { useCreateFlow } from "../../context/CreateFlowContext";
|
||||
import { useCreateFlowMdUp } from "../../hooks/useCreateFlowMdUp";
|
||||
@@ -204,16 +205,20 @@ export function DecisionApproachesScreen() {
|
||||
contentTopBelowMd="space-800"
|
||||
lgVerticalAlign="start"
|
||||
header={
|
||||
<DecisionMakingSidebar
|
||||
title={da.sidebar.title}
|
||||
description={sidebarDescription}
|
||||
messageBoxTitle={da.messageBox.title}
|
||||
messageBoxItems={messageBoxItems}
|
||||
messageBoxCheckedIds={messageBoxCheckedIds}
|
||||
onMessageBoxCheckboxChange={handleMessageBoxCheckboxChange}
|
||||
size={mdUp ? "L" : "M"}
|
||||
justification={mdUp ? "left" : "center"}
|
||||
/>
|
||||
<div className="flex w-full min-w-0 flex-col gap-3">
|
||||
<HeaderLockup
|
||||
title={da.sidebar.title}
|
||||
description={sidebarDescription}
|
||||
size={mdUp ? "L" : "M"}
|
||||
justification={mdUp ? "left" : "center"}
|
||||
/>
|
||||
<InfoMessageBox
|
||||
title={da.messageBox.title}
|
||||
items={messageBoxItems}
|
||||
checkedIds={messageBoxCheckedIds}
|
||||
onCheckboxChange={handleMessageBoxCheckboxChange}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex w-full min-w-0 flex-col items-stretch gap-6 py-0">
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { useId, useMemo } from "react";
|
||||
import Button from "../../../components/buttons/Button";
|
||||
import RuleCard from "../../../components/cards/RuleCard";
|
||||
import Rule from "../../../components/cards/Rule";
|
||||
import TextInput from "../../../components/controls/TextInput";
|
||||
import List from "../../../components/layout/List";
|
||||
import type { ListItem, ListSize } from "../../../components/layout/List";
|
||||
import Icon from "../../../components/asset/Icon";
|
||||
import Icon from "../../../components/asset/icon";
|
||||
import Dialog from "../../../components/modals/Dialog";
|
||||
import Alert from "../../../components/modals/Alert";
|
||||
import HeaderLockup from "../../../components/type/HeaderLockup";
|
||||
@@ -82,10 +82,10 @@ const profileSectionHeadingClass =
|
||||
"font-bricolage text-base font-bold leading-[22px] text-[var(--color-content-default-primary)] md:font-inter md:text-xl md:font-bold md:leading-7 xl:font-bricolage-grotesque xl:font-bold xl:text-[28px] xl:leading-9";
|
||||
|
||||
/**
|
||||
* Sticky `top` for page content below the product {@link TopNav} (standard variant).
|
||||
* Must match `TopNav.view.tsx`: nav `h` 40px → `lg` 84px → `xl` 88px, plus `header` `border-b` (+1px).
|
||||
* Sticky `top` for page content below the product {@link Top} (standard variant).
|
||||
* Must match `Top.view.tsx`: nav `h` 40px → `lg` 84px → `xl` 88px, plus `header` `border-b` (+1px).
|
||||
*/
|
||||
const stickyBelowTopNavTopClass =
|
||||
const stickyBelowTopTopClass =
|
||||
"top-[41px] lg:top-[85px] xl:top-[89px]";
|
||||
|
||||
export type ProfilePageSignedOutViewProps = {
|
||||
@@ -111,7 +111,7 @@ export function ProfilePageSignedOutView({
|
||||
<header
|
||||
className={
|
||||
profileLgUp
|
||||
? `sticky z-10 bg-[var(--color-surface-default-primary)] ${stickyBelowTopNavTopClass}`
|
||||
? `sticky z-10 bg-[var(--color-surface-default-primary)] ${stickyBelowTopTopClass}`
|
||||
: `flex flex-col gap-1 py-3 md:sticky md:top-[41px] md:z-10 md:bg-[var(--color-surface-default-primary)]`
|
||||
}
|
||||
>
|
||||
@@ -304,7 +304,7 @@ export function ProfilePageView({
|
||||
</h2>
|
||||
<div className="flex flex-col gap-3">
|
||||
{showDraftCard && draft?.hasDraft ? (
|
||||
<RuleCard
|
||||
<Rule
|
||||
title={(() => {
|
||||
const raw = draft.state.title;
|
||||
const s = typeof raw === "string" ? raw.trim() : "";
|
||||
@@ -337,7 +337,7 @@ export function ProfilePageView({
|
||||
/>
|
||||
) : null}
|
||||
{rules.map((rule) => (
|
||||
<RuleCard
|
||||
<Rule
|
||||
key={rule.id}
|
||||
title={rule.title}
|
||||
description={rule.summary ?? undefined}
|
||||
|
||||
+19
-17
@@ -14,15 +14,12 @@ const LogoWall = dynamic(() => import("../components/sections/LogoWall"), {
|
||||
ssr: true,
|
||||
});
|
||||
|
||||
const NumberedCards = dynamic(
|
||||
() => import("../components/sections/NumberedCards"),
|
||||
{
|
||||
loading: () => (
|
||||
<section className="py-[var(--spacing-scale-032)] min-h-[300px]" />
|
||||
),
|
||||
ssr: true,
|
||||
},
|
||||
);
|
||||
const CardSteps = dynamic(() => import("../components/sections/CardSteps"), {
|
||||
loading: () => (
|
||||
<section className="py-[var(--spacing-scale-032)] min-h-[300px]" />
|
||||
),
|
||||
ssr: true,
|
||||
});
|
||||
|
||||
const FeatureGrid = dynamic(
|
||||
() => import("../components/sections/FeatureGrid"),
|
||||
@@ -54,22 +51,27 @@ export default function Page() {
|
||||
ctaHref: t("pages.home.heroBanner.ctaHref"),
|
||||
};
|
||||
|
||||
const numberedCardsData = {
|
||||
title: t("pages.home.numberedCards.title"),
|
||||
subtitle: t("pages.home.numberedCards.subtitle"),
|
||||
cards: [
|
||||
const cardStepsData = {
|
||||
title: t("pages.home.cardSteps.title"),
|
||||
subtitle: t("pages.home.cardSteps.subtitle"),
|
||||
headingDesktopLines: [
|
||||
t("pages.home.cardSteps.headingDesktopLine1"),
|
||||
t("pages.home.cardSteps.headingDesktopLine2"),
|
||||
t("pages.home.cardSteps.headingDesktopLine3"),
|
||||
] as const,
|
||||
steps: [
|
||||
{
|
||||
text: t("pages.home.numberedCards.cards.card1.text"),
|
||||
text: t("pages.home.cardSteps.cards.card1.text"),
|
||||
iconShape: "blob",
|
||||
iconColor: "green",
|
||||
},
|
||||
{
|
||||
text: t("pages.home.numberedCards.cards.card2.text"),
|
||||
text: t("pages.home.cardSteps.cards.card2.text"),
|
||||
iconShape: "gear",
|
||||
iconColor: "purple",
|
||||
},
|
||||
{
|
||||
text: t("pages.home.numberedCards.cards.card3.text"),
|
||||
text: t("pages.home.cardSteps.cards.card3.text"),
|
||||
iconShape: "star",
|
||||
iconColor: "orange",
|
||||
},
|
||||
@@ -92,7 +94,7 @@ export default function Page() {
|
||||
<div>
|
||||
<HeroBanner {...heroBannerData} />
|
||||
<LogoWall />
|
||||
<NumberedCards {...numberedCardsData} />
|
||||
<CardSteps {...cardStepsData} />
|
||||
<Suspense
|
||||
fallback={
|
||||
<section className="py-[var(--spacing-scale-032)] min-h-[400px]" />
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPublicPublishedRuleById } from "../../../../lib/server/publishedRules";
|
||||
import { parseDocumentSectionsForDisplay } from "../../../../lib/create/buildPublishPayload";
|
||||
import CommunityRuleDocument from "../../../components/sections/CommunityRuleDocument";
|
||||
import CommunityRule from "../../../components/type/CommunityRule";
|
||||
import HeaderLockup from "../../../components/type/HeaderLockup";
|
||||
|
||||
interface PageProps {
|
||||
@@ -65,7 +65,7 @@ export default async function PublicRuleDetailPage({ params }: PageProps) {
|
||||
size="L"
|
||||
palette="inverse"
|
||||
/>
|
||||
<CommunityRuleDocument sections={sections} />
|
||||
<CommunityRule sections={sections} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Avatar";
|
||||
export type { AvatarSizeValue } from "./Avatar";
|
||||
@@ -1,14 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { memo } from "react";
|
||||
import ArrowBackIcon from "./icon/arrow_back.svg";
|
||||
import ContentCopyIcon from "./icon/content_copy.svg";
|
||||
import EditIcon from "./icon/edit.svg";
|
||||
import ExclamationIcon from "./icon/exclamation.svg";
|
||||
import ChevronRightIcon from "./icon/chevron_right.svg";
|
||||
import LogOutIcon from "./icon/log_out.svg";
|
||||
import MailIcon from "./icon/mail.svg";
|
||||
import WarningIcon from "./icon/warning.svg";
|
||||
import ArrowBackIcon from "./arrow_back.svg";
|
||||
import ContentCopyIcon from "./content_copy.svg";
|
||||
import EditIcon from "./edit.svg";
|
||||
import ExclamationIcon from "./exclamation.svg";
|
||||
import ChevronRightIcon from "./chevron_right.svg";
|
||||
import LogOutIcon from "./log_out.svg";
|
||||
import MailIcon from "./mail.svg";
|
||||
import WarningIcon from "./warning.svg";
|
||||
|
||||
export const ICON_NAME_OPTIONS = [
|
||||
"arrow_back",
|
||||
@@ -75,12 +76,13 @@ function IconComponent({
|
||||
// Turbopack/webpack mismatch: `.svg` may be a URL string instead of SVGR output.
|
||||
if (typeof resolved === "string") {
|
||||
return (
|
||||
<img
|
||||
<Image
|
||||
src={resolved}
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
alt=""
|
||||
unoptimized
|
||||
aria-hidden={ariaHidden}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default } from "./Icon";
|
||||
export { ICON_NAME_OPTIONS } from "./Icon";
|
||||
export type { IconName, IconProps } from "./Icon";
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as Icon, ICON_NAME_OPTIONS } from "./Icon";
|
||||
export type { IconName, IconProps } from "./Icon";
|
||||
export { default as Logo } from "./logo";
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
ButtonTypeValue,
|
||||
ButtonPaletteValue,
|
||||
ButtonStateValue,
|
||||
} from "../../../lib/propNormalization";
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./Button";
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./InlineTextButton";
|
||||
export type { InlineTextButtonProps } from "./InlineTextButton";
|
||||
@@ -1,101 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Tag from "../../utility/Tag";
|
||||
import type { CardViewProps } from "./Card.types";
|
||||
|
||||
function InfoIcon() {
|
||||
return (
|
||||
<span
|
||||
className="flex h-[var(--spacing-scale-016)] w-[var(--spacing-scale-016)] shrink-0 items-center justify-center rounded-full border border-[var(--color-content-invert-brand-secondary)] bg-transparent font-inter text-[10px] font-medium leading-none text-[var(--color-content-invert-brand-secondary)]"
|
||||
aria-hidden
|
||||
>
|
||||
?
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function CardTag({
|
||||
recommended,
|
||||
selected,
|
||||
}: {
|
||||
recommended: boolean;
|
||||
selected: boolean;
|
||||
}) {
|
||||
if (selected) return <Tag variant="selected" />;
|
||||
if (recommended) return <Tag variant="recommended" />;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function CardView({
|
||||
label,
|
||||
supportText,
|
||||
recommended,
|
||||
selected,
|
||||
orientation,
|
||||
showInfoIcon,
|
||||
id: cardId,
|
||||
className,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
}: CardViewProps) {
|
||||
const borderClass = "border border-[var(--color-border-default-primary)]";
|
||||
const selectedBorder = selected
|
||||
? "outline outline-2 outline-dashed outline-black outline-offset-[-2px]"
|
||||
: "";
|
||||
const baseClasses = `select-none rounded-[var(--radius-measures-radius-small)] bg-[#FFFFFF] p-4 transition-[border-color,box-shadow,outline] duration-200 cursor-pointer ${borderClass} ${selectedBorder} ${className}`;
|
||||
|
||||
if (orientation === "horizontal") {
|
||||
return (
|
||||
<div
|
||||
{...(cardId ? { "data-card-id": cardId } : {})}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={supportText ? `${label}: ${supportText}` : label}
|
||||
className={baseClasses}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<div className="flex flex-col gap-2 items-start w-full">
|
||||
<CardTag recommended={recommended} selected={selected} />
|
||||
<span className="font-inter text-base font-semibold leading-6 text-black w-full">
|
||||
{label}
|
||||
</span>
|
||||
{supportText ? (
|
||||
<p className="font-inter text-sm font-normal leading-5 text-black w-full">
|
||||
{supportText}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
{...(cardId ? { "data-card-id": cardId } : {})}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={supportText ? `${label}: ${supportText}` : label}
|
||||
className={`${baseClasses} flex flex-row items-center justify-between gap-4`}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<div className="min-w-0 flex-1 flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-inter text-base font-semibold leading-6 text-black">
|
||||
{label}
|
||||
</span>
|
||||
{showInfoIcon ? <InfoIcon /> : null}
|
||||
</div>
|
||||
{supportText ? (
|
||||
<p className="font-inter text-sm font-normal leading-5 text-black">
|
||||
{supportText}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="shrink-0 w-[6rem]">
|
||||
<CardTag recommended={recommended} selected={selected} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from "./Card.container";
|
||||
export type { CardProps } from "./Card.types";
|
||||
+2
-1
@@ -8,7 +8,8 @@ const DEFAULT_TOGGLE_LABEL = "See all communication approaches";
|
||||
const DEFAULT_SHOW_LESS_LABEL = "Show less";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / CardStack" (TODO(figma)). Selectable stack of cards with
|
||||
* Figma: "Utility / CardStack"; canonical code under `cards/`.
|
||||
* Selectable stack of cards with
|
||||
* an optional "see all"/"show less" expand toggle.
|
||||
*/
|
||||
const CardStackContainer = memo<CardStackProps>(
|
||||
+16
-16
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import HeaderLockup from "../../type/HeaderLockup";
|
||||
import Card from "../../cards/Card";
|
||||
import Selection from "../Selection";
|
||||
import type { CardStackViewProps } from "./CardStack.types";
|
||||
|
||||
export function CardStackView({
|
||||
@@ -59,7 +59,7 @@ export function CardStackView({
|
||||
) : null}
|
||||
<div className="flex w-full min-w-0 flex-col gap-2">
|
||||
{displayedCards.map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -103,7 +103,7 @@ export function CardStackView({
|
||||
{expanded ? (
|
||||
<div className="mx-auto grid w-full max-w-[min(100%,860px)] grid-cols-1 gap-x-4 gap-y-6 md:grid-cols-2">
|
||||
{cards.map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -120,7 +120,7 @@ export function CardStackView({
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-2 md:hidden">
|
||||
{compactCards.map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -143,7 +143,7 @@ export function CardStackView({
|
||||
<div className="hidden flex-col gap-2 lg:flex">
|
||||
<div className="flex justify-center gap-2">
|
||||
{compactCards.slice(0, 3).map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -162,7 +162,7 @@ export function CardStackView({
|
||||
{compactCards
|
||||
.slice(3, compactRecommendedLimit)
|
||||
.map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -181,7 +181,7 @@ export function CardStackView({
|
||||
<div className="hidden flex-col gap-2 md:flex lg:hidden">
|
||||
<div className="flex justify-center gap-2">
|
||||
{compactCards.slice(0, 2).map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -197,7 +197,7 @@ export function CardStackView({
|
||||
</div>
|
||||
<div className="flex justify-center gap-2">
|
||||
{compactCards.slice(2, 4).map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -213,7 +213,7 @@ export function CardStackView({
|
||||
</div>
|
||||
{compactCards[4] ? (
|
||||
<div className="flex justify-center gap-2">
|
||||
<Card
|
||||
<Selection
|
||||
id={compactCards[4].id}
|
||||
label={compactCards[4].label}
|
||||
supportText={compactCards[4].supportText}
|
||||
@@ -233,7 +233,7 @@ export function CardStackView({
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-2 md:hidden">
|
||||
{compactCards.map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -253,7 +253,7 @@ export function CardStackView({
|
||||
<div className="mx-auto hidden w-full max-w-[min(100%,860px)] flex-col gap-2 md:flex lg:hidden">
|
||||
<div className="flex justify-center gap-2">
|
||||
{compactCards.slice(0, 2).map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -268,7 +268,7 @@ export function CardStackView({
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Card
|
||||
<Selection
|
||||
id={compactCards[2].id}
|
||||
label={compactCards[2].label}
|
||||
supportText={compactCards[2].supportText}
|
||||
@@ -283,7 +283,7 @@ export function CardStackView({
|
||||
</div>
|
||||
<div className="mx-auto hidden w-full max-w-[min(100%,860px)] flex-wrap justify-center gap-2 lg:flex">
|
||||
{compactCards.map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -305,7 +305,7 @@ export function CardStackView({
|
||||
key={item.id}
|
||||
className="flex w-full min-w-0 shrink-0 justify-center md:w-[281px] md:max-w-[281px]"
|
||||
>
|
||||
<Card
|
||||
<Selection
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
supportText={item.supportText}
|
||||
@@ -326,7 +326,7 @@ export function CardStackView({
|
||||
{/* Compact under 640: single column, up to 5 recommended cards */}
|
||||
<div className="flex w-full flex-col gap-2 md:hidden">
|
||||
{compactCards.map((item) => (
|
||||
<Card
|
||||
<Selection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
@@ -352,7 +352,7 @@ export function CardStackView({
|
||||
: "md:col-start-4 md:col-span-2";
|
||||
return (
|
||||
<div key={item.id} className={colClass}>
|
||||
<Card
|
||||
<Selection
|
||||
id={item.id}
|
||||
label={item.label}
|
||||
supportText={item.supportText}
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { IconCardView } from "./IconCard.view";
|
||||
import type { IconCardProps } from "./IconCard.types";
|
||||
import { IconView } from "./Icon.view";
|
||||
import type { IconProps } from "./Icon.types";
|
||||
|
||||
const IconCardContainer = memo<IconCardProps>(
|
||||
const IconContainer = memo<IconProps>(
|
||||
({ icon, title, description, className = "", onClick }) => {
|
||||
const handleClick = () => {
|
||||
if (onClick) onClick();
|
||||
@@ -18,7 +18,7 @@ const IconCardContainer = memo<IconCardProps>(
|
||||
};
|
||||
|
||||
return (
|
||||
<IconCardView
|
||||
<IconView
|
||||
icon={icon}
|
||||
title={title}
|
||||
description={description}
|
||||
@@ -30,6 +30,6 @@ const IconCardContainer = memo<IconCardProps>(
|
||||
},
|
||||
);
|
||||
|
||||
IconCardContainer.displayName = "IconCard";
|
||||
IconContainer.displayName = "Icon";
|
||||
|
||||
export default IconCardContainer;
|
||||
export default IconContainer;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
export interface IconCardProps {
|
||||
export interface IconProps {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -6,7 +6,7 @@ export interface IconCardProps {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface IconCardViewProps {
|
||||
export interface IconViewProps {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import type { IconCardViewProps } from "./IconCard.types";
|
||||
import type { IconViewProps } from "./Icon.types";
|
||||
|
||||
export function IconCardView({
|
||||
export function IconView({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
className,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
}: IconCardViewProps) {
|
||||
}: IconViewProps) {
|
||||
return (
|
||||
<div
|
||||
className={`border border-[var(--color-border-default-primary)] flex flex-col h-[350px] items-start justify-between p-[var(--measures-spacing-020)] relative w-[288px] bg-transparent cursor-pointer transition-all duration-200 hover:scale-[1.02] hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-content-default-brand-primary)] focus:ring-offset-2 ${className}`}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Icon.container";
|
||||
export type { IconProps } from "./Icon.types";
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from "./IconCard.container";
|
||||
export type { IconCardProps } from "./IconCard.types";
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useMemo } from "react";
|
||||
import MiniCardView from "./MiniCard.view";
|
||||
import type { MiniCardProps } from "./MiniCard.types";
|
||||
import MiniView from "./Mini.view";
|
||||
import type { MiniProps } from "./Mini.types";
|
||||
|
||||
const MiniCardContainer = memo<MiniCardProps>(
|
||||
const MiniContainer = memo<MiniProps>(
|
||||
({
|
||||
children,
|
||||
className = "",
|
||||
@@ -75,7 +75,7 @@ const MiniCardContainer = memo<MiniCardProps>(
|
||||
}, [href, onClick, computedAriaLabel]);
|
||||
|
||||
return (
|
||||
<MiniCardView
|
||||
<MiniView
|
||||
className={className}
|
||||
backgroundColor={backgroundColor}
|
||||
panelContent={panelContent}
|
||||
@@ -87,11 +87,11 @@ const MiniCardContainer = memo<MiniCardProps>(
|
||||
wrapperProps={wrapperProps}
|
||||
>
|
||||
{children}
|
||||
</MiniCardView>
|
||||
</MiniView>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
MiniCardContainer.displayName = "MiniCard";
|
||||
MiniContainer.displayName = "Mini";
|
||||
|
||||
export default MiniCardContainer;
|
||||
export default MiniContainer;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
export interface MiniCardProps {
|
||||
export interface MiniProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
backgroundColor?: string;
|
||||
@@ -11,7 +11,7 @@ export interface MiniCardProps {
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export interface MiniCardViewProps {
|
||||
export interface MiniViewProps {
|
||||
children?: React.ReactNode;
|
||||
className: string;
|
||||
backgroundColor: string;
|
||||
+5
-5
@@ -2,9 +2,9 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import Image from "next/image";
|
||||
import type { MiniCardViewProps } from "./MiniCard.types";
|
||||
import type { MiniViewProps } from "./Mini.types";
|
||||
|
||||
function MiniCardView({
|
||||
function MiniView({
|
||||
children,
|
||||
className,
|
||||
backgroundColor,
|
||||
@@ -15,7 +15,7 @@ function MiniCardView({
|
||||
computedAriaLabel,
|
||||
wrapperElement,
|
||||
wrapperProps,
|
||||
}: MiniCardViewProps) {
|
||||
}: MiniViewProps) {
|
||||
const cardContentElement = (
|
||||
<div className={`h-[186px] flex flex-col gap-[7px] ${className}`}>
|
||||
{/* Top part - Inner panel */}
|
||||
@@ -81,6 +81,6 @@ function MiniCardView({
|
||||
);
|
||||
}
|
||||
|
||||
MiniCardView.displayName = "MiniCardView";
|
||||
MiniView.displayName = "MiniView";
|
||||
|
||||
export default memo(MiniCardView);
|
||||
export default memo(MiniView);
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Mini.container";
|
||||
export type { MiniProps } from "./Mini.types";
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from "./MiniCard.container";
|
||||
export type { MiniCardProps } from "./MiniCard.types";
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { RuleCardView } from "./RuleCard.view";
|
||||
import type { RuleCardProps } from "./RuleCard.types";
|
||||
import { RuleView } from "./Rule.view";
|
||||
import type { RuleProps } from "./Rule.types";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -21,7 +21,7 @@ declare global {
|
||||
* Figma: "Card / Rule" — e.g. profile `22143:900771` when **Has bottom link** is on
|
||||
* (`hasBottomLinks` + `bottomLinks` / optional `bottomStatusLabel`).
|
||||
*/
|
||||
const RuleCardContainer = memo<RuleCardProps>(
|
||||
const RuleContainer = memo<RuleProps>(
|
||||
({
|
||||
title,
|
||||
description,
|
||||
@@ -72,7 +72,7 @@ const RuleCardContainer = memo<RuleCardProps>(
|
||||
};
|
||||
|
||||
return (
|
||||
<RuleCardView
|
||||
<RuleView
|
||||
title={title}
|
||||
description={description}
|
||||
icon={icon}
|
||||
@@ -95,6 +95,6 @@ const RuleCardContainer = memo<RuleCardProps>(
|
||||
},
|
||||
);
|
||||
|
||||
RuleCardContainer.displayName = "RuleCard";
|
||||
RuleContainer.displayName = "Rule";
|
||||
|
||||
export default RuleCardContainer;
|
||||
export default RuleContainer;
|
||||
+8
-7
@@ -1,4 +1,5 @@
|
||||
import type { ChipOption } from "../../controls/MultiSelect/MultiSelect.types";
|
||||
import type { RuleSizeValue } from "../../../../lib/propNormalization";
|
||||
|
||||
export interface Category {
|
||||
name: string;
|
||||
@@ -14,14 +15,14 @@ export interface Category {
|
||||
}
|
||||
|
||||
/** Bottom row for `Card / Rule` when Figma **Has bottom link** is on (profile, etc.). */
|
||||
export interface RuleCardBottomLink {
|
||||
export interface RuleBottomLink {
|
||||
id: string;
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface RuleCardProps {
|
||||
export interface RuleProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
@@ -29,7 +30,7 @@ export interface RuleCardProps {
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
expanded?: boolean;
|
||||
size?: "XS" | "S" | "M" | "L";
|
||||
size?: RuleSizeValue;
|
||||
categories?: Category[];
|
||||
logoUrl?: string;
|
||||
logoAlt?: string;
|
||||
@@ -44,10 +45,10 @@ export interface RuleCardProps {
|
||||
hasBottomLinks?: boolean;
|
||||
/** Uppercase chip (e.g. IN PROGRESS); omit when no left badge. */
|
||||
bottomStatusLabel?: string;
|
||||
bottomLinks?: RuleCardBottomLink[];
|
||||
bottomLinks?: RuleBottomLink[];
|
||||
}
|
||||
|
||||
export interface RuleCardViewProps {
|
||||
export interface RuleViewProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
@@ -56,7 +57,7 @@ export interface RuleCardViewProps {
|
||||
onClick?: () => void;
|
||||
onKeyDown?: (_event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
expanded: boolean;
|
||||
size: "XS" | "S" | "M" | "L";
|
||||
size: RuleSizeValue;
|
||||
categories?: Category[];
|
||||
logoUrl?: string;
|
||||
logoAlt?: string;
|
||||
@@ -64,5 +65,5 @@ export interface RuleCardViewProps {
|
||||
hideCategoryAddButton?: boolean;
|
||||
hasBottomLinks?: boolean;
|
||||
bottomStatusLabel?: string;
|
||||
bottomLinks?: RuleCardBottomLink[];
|
||||
bottomLinks?: RuleBottomLink[];
|
||||
}
|
||||
+4
-4
@@ -4,9 +4,9 @@ import Image from "next/image";
|
||||
import { useTranslation } from "../../../contexts/MessagesContext";
|
||||
import MultiSelect from "../../controls/MultiSelect";
|
||||
import NavigationLink from "../../navigation/Link";
|
||||
import type { RuleCardBottomLink, RuleCardViewProps } from "./RuleCard.types";
|
||||
import type { RuleBottomLink, RuleViewProps } from "./Rule.types";
|
||||
|
||||
export function RuleCardView({
|
||||
export function RuleView({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
@@ -24,7 +24,7 @@ export function RuleCardView({
|
||||
hasBottomLinks = false,
|
||||
bottomStatusLabel,
|
||||
bottomLinks,
|
||||
}: RuleCardViewProps) {
|
||||
}: RuleViewProps) {
|
||||
const t = useTranslation("ruleCard");
|
||||
const ariaLabel = t("ariaLabel")?.replace("{title}", title) || title;
|
||||
const interactiveCard = !hasBottomLinks;
|
||||
@@ -181,7 +181,7 @@ export function RuleCardView({
|
||||
? "rounded-[var(--measures-radius-300,12px)]"
|
||||
: "rounded-[var(--radius-measures-radius-small)]";
|
||||
|
||||
function renderBottomLink(link: RuleCardBottomLink) {
|
||||
function renderBottomLink(link: RuleBottomLink) {
|
||||
const shared = {
|
||||
variant: "paragraph" as const,
|
||||
type: "primary" as const,
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Rule.container";
|
||||
export type { Category, RuleBottomLink, RuleProps } from "./Rule.types";
|
||||
@@ -1,5 +0,0 @@
|
||||
export { default } from "./RuleCard.container";
|
||||
export type {
|
||||
RuleCardBottomLink,
|
||||
RuleCardProps,
|
||||
} from "./RuleCard.types";
|
||||
+10
-6
@@ -1,10 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { CardView } from "./Card.view";
|
||||
import type { CardProps } from "./Card.types";
|
||||
import { SelectionView } from "./Selection.view";
|
||||
import type { SelectionProps } from "./Selection.types";
|
||||
|
||||
const CardContainer = memo<CardProps>(
|
||||
/**
|
||||
* Figma: "Card / CardSelection" — stacked tile e.g. `16775:28762` (recommended + label + supportText).
|
||||
* `orientation="horizontal"` selects that vertical stack; `"vertical"` is label + optional info icon with tag on the right (CardStack expanded / single-column).
|
||||
*/
|
||||
const SelectionContainer = memo<SelectionProps>(
|
||||
({
|
||||
label,
|
||||
supportText = "",
|
||||
@@ -28,7 +32,7 @@ const CardContainer = memo<CardProps>(
|
||||
};
|
||||
|
||||
return (
|
||||
<CardView
|
||||
<SelectionView
|
||||
label={label}
|
||||
supportText={supportText}
|
||||
recommended={recommended}
|
||||
@@ -44,6 +48,6 @@ const CardContainer = memo<CardProps>(
|
||||
},
|
||||
);
|
||||
|
||||
CardContainer.displayName = "Card";
|
||||
SelectionContainer.displayName = "Selection";
|
||||
|
||||
export default CardContainer;
|
||||
export default SelectionContainer;
|
||||
+3
-3
@@ -1,17 +1,17 @@
|
||||
export interface CardProps {
|
||||
export interface SelectionProps {
|
||||
label: string;
|
||||
supportText?: string;
|
||||
recommended?: boolean;
|
||||
selected?: boolean;
|
||||
orientation: "horizontal" | "vertical";
|
||||
showInfoIcon?: boolean;
|
||||
/** Optional id for the card root (e.g. data-card-id for focus after modal close). */
|
||||
/** Optional id for the root (e.g. `data-card-id` for focus after modal close). */
|
||||
id?: string;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface CardViewProps {
|
||||
export interface SelectionViewProps {
|
||||
label: string;
|
||||
supportText: string;
|
||||
recommended: boolean;
|
||||
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import Tag from "../../utility/Tag";
|
||||
import type { SelectionViewProps } from "./Selection.types";
|
||||
|
||||
function InfoIcon() {
|
||||
return (
|
||||
<span
|
||||
className="flex h-[var(--spacing-scale-016)] w-[var(--spacing-scale-016)] shrink-0 items-center justify-center rounded-full border border-[var(--color-content-invert-brand-secondary)] bg-transparent font-inter text-[10px] font-medium leading-none text-[var(--color-content-invert-brand-secondary)]"
|
||||
aria-hidden
|
||||
>
|
||||
?
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectionTag({
|
||||
recommended,
|
||||
selected,
|
||||
}: {
|
||||
recommended: boolean;
|
||||
selected: boolean;
|
||||
}) {
|
||||
if (selected) return <Tag variant="selected" />;
|
||||
if (recommended) return <Tag variant="recommended" />;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function SelectionView({
|
||||
label,
|
||||
supportText,
|
||||
recommended,
|
||||
selected,
|
||||
orientation,
|
||||
showInfoIcon,
|
||||
id: selectionId,
|
||||
className,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
}: SelectionViewProps) {
|
||||
const borderClass = "border border-[var(--color-border-default-primary)]";
|
||||
const selectedBorder = selected
|
||||
? "outline outline-2 outline-dashed outline-black outline-offset-[-2px]"
|
||||
: "";
|
||||
|
||||
// Figma: "Card / CardSelection" vertical stack — node `16775:28762` (dev).
|
||||
// Prop `orientation="horizontal"` is this stacked layout (historical naming).
|
||||
if (orientation === "horizontal") {
|
||||
const baseClasses = `select-none rounded-[var(--measures-radius-200,8px)] bg-[var(--color-gray-000)] px-4 py-3 transition-[border-color,box-shadow,outline] duration-200 cursor-pointer ${borderClass} ${selectedBorder} ${className}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...(selectionId ? { "data-card-id": selectionId } : {})}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={supportText ? `${label}: ${supportText}` : label}
|
||||
className={`${baseClasses} flex min-h-0 w-full flex-col items-start justify-center gap-1`}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<SelectionTag recommended={recommended} selected={selected} />
|
||||
<span className="w-full font-inter text-base font-medium leading-5 text-[var(--color-content-invert-secondary)]">
|
||||
{label}
|
||||
</span>
|
||||
{supportText ? (
|
||||
<p className="w-full font-inter text-xs font-normal leading-4 text-[var(--color-content-invert-tertiary)]">
|
||||
{supportText}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const baseClasses = `select-none rounded-[var(--measures-radius-200,8px)] bg-[var(--color-gray-000)] p-4 transition-[border-color,box-shadow,outline] duration-200 cursor-pointer ${borderClass} ${selectedBorder} ${className}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...(selectionId ? { "data-card-id": selectionId } : {})}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={supportText ? `${label}: ${supportText}` : label}
|
||||
className={`${baseClasses} flex flex-row items-center justify-between gap-4`}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-inter text-base font-medium leading-5 text-[var(--color-content-invert-secondary)]">
|
||||
{label}
|
||||
</span>
|
||||
{showInfoIcon ? <InfoIcon /> : null}
|
||||
</div>
|
||||
{supportText ? (
|
||||
<p className="font-inter text-xs font-normal leading-4 text-[var(--color-content-invert-tertiary)]">
|
||||
{supportText}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<SelectionTag recommended={recommended} selected={selected} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Selection.container";
|
||||
export type { SelectionProps } from "./Selection.types";
|
||||
@@ -1,19 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import SectionNumber from "../sections/SectionNumber";
|
||||
import type { StepSizeValue } from "../../../../lib/propNormalization";
|
||||
import SectionNumber from "../../sections/SectionNumber";
|
||||
|
||||
export type NumberCardSizeValue = "small" | "medium" | "large" | "xlarge";
|
||||
|
||||
interface NumberCardProps {
|
||||
interface StepProps {
|
||||
number: number;
|
||||
text: string;
|
||||
size?: NumberCardSizeValue;
|
||||
size?: StepSizeValue;
|
||||
iconShape?: string;
|
||||
iconColor?: string;
|
||||
}
|
||||
|
||||
const NumberCard = memo<NumberCardProps>(({ number, text, size: sizeProp }) => {
|
||||
const Step = memo<StepProps>(({ number, text, size: sizeProp }) => {
|
||||
const baseClasses =
|
||||
"bg-[var(--color-surface-inverse-primary)] rounded-[12px] shadow-lg";
|
||||
|
||||
@@ -101,6 +100,6 @@ const NumberCard = memo<NumberCardProps>(({ number, text, size: sizeProp }) => {
|
||||
);
|
||||
});
|
||||
|
||||
NumberCard.displayName = "NumberCard";
|
||||
Step.displayName = "Step";
|
||||
|
||||
export default NumberCard;
|
||||
export default Step;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Step";
|
||||
export type { StepSizeValue } from "../../../../lib/propNormalization";
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useMemo } from "react";
|
||||
import Create from "../../modals/Create";
|
||||
import Chip from "../../controls/Chip";
|
||||
import InputLabel from "../../utility/InputLabel";
|
||||
import InputLabel from "../../type/InputLabel";
|
||||
import ContentLockup from "../../type/ContentLockup";
|
||||
import ModalTextAreaField from "../../../(app)/create/components/ModalTextAreaField";
|
||||
import { useMessages, useTranslation } from "../../../contexts/MessagesContext";
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import RuleCard from "../RuleCard";
|
||||
import type {
|
||||
Category,
|
||||
RuleCardProps,
|
||||
} from "../RuleCard/RuleCard.types";
|
||||
import Rule from "../Rule";
|
||||
import type { Category, RuleProps } from "../Rule";
|
||||
import { getAssetPath } from "../../../../lib/assetUtils";
|
||||
import type { RuleTemplateDto } from "../../../../lib/create/fetchTemplates";
|
||||
import {
|
||||
@@ -21,14 +18,14 @@ import { TemplateChipDetailModal } from "./TemplateChipDetailModal";
|
||||
|
||||
export interface TemplateReviewCardProps {
|
||||
template: RuleTemplateDto;
|
||||
/** Merged onto RuleCard `className` (e.g. final-review desktop vs mobile radius/padding). */
|
||||
/** Merged onto Rule `className` (e.g. final-review desktop vs mobile radius/padding). */
|
||||
ruleCardClassName?: string;
|
||||
/** RuleCard size; create-flow passes `L` at/above `md`, `M` below (640px). */
|
||||
size?: RuleCardProps["size"];
|
||||
/** Rule size; create-flow passes `L` at/above `md`, `M` below (640px). */
|
||||
size?: RuleProps["size"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Expanded RuleCard for template review: surfaces + icon from Figma catalog (21764-16435);
|
||||
* Expanded Rule for template review: surfaces + icon from Figma catalog (21764-16435);
|
||||
* tag rows from API `body`. Chip clicks open a read-only detail modal per
|
||||
* facet group (values / communication / membership / decision-making / conflict
|
||||
* management) so reviewers can see what each chip means without editing.
|
||||
@@ -56,7 +53,7 @@ export function TemplateReviewCard({
|
||||
setActiveChipId(chipId);
|
||||
},
|
||||
})),
|
||||
[rawCategories],
|
||||
[rawCategories, setActiveChipId],
|
||||
);
|
||||
|
||||
const activeDetail =
|
||||
@@ -64,7 +61,7 @@ export function TemplateReviewCard({
|
||||
|
||||
return (
|
||||
<>
|
||||
<RuleCard
|
||||
<Rule
|
||||
title={template.title}
|
||||
description={summary}
|
||||
expanded
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CheckboxView } from "./Checkbox.view";
|
||||
import type { CheckboxProps } from "./Checkbox.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Checkbox" (TODO(figma)). Single boolean checkbox with
|
||||
* Figma: "Control / Checkbox". Single boolean checkbox with
|
||||
* optional label, supporting standard and inverse modes.
|
||||
*/
|
||||
const CheckboxContainer = memo<CheckboxProps>(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CheckboxGroupView } from "./CheckboxGroup.view";
|
||||
import type { CheckboxGroupProps } from "./CheckboxGroup.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / CheckboxGroup" (TODO(figma)). Group of checkboxes sharing
|
||||
* Figma: "Control / CheckboxGroup". Group of checkboxes sharing
|
||||
* a name that emits the array of currently selected values.
|
||||
*/
|
||||
const CheckboxGroupContainer = ({
|
||||
|
||||
@@ -5,7 +5,7 @@ import ChipView from "./Chip.view";
|
||||
import type { ChipProps } from "./Chip.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Chip" (TODO(figma)). Compact pill-shaped tag with
|
||||
* Figma: "Control / Chip". Compact pill-shaped tag with
|
||||
* selectable, removable, and inline-editable (custom) states.
|
||||
*/
|
||||
const ChipContainer = memo<ChipProps>(
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { IncrementerProps } from "../Incrementer/Incrementer.types";
|
||||
import type {
|
||||
InputLabelPaletteValue,
|
||||
InputLabelSizeValue,
|
||||
} from "../../utility/InputLabel/InputLabel.types";
|
||||
} from "../../type/InputLabel/InputLabel.types";
|
||||
|
||||
export interface IncrementerBlockProps extends IncrementerProps {
|
||||
/** Label text displayed above the incrementer. */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import Incrementer from "../Incrementer";
|
||||
import InputLabel from "../../utility/InputLabel";
|
||||
import InputLabel from "../../type/InputLabel";
|
||||
import type { IncrementerBlockViewProps } from "./IncrementerBlock.types";
|
||||
|
||||
function IncrementerBlockView({
|
||||
|
||||
+3
-2
@@ -5,8 +5,9 @@ import InfoMessageBoxView from "./InfoMessageBox.view";
|
||||
import type { InfoMessageBoxProps } from "./InfoMessageBox.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / InfoMessageBox" (TODO(figma)). Bordered message box that
|
||||
* lists checkbox items under a title with an optional leading icon.
|
||||
* Figma: "Utility / InfoMessageBox"; canonical code under `controls/`.
|
||||
* Bordered message box that lists checkbox items under a title with optional
|
||||
* leading icon.
|
||||
*/
|
||||
const InfoMessageBoxContainer = memo<InfoMessageBoxProps>(
|
||||
({
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import CheckboxGroup from "../../controls/CheckboxGroup";
|
||||
import CheckboxGroup from "../CheckboxGroup";
|
||||
import type { InfoMessageBoxViewProps } from "./InfoMessageBox.types";
|
||||
|
||||
/** Exclamation icon per Figma 19751:35053 – vertical bar + dot inside circle; circle bg white 10% opacity, no border */
|
||||
@@ -5,7 +5,7 @@ import { InputWithCounterView } from "./InputWithCounter.view";
|
||||
import type { InputWithCounterProps } from "./InputWithCounter.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / InputWithCounter" (TODO(figma)).
|
||||
* Figma: "Control / InputWithCounter".
|
||||
* Single-line text input with a label, optional help glyph, and a live
|
||||
* `value.length / maxLength` counter underneath.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@ import MultiSelectView from "./MultiSelect.view";
|
||||
import type { MultiSelectProps } from "./MultiSelect.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / MultiSelect" (TODO(figma)). Labelled set of chips for
|
||||
* Figma: "Control / MultiSelect". Labelled set of chips for
|
||||
* picking multiple values, with an optional add button for custom entries.
|
||||
*/
|
||||
const MultiSelectContainer = memo<MultiSelectProps>(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import Chip from "../Chip";
|
||||
import InputLabel from "../../utility/InputLabel";
|
||||
import InputLabel from "../../type/InputLabel";
|
||||
import type { MultiSelectViewProps } from "./MultiSelect.types";
|
||||
|
||||
function MultiSelectView({
|
||||
@@ -88,7 +88,7 @@ function MultiSelectView({
|
||||
}}
|
||||
className={
|
||||
!addButtonText
|
||||
? // Circular button with border (RuleCard style)
|
||||
? // Circular button with border (Rule style)
|
||||
`bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))] border-[1.25px] ${isInverse ? "border-[var(--color-border-default-primary,#141414)]" : "border-[var(--color-border-default-tertiary,#464646)]"} border-solid flex items-center justify-center ${isSmall ? "size-[30px]" : "size-[40px]"} rounded-[var(--measures-radius-full,9999px)] shrink-0 hover:opacity-80 transition-opacity`
|
||||
: // Text add control (default palette: white label + brand “+”; inverse: inverse primary for both)
|
||||
`flex items-center justify-center overflow-hidden rounded-[var(--measures-radius-full,9999px)] shrink-0 hover:opacity-80 transition-opacity ${
|
||||
|
||||
@@ -5,7 +5,7 @@ import { RadioGroupView } from "./RadioGroup.view";
|
||||
import type { RadioGroupProps } from "./RadioGroup.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / RadioGroup" (TODO(figma)). Group of radio buttons sharing
|
||||
* Figma: "Control / RadioGroup". Group of radio buttons sharing
|
||||
* a name that emits the single currently selected value.
|
||||
*/
|
||||
const RadioGroupContainer = ({
|
||||
|
||||
@@ -18,7 +18,7 @@ import { SelectInputView } from "./SelectInput.view";
|
||||
import type { SelectInputProps } from "./SelectInput.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / SelectInput" (TODO(figma)). Custom-styled select dropdown
|
||||
* Figma: "Control / SelectInput". Custom-styled select dropdown
|
||||
* with a labelled trigger button and floating option menu.
|
||||
*/
|
||||
const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SelectOptionView } from "./SelectOption.view";
|
||||
import type { SelectOptionProps } from "./SelectOption.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / SelectOption" (TODO(figma)). Single option row rendered
|
||||
* Figma: "Control / SelectOption". Single option row rendered
|
||||
* inside `SelectInput`'s dropdown menu.
|
||||
*/
|
||||
const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SwitchView } from "./Switch.view";
|
||||
import type { SwitchProps } from "./Switch.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Switch" (TODO(figma)). Animated on/off toggle switch,
|
||||
* Figma: "Control / Switch". Animated on/off toggle switch,
|
||||
* optionally paired with a trailing text label.
|
||||
*/
|
||||
const SwitchContainer = memo(
|
||||
|
||||
@@ -6,7 +6,7 @@ import { TextAreaView } from "./TextArea.view";
|
||||
import type { TextAreaProps } from "./TextArea.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / TextArea" (TODO(figma)). Multi-line text input with size
|
||||
* Figma: "Control / TextArea". Multi-line text input with size
|
||||
* variants, an embedded appearance, and an optional label and help glyph.
|
||||
*/
|
||||
const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||
|
||||
@@ -6,7 +6,7 @@ import { TextInputView } from "./TextInput.view";
|
||||
import type { TextInputProps } from "./TextInput.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / TextInput" (TODO(figma)). Single-line text input with size
|
||||
* Figma: "Control / TextInput". Single-line text input with size
|
||||
* variants and managed default/active/focus/error states.
|
||||
*/
|
||||
const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ToggleView } from "./Toggle.view";
|
||||
import type { ToggleProps } from "./Toggle.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Toggle" (TODO(figma)). Pill-shaped toggle button with
|
||||
* Figma: "Control / Toggle". Pill-shaped toggle button with
|
||||
* checked/unchecked states and optional leading icon and text.
|
||||
*/
|
||||
const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ToggleGroupView } from "./ToggleGroup.view";
|
||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / ToggleGroup" (TODO(figma)). Segmented row of `Toggle`
|
||||
* Figma: "Control / ToggleGroup". Segmented row of `Toggle`
|
||||
* buttons whose corner radii are shared based on position (left/middle/right).
|
||||
*/
|
||||
const ToggleGroupContainer = memo(
|
||||
|
||||
@@ -5,7 +5,7 @@ import UploadView from "./Upload.view";
|
||||
import type { UploadProps } from "./Upload.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Upload" (TODO(figma)). Click-to-upload tile with a label
|
||||
* Figma: "Control / Upload". Click-to-upload tile with a label
|
||||
* and hint text used to add an image from the user's device.
|
||||
*/
|
||||
const UploadContainer = memo<UploadProps>(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import InputLabel from "../../utility/InputLabel";
|
||||
import InputLabel from "../../type/InputLabel";
|
||||
import type { UploadViewProps } from "./Upload.types";
|
||||
|
||||
function UploadView({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IconName } from "../../asset/Icon";
|
||||
import type { IconName } from "../../asset/icon";
|
||||
import type {
|
||||
ListEntryVariant,
|
||||
ListSize,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IconName } from "../../asset/Icon";
|
||||
import type { IconName } from "../../asset/icon";
|
||||
|
||||
export const LIST_SIZE_OPTIONS = ["s", "m", "l"] as const;
|
||||
export type ListSize = (typeof LIST_SIZE_OPTIONS)[number];
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import Link from "next/link";
|
||||
import Icon, { type IconName } from "../../asset/Icon";
|
||||
import Icon, { type IconName } from "../../asset/icon";
|
||||
import Divider from "../../utility/Divider";
|
||||
import { FIGMA_LIST_ENTRY_OUTER, listEntrySizeLayout } from "../listSizeLayout";
|
||||
import type {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, memo } from "react";
|
||||
|
||||
interface ContextMenuProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>(
|
||||
({ className = "", children, ...props }, ref) => {
|
||||
const menuClasses = `
|
||||
bg-black
|
||||
border border-[var(--color-border-default-tertiary)]
|
||||
rounded-[var(--measures-radius-medium)]
|
||||
shadow-lg
|
||||
p-[4px]
|
||||
min-w-[200px]
|
||||
max-w-[300px]
|
||||
${className}
|
||||
`
|
||||
.trim()
|
||||
.replace(/\s+/g, " ");
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={menuClasses}
|
||||
role="menu"
|
||||
style={{ backgroundColor: "#000000" }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContextMenu.displayName = "ContextMenu";
|
||||
|
||||
export default memo(ContextMenu);
|
||||
@@ -1,27 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, memo } from "react";
|
||||
|
||||
interface ContextMenuDividerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ContextMenuDivider = forwardRef<HTMLDivElement, ContextMenuDividerProps>(
|
||||
({ className = "", ...props }, ref) => {
|
||||
const dividerClasses = `
|
||||
border-t border-[var(--color-border-default-tertiary)]
|
||||
my-1
|
||||
${className}
|
||||
`
|
||||
.trim()
|
||||
.replace(/\s+/g, " ");
|
||||
|
||||
return (
|
||||
<div ref={ref} className={dividerClasses} role="separator" {...props} />
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContextMenuDivider.displayName = "ContextMenuDivider";
|
||||
|
||||
export default memo(ContextMenuDivider);
|
||||
@@ -1,36 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, memo } from "react";
|
||||
|
||||
interface ContextMenuSectionProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
title?: string;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ContextMenuSection = forwardRef<HTMLDivElement, ContextMenuSectionProps>(
|
||||
({ title, children, className = "", ...props }, ref) => {
|
||||
const sectionClasses = `
|
||||
${className}
|
||||
`
|
||||
.trim()
|
||||
.replace(/\s+/g, " ");
|
||||
|
||||
return (
|
||||
<div ref={ref} className={sectionClasses} role="group" {...props}>
|
||||
{title && (
|
||||
<div className="px-3 py-2">
|
||||
<div className="text-[var(--color-content-default-primary)] text-sm font-medium">
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContextMenuSection.displayName = "ContextMenuSection";
|
||||
|
||||
export default memo(ContextMenuSection);
|
||||
@@ -1,101 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, memo, useCallback } from "react";
|
||||
import { ContextMenuItemView } from "./ContextMenuItem.view";
|
||||
import type { ContextMenuItemProps } from "./ContextMenuItem.types";
|
||||
|
||||
const ContextMenuItemContainer = forwardRef<
|
||||
HTMLDivElement,
|
||||
ContextMenuItemProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
selected = false,
|
||||
hasSubmenu = false,
|
||||
disabled = false,
|
||||
className = "",
|
||||
onClick,
|
||||
size: sizeProp = "medium",
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const size = sizeProp;
|
||||
const getTextSize = (): string => {
|
||||
switch (size) {
|
||||
case "small":
|
||||
return "text-[10px] leading-[14px]";
|
||||
case "medium":
|
||||
return "text-[14px] leading-[20px]";
|
||||
case "large":
|
||||
return "text-[16px] leading-[24px]";
|
||||
default:
|
||||
return "text-[14px] leading-[20px]";
|
||||
}
|
||||
};
|
||||
|
||||
const itemClasses = `
|
||||
flex items-center justify-between
|
||||
px-[8px] py-[4px]
|
||||
text-[var(--color-content-default-brand-primary)]
|
||||
${getTextSize()}
|
||||
cursor-pointer
|
||||
transition-colors duration-150
|
||||
${
|
||||
selected
|
||||
? "bg-[var(--color-surface-default-secondary)] rounded-[var(--measures-radius-small)]"
|
||||
: ""
|
||||
}
|
||||
${
|
||||
disabled
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: "hover:!bg-[var(--color-surface-default-secondary)] hover:!rounded-[var(--measures-radius-small)]"
|
||||
}
|
||||
${className}
|
||||
`
|
||||
.trim()
|
||||
.replace(/\s+/g, " ");
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!disabled && onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
},
|
||||
[disabled, onClick],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
if (!disabled && onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
[disabled, onClick],
|
||||
);
|
||||
|
||||
return (
|
||||
<ContextMenuItemView
|
||||
ref={ref}
|
||||
selected={selected}
|
||||
hasSubmenu={hasSubmenu}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
itemClasses={itemClasses}
|
||||
handleClick={handleClick}
|
||||
handleKeyDown={handleKeyDown}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ContextMenuItemView>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContextMenuItemContainer.displayName = "ContextMenuItem";
|
||||
|
||||
export default memo(ContextMenuItemContainer);
|
||||
@@ -1,27 +0,0 @@
|
||||
export type ContextMenuItemSizeValue = "small" | "medium" | "large";
|
||||
|
||||
export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
selected?: boolean;
|
||||
hasSubmenu?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
onClick?: (
|
||||
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
/**
|
||||
* Context menu item size.
|
||||
*/
|
||||
size?: ContextMenuItemSizeValue;
|
||||
}
|
||||
|
||||
export interface ContextMenuItemViewProps {
|
||||
children?: React.ReactNode;
|
||||
selected: boolean;
|
||||
hasSubmenu: boolean;
|
||||
disabled: boolean;
|
||||
className: string;
|
||||
itemClasses: string;
|
||||
handleClick: (_e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
handleKeyDown: (_e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { forwardRef } from "react";
|
||||
import type { ContextMenuItemViewProps } from "./ContextMenuItem.types";
|
||||
|
||||
export const ContextMenuItemView = forwardRef<
|
||||
HTMLDivElement,
|
||||
ContextMenuItemViewProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
selected,
|
||||
hasSubmenu,
|
||||
disabled,
|
||||
itemClasses,
|
||||
handleClick,
|
||||
handleKeyDown,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={itemClasses}
|
||||
role="menuitem"
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
aria-current={selected ? "true" : undefined}
|
||||
aria-disabled={disabled}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center gap-[8px]">
|
||||
{selected && (
|
||||
<svg
|
||||
className="w-4 h-4 text-[var(--color-content-default-brand-primary)]"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
{hasSubmenu && (
|
||||
<svg
|
||||
className="w-4 h-4 text-[var(--color-content-default-brand-primary)]"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContextMenuItemView.displayName = "ContextMenuItemView";
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from "./ContextMenuItem.container";
|
||||
export type { ContextMenuItemProps } from "./ContextMenuItem.types";
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import ContentLockup from "../../type/ContentLockup";
|
||||
import ModalFooter from "../../utility/ModalFooter";
|
||||
import ModalHeader from "../../utility/ModalHeader";
|
||||
import ModalFooter from "../ModalFooter";
|
||||
import ModalHeader from "../ModalHeader";
|
||||
import { CreateModalFrameView } from "./CreateModalFrame.view";
|
||||
import type { CreateViewProps } from "./Create.types";
|
||||
|
||||
|
||||
@@ -78,5 +78,5 @@ export function useCreateModalA11y(
|
||||
document.removeEventListener("keydown", handleTab);
|
||||
previousActiveElementRef.current?.focus();
|
||||
};
|
||||
}, [isOpen]);
|
||||
}, [dialogRef, isOpen]);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ const DialogContainer = memo<DialogProps>(
|
||||
title={title}
|
||||
description={description}
|
||||
footer={footer}
|
||||
children={children}
|
||||
className={className}
|
||||
ariaLabel={ariaLabel}
|
||||
ariaLabelledBy={titleId}
|
||||
@@ -40,7 +39,9 @@ const DialogContainer = memo<DialogProps>(
|
||||
backdropVariant={backdropVariant}
|
||||
overlayRef={overlayRef}
|
||||
dialogRef={dialogRef}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</DialogView>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import ContentLockup from "../../type/ContentLockup";
|
||||
import ModalFooter from "../../utility/ModalFooter";
|
||||
import ModalHeader from "../../utility/ModalHeader";
|
||||
import ModalFooter from "../ModalFooter";
|
||||
import ModalHeader from "../ModalHeader";
|
||||
import { CreateModalFrameView } from "../Create/CreateModalFrame.view";
|
||||
import type { DialogViewProps } from "./Dialog.types";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { createPortal } from "react-dom";
|
||||
import ModalHeader from "../../utility/ModalHeader";
|
||||
import ModalHeader from "../ModalHeader";
|
||||
import type { LoginBackdropVariant, LoginViewProps } from "./Login.types";
|
||||
|
||||
const backdropClasses: Record<LoginBackdropVariant, string> = {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { ModalFooterView } from "./ModalFooter.view";
|
||||
import type { ModalFooterProps } from "./ModalFooter.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / ModalFooter" (TODO(figma)).
|
||||
* Figma: "Utility / ModalFooter". Lives under `modals/` with other composed modal chrome.
|
||||
* Sticky modal footer slot used by the create-flow + login modals to host
|
||||
* primary/secondary actions.
|
||||
*/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user