Component cleanup
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
/** Labeled paragraph group (Figma “Text” stacks under Membership / Decision-making, etc.). */
|
||||
export interface CommunityRuleLabeledBlock {
|
||||
label: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface CommunityRuleEntry {
|
||||
title: string;
|
||||
/** Plain text; split on blank lines into paragraphs when rendering. */
|
||||
body: string;
|
||||
/**
|
||||
* When set, rendered as Figma-style label + body stacks. If non-empty, takes
|
||||
* precedence over {@link body} for main content (body may be empty).
|
||||
*/
|
||||
blocks?: CommunityRuleLabeledBlock[];
|
||||
}
|
||||
|
||||
export interface CommunityRuleSection {
|
||||
categoryName: string;
|
||||
entries: CommunityRuleEntry[];
|
||||
}
|
||||
|
||||
export interface CommunityRuleProps {
|
||||
sections: CommunityRuleSection[];
|
||||
className?: string;
|
||||
/** When true, wrap in white background with left teal bar (small breakpoint). */
|
||||
useCardStyle?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import Section from "../Section";
|
||||
import TextBlock from "../TextBlock";
|
||||
import type { CommunityRuleProps } from "./CommunityRule.types";
|
||||
|
||||
/**
|
||||
* Figma: **Sections** canvas, “Community Rule” frame (16489:4507). Composes
|
||||
* **`Section`** (22002:30757) + **`TextBlock`** (22001:29793); canonical code under **`type/CommunityRule`**.
|
||||
*/
|
||||
|
||||
const SECTION_GAP = "var(--measures-spacing-1200, 64px)";
|
||||
const TEAL_BG = "var(--color-teal-teal50, #c9fef9)";
|
||||
|
||||
function CommunityRuleView({
|
||||
sections,
|
||||
className = "",
|
||||
useCardStyle = false,
|
||||
}: CommunityRuleProps) {
|
||||
const rootClass = useCardStyle
|
||||
? `rounded-[12px] bg-white pl-3 border-l-4 ${className}`
|
||||
: className;
|
||||
const rootStyle = useCardStyle ? { borderLeftColor: TEAL_BG } : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col min-w-0 ${rootClass}`}
|
||||
style={{ gap: SECTION_GAP, ...rootStyle }}
|
||||
>
|
||||
{sections.map((ruleSection, sectionIndex) => (
|
||||
<Section
|
||||
key={sectionIndex}
|
||||
categoryName={ruleSection.categoryName}
|
||||
showRail={!useCardStyle}
|
||||
>
|
||||
{ruleSection.entries.map((entry, entryIndex) => {
|
||||
const hasBlocks = Boolean(entry.blocks?.length);
|
||||
return (
|
||||
<TextBlock
|
||||
key={entryIndex}
|
||||
title={entry.title}
|
||||
body={hasBlocks ? undefined : entry.body}
|
||||
rows={hasBlocks ? entry.blocks : undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Section>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CommunityRuleView.displayName = "CommunityRuleView";
|
||||
|
||||
export default memo(CommunityRuleView);
|
||||
@@ -0,0 +1,7 @@
|
||||
export { default } from "./CommunityRule.view";
|
||||
export type {
|
||||
CommunityRuleProps,
|
||||
CommunityRuleLabeledBlock,
|
||||
CommunityRuleEntry,
|
||||
CommunityRuleSection,
|
||||
} from "./CommunityRule.types";
|
||||
@@ -46,7 +46,8 @@ function HeaderLockupView({
|
||||
|
||||
{/* Description */}
|
||||
{description != null &&
|
||||
!(typeof description === "string" && description.length === 0) && (
|
||||
!(typeof description === "string" && description.length === 0) &&
|
||||
(typeof description === "string" ? (
|
||||
<p
|
||||
className={`font-inter font-normal max-w-[640px] overflow-hidden relative shrink-0 ${descriptionColorClass} text-ellipsis w-full whitespace-pre-wrap ${
|
||||
isLeft ? "" : "text-center"
|
||||
@@ -56,7 +57,17 @@ function HeaderLockupView({
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
) : (
|
||||
<div
|
||||
className={`font-inter font-normal max-w-[640px] overflow-hidden relative shrink-0 ${descriptionColorClass} text-ellipsis w-full whitespace-pre-wrap ${
|
||||
isLeft ? "" : "text-center"
|
||||
} ${
|
||||
isL ? "text-[18px] leading-[1.3]" : "text-[14px] leading-[20px]"
|
||||
}`}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import InputLabelView from "./InputLabel.view";
|
||||
import type { InputLabelProps } from "./InputLabel.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / InputLabel"; canonical code under `type/`.
|
||||
* Reusable form-input label with
|
||||
* optional asterisk, help icon, and helper text.
|
||||
*/
|
||||
const InputLabelContainer = memo<InputLabelProps>(
|
||||
({
|
||||
label,
|
||||
helpIcon = false,
|
||||
asterisk = false,
|
||||
helperText = false,
|
||||
size: sizeProp = "s",
|
||||
palette: paletteProp = "default",
|
||||
className = "",
|
||||
}) => {
|
||||
const size = sizeProp;
|
||||
const palette = paletteProp;
|
||||
|
||||
return (
|
||||
<InputLabelView
|
||||
label={label}
|
||||
helpIcon={helpIcon}
|
||||
asterisk={asterisk}
|
||||
helperText={helperText}
|
||||
size={size}
|
||||
palette={palette}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
InputLabelContainer.displayName = "InputLabel";
|
||||
|
||||
export default InputLabelContainer;
|
||||
@@ -0,0 +1,42 @@
|
||||
export type InputLabelSizeValue = "s" | "m";
|
||||
export type InputLabelPaletteValue = "default" | "inverse";
|
||||
|
||||
export interface InputLabelProps {
|
||||
/**
|
||||
* The label text to display
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Show help icon next to label
|
||||
*/
|
||||
helpIcon?: boolean;
|
||||
/**
|
||||
* Show asterisk (*) to indicate required field
|
||||
*/
|
||||
asterisk?: boolean;
|
||||
/**
|
||||
* Helper text to display on the right side.
|
||||
* If boolean true, shows "Optional text".
|
||||
* If string, shows the provided text.
|
||||
*/
|
||||
helperText?: boolean | string;
|
||||
/**
|
||||
* Size variant: "s" (small) or "m" (medium)
|
||||
*/
|
||||
size?: InputLabelSizeValue;
|
||||
/**
|
||||
* Palette variant: "default" or "inverse"
|
||||
*/
|
||||
palette?: InputLabelPaletteValue;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface InputLabelViewProps {
|
||||
label: string;
|
||||
helpIcon: boolean;
|
||||
asterisk: boolean;
|
||||
helperText: boolean | string;
|
||||
size: "s" | "m";
|
||||
palette: "default" | "inverse";
|
||||
className: string;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { getAssetPath, ASSETS } from "../../../../lib/assetUtils";
|
||||
import type { InputLabelViewProps } from "./InputLabel.types";
|
||||
|
||||
function InputLabelView({
|
||||
label,
|
||||
helpIcon,
|
||||
asterisk,
|
||||
helperText,
|
||||
size,
|
||||
palette,
|
||||
className = "",
|
||||
}: InputLabelViewProps) {
|
||||
const isSmall = size === "s";
|
||||
const isInverse = palette === "inverse";
|
||||
|
||||
// Size-based typography
|
||||
const labelTextSize = isSmall
|
||||
? "text-[length:var(--sizing-350,14px)] leading-[20px]"
|
||||
: "text-[length:var(--sizing-400,16px)] leading-[24px]";
|
||||
|
||||
const helperTextSize = isSmall
|
||||
? "text-[length:var(--measures-sizing-250,10px)] leading-[var(--measures-spacing-350,14px)]"
|
||||
: "text-[length:var(--sizing-300,12px)] leading-[16px]";
|
||||
|
||||
const asteriskSize = isSmall
|
||||
? "text-[length:var(--measures-sizing-250,10px)] leading-[var(--measures-spacing-300,12px)]"
|
||||
: "text-[length:var(--measures-spacing-300,12px)] leading-[var(--measures-spacing-300,12px)]";
|
||||
|
||||
// Palette-based colors
|
||||
const labelColor = isInverse
|
||||
? "text-[color:var(--color-content-inverse-secondary,#1f1f1f)]"
|
||||
: "text-[color:var(--color-content-default-secondary,#d2d2d2)]";
|
||||
|
||||
const helperTextColor =
|
||||
"text-[color:var(--color-content-default-tertiary,#b4b4b4)]";
|
||||
|
||||
// Layout: S uses flex-wrap with baseline, M uses flex with center
|
||||
const containerClass = isSmall
|
||||
? "flex flex-wrap gap-[var(--measures-spacing-200,4px_8px)] items-baseline pr-[var(--measures-spacing-100,4px)] relative w-full"
|
||||
: "flex gap-[4px] items-center relative w-full";
|
||||
|
||||
const labelContainerClass = isSmall
|
||||
? "flex gap-[var(--measures-spacing-050,2px)] items-center relative shrink-0"
|
||||
: "flex gap-[var(--measures-spacing-100,4px)] items-center relative shrink-0";
|
||||
|
||||
const helpIconSize = isSmall ? "size-[12px]" : "size-[16px]";
|
||||
|
||||
// Help icon color filter based on palette
|
||||
// Default: Light yellow (#f6f06f / rgba(246, 240, 111, 1)) - SVG already has this color
|
||||
// Inverse: Dark yellow (#8c8505 / rgba(140, 133, 5, 1))
|
||||
// For default, no filter needed as SVG already has the correct yellow
|
||||
// For inverse, darken the yellow
|
||||
const helpIconFilter = isInverse
|
||||
? "brightness(0.57) saturate(100%)" // Dark yellow (#8c8505) - darken the existing yellow
|
||||
: undefined; // No filter for default - use SVG's native yellow color
|
||||
|
||||
return (
|
||||
<div className={`${containerClass} ${className}`}>
|
||||
<div className={labelContainerClass}>
|
||||
<div className="flex gap-px items-start relative shrink-0">
|
||||
<p
|
||||
className={`font-inter font-normal ${labelTextSize} ${labelColor} relative shrink-0`}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
{asterisk && (
|
||||
<p
|
||||
className={`font-inter font-medium ${asteriskSize} relative shrink-0 text-[color:var(--color-content-default-negative-primary,#ea4845)]`}
|
||||
>
|
||||
*
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{helpIcon && (
|
||||
<div className={`relative shrink-0 ${helpIconSize}`}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- icon from asset path */}
|
||||
<img
|
||||
src={getAssetPath(ASSETS.ICON_HELP)}
|
||||
alt="Help"
|
||||
className="block max-w-none size-full"
|
||||
style={
|
||||
helpIconFilter
|
||||
? {
|
||||
filter: helpIconFilter,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{helperText && (
|
||||
<p
|
||||
className={`flex-[1_0_0] font-inter font-normal ${helperTextSize} min-h-px min-w-px relative ${helperTextColor} text-right`}
|
||||
>
|
||||
{typeof helperText === "string" ? helperText : "Optional text"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
InputLabelView.displayName = "InputLabelView";
|
||||
|
||||
export default memo(InputLabelView);
|
||||
@@ -0,0 +1,3 @@
|
||||
import InputLabel from "./InputLabel.container";
|
||||
|
||||
export default InputLabel;
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export interface SectionProps {
|
||||
/** Category label (16px medium, invert secondary). */
|
||||
categoryName: string;
|
||||
/** When true, renders the 12px-gapped vertical rail like Figma Section (22002:30757). */
|
||||
showRail?: boolean;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import type { SectionProps } from "./Section.types";
|
||||
|
||||
/**
|
||||
* Figma: **Section** (22002:30757) — vertical rail + category + stacked content
|
||||
* (typically **TextBlock** children inside Community Rule).
|
||||
*/
|
||||
|
||||
const SECTION_LINE_COLOR = "var(--color-border-default-tertiary, #464646)";
|
||||
|
||||
const CATEGORY_CLASS =
|
||||
"font-inter font-medium text-[16px] leading-[20px] text-[var(--color-content-invert-secondary,#1f1f1f)] shrink-0 w-full min-w-0";
|
||||
|
||||
function SectionView({
|
||||
categoryName,
|
||||
showRail = true,
|
||||
children,
|
||||
className = "",
|
||||
}: SectionProps) {
|
||||
const inner = (
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-4" style={{ gap: "16px" }}>
|
||||
<p className={CATEGORY_CLASS}>{categoryName}</p>
|
||||
<div className="flex min-w-0 flex-col gap-6">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!showRail) {
|
||||
return (
|
||||
<div className={`flex min-w-0 flex-col ${className}`.trim()} data-name="Section">
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex min-w-0 items-stretch gap-3 ${className}`.trim()}
|
||||
style={{ gap: "12px" }}
|
||||
data-name="Section"
|
||||
>
|
||||
<div
|
||||
className="w-px shrink-0 self-stretch"
|
||||
style={{ backgroundColor: SECTION_LINE_COLOR }}
|
||||
aria-hidden
|
||||
/>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SectionView.displayName = "SectionView";
|
||||
|
||||
export default memo(SectionView);
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Section.view";
|
||||
export type { SectionProps } from "./Section.types";
|
||||
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import type { SectionHeaderVariantValue } from "../../../../lib/propNormalization";
|
||||
|
||||
interface SectionHeaderProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
titleLg?: string;
|
||||
variant?: SectionHeaderVariantValue;
|
||||
/** When set with `variant="multi-line"`, large screens show three title lines (Figma SectionCardSteps). */
|
||||
stackedDesktopLines?: readonly [string, string, string];
|
||||
}
|
||||
|
||||
/**
|
||||
* Figma: **Type / SectionHeader** ([17411:10981](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=17411-10981)).
|
||||
* Responsive title + subtitle lockup: stacked on small viewports, split row from `lg` up.
|
||||
*/
|
||||
const SectionHeader = memo<SectionHeaderProps>(
|
||||
({
|
||||
title,
|
||||
subtitle,
|
||||
titleLg,
|
||||
variant: variantProp = "default",
|
||||
stackedDesktopLines,
|
||||
}) => {
|
||||
const variant = variantProp;
|
||||
const useStackedDesktop =
|
||||
variant === "multi-line" && stackedDesktopLines != null;
|
||||
const rowAlignClasses =
|
||||
variant === "multi-line"
|
||||
? "lg:flex-row lg:justify-between lg:items-center xl:gap-[var(--spacing-scale-024)]"
|
||||
: "lg:flex-row lg:justify-between lg:items-start xl:gap-[var(--spacing-scale-024)]";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col gap-[var(--spacing-scale-004)] w-full ${rowAlignClasses}`}
|
||||
>
|
||||
{/* Title — left column at lg+ */}
|
||||
<div
|
||||
className={
|
||||
variant === "multi-line"
|
||||
? "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center xl:w-[50%] xl:h-[156px] xl:flex xl:items-center"
|
||||
: "lg:w-[369px] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center xl:w-[452px] xl:h-[156px] xl:flex xl:items-center"
|
||||
}
|
||||
>
|
||||
<h2
|
||||
className={
|
||||
variant === "multi-line"
|
||||
? "font-bricolage-grotesque font-bold text-[28px] leading-[36px] md:text-[32px] md:leading-[40px] lg:w-[410px] lg:text-left xl:text-[40px] xl:leading-[52px] text-[var(--color-content-default-primary)]"
|
||||
: "font-bricolage-grotesque font-bold text-[28px] leading-[36px] sm:text-[32px] sm:leading-[40px] lg:text-[32px] lg:leading-[40px] lg:w-[369px] lg:pr-[var(--spacing-scale-096)] xl:text-[40px] xl:leading-[52px] xl:w-[452px] xl:pr-[var(--spacing-scale-096)] text-[var(--color-content-default-primary)]"
|
||||
}
|
||||
>
|
||||
<span className="block lg:hidden">{title}</span>
|
||||
{useStackedDesktop ? (
|
||||
<span className="hidden lg:block">
|
||||
<span className="block">{stackedDesktopLines[0]}</span>
|
||||
<span className="block">{stackedDesktopLines[1]}</span>
|
||||
<span className="block">{stackedDesktopLines[2]}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="hidden lg:block">{titleLg || title}</span>
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Subtitle — right column at lg+ (Figma X Large / Large / stacked small) */}
|
||||
<div
|
||||
className={
|
||||
variant === "multi-line"
|
||||
? "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end lg:ml-[var(--spacing-scale-016)] xl:ml-0 xl:w-[50%] xl:h-[156px] xl:flex xl:items-center xl:justify-end"
|
||||
: "lg:w-[928px] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end xl:h-[156px] xl:flex xl:items-center xl:justify-end"
|
||||
}
|
||||
>
|
||||
<p
|
||||
className={
|
||||
variant === "multi-line"
|
||||
? "font-inter font-normal text-[14px] leading-[20px] md:text-[18px] md:leading-[130%] xl:text-[24px] xl:leading-[32px] text-[var(--color-content-default-tertiary)] lg:text-right"
|
||||
: "font-inter font-normal text-[18px] leading-[130%] sm:text-[18px] sm:leading-[32px] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] xl:text-right text-[#484848] sm:text-[var(--color-content-default-tertiary)] lg:text-[var(--color-content-default-tertiary)] xl:text-[var(--color-content-default-tertiary)] tracking-[0px]"
|
||||
}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SectionHeader.displayName = "SectionHeader";
|
||||
|
||||
export default SectionHeader;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./SectionHeader";
|
||||
export type { SectionHeaderVariantValue } from "../../../../lib/propNormalization";
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { CommunityRuleLabeledBlock } from "../CommunityRule/CommunityRule.types";
|
||||
|
||||
export interface TextBlockProps {
|
||||
/** Figma X Small/Heading — entry title (20px bold, 28px line). */
|
||||
title: string;
|
||||
/** Plain copy; blank-line splits become paragraphs when `rows` is absent. */
|
||||
body?: string;
|
||||
/** Figma labeled stacks (14px medium label + 14px body). Overrides plain `body` when non-empty. */
|
||||
rows?: CommunityRuleLabeledBlock[];
|
||||
className?: string;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import type { TextBlockProps } from "./TextBlock.types";
|
||||
|
||||
/**
|
||||
* Figma: Utility / **Community Rule / Text Block** (22001:29793).
|
||||
* Title + body paragraphs and/or labeled rows (12px between stacks, 8px label→body).
|
||||
*/
|
||||
|
||||
const ENTRY_TITLE_CLASS =
|
||||
"font-inter font-bold text-[20px] leading-[28px] text-[var(--color-content-invert-primary)] shrink-0";
|
||||
|
||||
const ROW_LABEL_CLASS =
|
||||
"font-inter font-medium text-[14px] leading-[18px] text-[var(--color-content-invert-primary)] shrink-0";
|
||||
|
||||
const PARAGRAPH_CLASS =
|
||||
"font-inter font-normal text-[14px] leading-[20px] text-[var(--color-content-invert-primary)] shrink-0";
|
||||
|
||||
function bodyToParagraphs(body: string): string[] {
|
||||
return body
|
||||
.split(/\n\n/)
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
}
|
||||
|
||||
function ParagraphGroup({ text }: { text: string }) {
|
||||
const paragraphs = bodyToParagraphs(text);
|
||||
if (paragraphs.length === 0) return null;
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
{paragraphs.map((p, i) => (
|
||||
<p key={i} className={`${PARAGRAPH_CLASS} whitespace-pre-wrap`}>
|
||||
{p}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TextBlockView({
|
||||
title,
|
||||
body = "",
|
||||
rows,
|
||||
className = "",
|
||||
}: TextBlockProps) {
|
||||
const hasRows = rows && rows.length > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex min-w-0 flex-col gap-2 ${className}`.trim()}
|
||||
data-name="TextBlock"
|
||||
>
|
||||
<p className={`${ENTRY_TITLE_CLASS} w-full min-w-0`}>{title}</p>
|
||||
<div className="flex min-w-0 flex-col gap-3">
|
||||
{hasRows
|
||||
? rows!.map((row, i) => (
|
||||
<div key={i} className="flex min-w-0 flex-col gap-2">
|
||||
<p className={ROW_LABEL_CLASS}>{row.label}</p>
|
||||
<ParagraphGroup text={row.body} />
|
||||
</div>
|
||||
))
|
||||
: body.trim().length > 0 && <ParagraphGroup text={body} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TextBlockView.displayName = "TextBlockView";
|
||||
|
||||
export default memo(TextBlockView);
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./TextBlock.view";
|
||||
export type { TextBlockProps } from "./TextBlock.types";
|
||||
Reference in New Issue
Block a user