From 7eb9cc4c9c745871f34ee9af07d12ccb4ba183f7 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Tue, 22 Sep 2026 12:51:50 -0700 Subject: [PATCH 1/2] improvement(emcn): share larger chip and field geometry --- .claude/rules/emcn-components.md | 4 + .cursor/rules/emcn-components.mdc | 4 + apps/sim/app/(auth)/components/auth-input.tsx | 17 +--- .../landing-cta-link/landing-cta-link.tsx | 2 +- .../secret-value-field/secret-value-field.tsx | 2 +- .../components/chip-input/chip-input.test.tsx | 17 +++- .../src/components/chip-input/chip-input.tsx | 15 ++- .../emcn/src/components/chip/chip-chrome.ts | 6 +- .../emcn/src/components/chip/chip.test.tsx | 93 +++++++++++++++++++ packages/emcn/src/components/chip/chip.tsx | 36 +++++-- 10 files changed, 169 insertions(+), 27 deletions(-) create mode 100644 packages/emcn/src/components/chip/chip.test.tsx diff --git a/.claude/rules/emcn-components.md b/.claude/rules/emcn-components.md index af477a7d886..8c8b468d246 100644 --- a/.claude/rules/emcn-components.md +++ b/.claude/rules/emcn-components.md @@ -70,3 +70,7 @@ geometry only; colour, radius and SVG stroke continue to come from the selected Use `shape='round'` for circular actions, or omit it to retain the current radius. `size='inline'` is a 20px-high action with caption typography and compact horizontal padding. Prefer these supported props to size, padding and radius overrides. + +### Chip sizing and centered actions + +`Chip`, `ChipLink`, and `ChipInput` retain their default 30px height. Use `size="lg"` for the existing auth-scale 36px controls; the larger size follows the root font size. `Chip` and `ChipLink` accept `align="center"` to center their icon/label group, including full-width form actions. Long labels still shrink and retain their overflow treatment. Avoid descendant flex overrides to center chip labels. The default geometry exports remain unchanged for static consumers. diff --git a/.cursor/rules/emcn-components.mdc b/.cursor/rules/emcn-components.mdc index 6a70e29d4b3..75fe49a83e3 100644 --- a/.cursor/rules/emcn-components.mdc +++ b/.cursor/rules/emcn-components.mdc @@ -71,3 +71,7 @@ geometry only; colour, radius and SVG stroke continue to come from the selected Use `shape='round'` for circular actions, or omit it to retain the current radius. `size='inline'` is a 20px-high action with caption typography and compact horizontal padding. Prefer these supported props to size, padding and radius overrides. + +### Chip sizing and centered actions + +`Chip`, `ChipLink`, and `ChipInput` retain their default 30px height. Use `size="lg"` for the existing auth-scale 36px controls; the larger size follows the root font size. `Chip` and `ChipLink` accept `align="center"` to center their icon/label group, including full-width form actions. Long labels still shrink and retain their overflow treatment. Avoid descendant flex overrides to center chip labels. The default geometry exports remain unchanged for static consumers. diff --git a/apps/sim/app/(auth)/components/auth-input.tsx b/apps/sim/app/(auth)/components/auth-input.tsx index 396bae64cf8..a2c3bc12979 100644 --- a/apps/sim/app/(auth)/components/auth-input.tsx +++ b/apps/sim/app/(auth)/components/auth-input.tsx @@ -1,20 +1,11 @@ 'use client' import * as React from 'react' -import { ChipInput, type ChipInputProps, cn } from '@sim/emcn' -import { AUTH_CONTROL_HEIGHT } from '@/app/(auth)/components/constants' +import { ChipInput, type ChipInputProps } from '@sim/emcn' -/** - * The auth text field — a {@link ChipInput} raised to the auth control height - * ({@link AUTH_CONTROL_HEIGHT}) so every labeled field on the auth and invite - * surfaces shares one slightly-taller geometry. All chip props pass through - * (`error`, `endAdornment`, `icon`, …); only the height is owned here, and a - * caller's `className` (layout only) still composes on top. - */ -export const AuthInput = React.forwardRef( - ({ className, ...props }, ref) => ( - - ) +/** Auth fields use the larger shared chip size while retaining native input props and refs. */ +export const AuthInput = React.forwardRef>( + (props, ref) => ) AuthInput.displayName = 'AuthInput' diff --git a/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx b/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx index 4fbba3d3750..5787f2c8560 100644 --- a/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx +++ b/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx @@ -9,7 +9,7 @@ type LandingCtaSize = 'compact' | 'default' | 'display' export type LandingCtaSection = PostHogEventMap['landing_cta_clicked']['section'] -interface LandingCtaLinkProps extends Omit { +interface LandingCtaLinkProps extends Omit { size?: LandingCtaSize variant?: 'primary' | 'outline' /** Adds the animated chevron used by demo actions. */ diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx index a843694bfe8..fa0e488c761 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx @@ -11,7 +11,7 @@ const VIEWER_MASK_LENGTH = 10 type SecretValueFieldProps = Omit< ComponentProps<'input'>, - 'type' | 'value' | 'onChange' | 'readOnly' | 'style' + 'type' | 'value' | 'onChange' | 'readOnly' | 'style' | 'size' > & { /** The chip owns field styling; callers use className for layout. */ style?: never diff --git a/packages/emcn/src/components/chip-input/chip-input.test.tsx b/packages/emcn/src/components/chip-input/chip-input.test.tsx index acbc4226246..a1d70082ff6 100644 --- a/packages/emcn/src/components/chip-input/chip-input.test.tsx +++ b/packages/emcn/src/components/chip-input/chip-input.test.tsx @@ -30,6 +30,19 @@ afterEach(() => { }) describe('ChipInput', () => { + it.each([undefined, 'lg'] as const)( + 'emits a single height for size %s without forwarding it to the native field', + (size) => { + const input = mount() + const heights = input.parentElement?.className + .split(' ') + .filter((token) => token.startsWith('h-')) + expect(heights).toEqual([size === 'lg' ? 'h-9' : 'h-[30px]']) + expect(input.hasAttribute('size')).toBe(false) + expect(input.disabled).toBe(true) + } + ) + it('keeps the focused input mounted when custom leading content changes', () => { const input = mount() const render = (color: string) => ( @@ -76,7 +89,7 @@ describe('chip form controls', () => { error aria-invalid aria-describedby='error' - className='h-[34px]' + size='lg' />

Enter a work email

{ expect(input.labels?.[0].textContent).toBe('Work email') expect(input.getAttribute('aria-describedby')).toBe('error') expect(input.getAttribute('aria-invalid')).toBe('true') - expect(input.parentElement?.className).toContain('h-[34px]') + expect(input.parentElement?.className).toContain('h-9') expect(input.parentElement?.className).toContain('border-[var(--text-error)]') expect(textareaRef.current?.rows).toBe(3) expect(textareaRef.current?.className).toContain('min-h-[80px]') diff --git a/packages/emcn/src/components/chip-input/chip-input.tsx b/packages/emcn/src/components/chip-input/chip-input.tsx index 444cbb1704a..3c5d7d1f161 100644 --- a/packages/emcn/src/components/chip-input/chip-input.tsx +++ b/packages/emcn/src/components/chip-input/chip-input.tsx @@ -28,11 +28,19 @@ */ import * as React from 'react' import { cn } from '../../lib/cn' -import { chipFieldSurfaceClass, chipFieldTextClass, chipGeometryClass } from '../chip/chip-chrome' +import { + chipContentGeometryClass, + chipFieldSurfaceClass, + chipFieldTextClass, + chipRadiusClass, + chipSizeClasses, +} from '../chip/chip-chrome' type ChipInputIcon = React.ComponentType<{ className?: string }> export interface ChipInputProps extends Omit, 'size'> { + /** Control height: 30px by default, or the larger 36px auth spacing scale. */ + size?: keyof typeof chipSizeClasses /** Leading icon component (e.g. `Search` from `@sim/emcn/icons`). Rendered at 14px in `--text-icon`, with the chip's 1.5 gap. */ icon?: ChipInputIcon /** Custom leading content, such as a color swatch. Takes precedence over `icon`. */ @@ -62,6 +70,7 @@ export const ChipInput = React.forwardRef( error, disabled, type = 'text', + size = 'md', ...props }, ref @@ -69,7 +78,9 @@ export const ChipInput = React.forwardRef(
{ + it.each([undefined, 'lg'] as const)( + 'uses one height for raw variants, buttons and links at size %s', + (size) => { + const height = size === 'lg' ? 'h-9' : 'h-[30px]' + expect( + chipVariants({ size }) + .split(' ') + .filter((token) => token.startsWith('h-')) + ).toEqual([height]) + for (const node of [ + + Continue + , + + Continue + , + ]) { + const markup = renderToStaticMarkup(node) + expect(markup).toContain(height) + expect(markup).not.toContain(size === 'lg' ? 'h-[30px]' : 'h-9') + expect(markup).not.toMatch(/ size=/) + } + expect(chipGeometryUnroundedClass).toContain('h-[30px]') + expect(chipGeometryClass).toContain('h-[30px]') + expect(chipGeometryClass).toContain('rounded-lg') + } + ) + + it('centers the icon and label without preventing long text from shrinking', () => { + const markup = renderToStaticMarkup( + }> + Continue with your identity provider + + ) + expect(markup).toContain('justify-center') + expect(markup).toContain('flex-initial') + expect(markup).toContain('min-w-0') + expect(markup).not.toContain('flex-none') + expect(markup).not.toMatch(/ align=/) + }) + + it('preserves native refs, focus, submission, disabled actions and link navigation', () => { + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + const button = createRef() + const link = createRef() + let submissions = 0 + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + const render = (disabled: boolean) => + act(() => + root.render( +
{ + event.preventDefault() + submissions++ + }} + > + + Continue + + + Workspace + +
+ ) + ) + try { + render(false) + button.current?.focus() + expect(document.activeElement).toBe(button.current) + act(() => button.current?.click()) + expect(submissions).toBe(1) + expect(link.current?.getAttribute('href')).toBe('/workspace') + render(true) + act(() => button.current?.click()) + expect(submissions).toBe(1) + expect(button.current?.disabled).toBe(true) + } finally { + act(() => root.unmount()) + container.remove() + } + }) +}) diff --git a/packages/emcn/src/components/chip/chip.tsx b/packages/emcn/src/components/chip/chip.tsx index 5b333032762..d8d3fbd2494 100644 --- a/packages/emcn/src/components/chip/chip.tsx +++ b/packages/emcn/src/components/chip/chip.tsx @@ -14,17 +14,18 @@ import { OverflowText, overflowTextClipClass } from '../overflow-text/overflow-t import { chipActiveSurfaceClass, chipBorderShadowRing, + chipContentGeometryClass, chipContentIconClass, chipContentLabelClass, chipFilledFillTokens, - chipGeometryUnroundedClass, chipHoverSurfaceClass, chipPrimaryFillTokens, chipRadiusClass, + chipSizeClasses, } from './chip-chrome' /** - * 30px pill — the platform's most common chrome pattern. + * 30px pill (36px with `size="lg"` at the default root font size) — the platform's most common chrome pattern. * * Render targets: * - {@link Chip} → ` diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx index 958b6b893bf..59505ceb97a 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx @@ -58,7 +58,7 @@ const LANG_ALIASES: Record = { const PROSE_CLASSES = cn( 'prose prose-base dark:prose-invert max-w-none', - 'font-[family-name:var(--font-inter)] antialiased break-words tracking-[0]', + 'font-inter antialiased break-words tracking-[0]', 'prose-headings:font-semibold prose-headings:tracking-[0] prose-headings:text-[var(--text-primary)]', 'prose-headings:mb-3 prose-headings:mt-6 first:prose-headings:mt-0', 'prose-p:text-base prose-p:leading-[25px] prose-p:text-[var(--text-primary)]', diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/options/options.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/options/options.tsx index 080edad2af7..33d543ace0e 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/options/options.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/options/options.tsx @@ -15,7 +15,7 @@ export function Options({ items, onSelect }: OptionsProps) { key={item.id} type='button' onClick={() => onSelect?.(item.id)} - className='rounded-full border border-[var(--border)] bg-[var(--bg)] px-3.5 py-1.5 font-[family-name:var(--font-inter)] text-[var(--text-primary)] text-sm leading-5 transition-colors hover-hover:bg-[var(--surface-5)]' + className='rounded-full border border-[var(--border)] bg-[var(--bg)] px-3.5 py-1.5 font-inter text-[var(--text-primary)] text-sm leading-5 transition-colors hover-hover:bg-[var(--surface-5)]' > {item.label} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx index fdd9ed5c7ee..6f39702465d 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx @@ -1095,7 +1095,9 @@ function MessageContentInner({ <>
- Stopped by user + + Stopped by user +
{actions &&
{actionsRow}
} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index 5b6560b4eae..ed9ac8e2711 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -669,7 +669,9 @@ function EmbeddedWorkflow({ workspaceId, workflowId }: EmbeddedWorkflowProps) {
-

Workflow not found

+

+ Workflow not found +

This workflow may have been deleted or moved

@@ -732,7 +734,7 @@ function EmbeddedFile({
-

File not found

+

File not found

This file may have been deleted or moved

@@ -783,7 +785,7 @@ function EmbeddedFolder({ workspaceId, folderId }: EmbeddedFolderProps) {
-

Folder not found

+

Folder not found

This folder may have been deleted or moved

@@ -794,7 +796,7 @@ function EmbeddedFolder({ workspaceId, folderId }: EmbeddedFolderProps) { return (
-

{folder.name}

+

{folder.name}

{folderWorkflows.length === 0 ? (

No workflows in this folder

) : ( @@ -841,7 +843,7 @@ function EmbeddedLog({ workspaceId, logId, onNotFound }: EmbeddedLogProps) {
-

Log not found

+

Log not found

This log may have been deleted or is no longer available

diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts b/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts index 088064324b1..32cfd395b83 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts @@ -59,12 +59,12 @@ export interface PlusMenuHandle { * Box and typography shared by the textarea and its mirror overlay — both must * produce identical line wrapping so the overlay text sits exactly over the * (transparent) textarea text. The scale is the chat input's native prompt - * scale (`text-[14px]`, `-0.015em` tracking); the task modal's body inherits it + * scale (`text-sm`, 14px at the default root, `-0.015em` tracking); the task modal's body inherits it * so the editor reads the same whether it's the chat input or inside the modal. */ const FIELD_MIRROR_CLASSES = cn( 'm-0 box-border min-h-[24px] w-full [overflow-wrap:anywhere] border-0 bg-transparent', - 'px-1 py-1 font-body text-[14px] leading-[24px] tracking-[-0.015em]' + 'px-1 py-1 font-body text-sm leading-[24px] tracking-[-0.015em]' ) /** diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/user-message-content/user-message-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/user-message-content/user-message-content.tsx index d807f00a5be..67359e00939 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/user-message-content/user-message-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/user-message-content/user-message-content.tsx @@ -8,10 +8,10 @@ import type { ChatMessageContext } from '@/app/workspace/[workspaceId]/home/type import { getIntegrationMatcher } from '@/blocks/integration-matcher' const USER_MESSAGE_CLASSES = - 'whitespace-pre-wrap [overflow-wrap:anywhere] font-[family-name:var(--font-inter)] text-base text-[var(--text-primary)] leading-[23px] tracking-[0] antialiased' + 'whitespace-pre-wrap [overflow-wrap:anywhere] font-inter text-base text-[var(--text-primary)] leading-[23px] tracking-[0] antialiased' const COMPACT_CLASSES = - 'truncate text-small leading-[20px] font-[family-name:var(--font-inter)] text-[var(--text-primary)] tracking-[0] antialiased' + 'truncate text-small leading-[20px] font-inter text-[var(--text-primary)] tracking-[0] antialiased' interface UserMessageContentProps { content: string diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index be6316624d4..cc387d5367e 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -675,7 +675,7 @@ function HomeContent({ chatId, userName, userId }: HomeProps) {
{/* Asymmetric padding biases the group up so the full cluster (heading + input + suggestions) sits at the optical center */}
-

+

What should we get done{firstName ? `, ${firstName}` : ''}?

diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx index 77303d43160..cc03ba5be29 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx @@ -445,7 +445,7 @@ export function Admin() {
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx index f040e922466..0578a08a50d 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx @@ -4698,7 +4698,9 @@ export function TableGrid({
-

Table not found

+

+ Table not found +

This table may have been deleted or moved

diff --git a/apps/sim/app/workspace/[workspaceId]/upgrade/components/plan-card/plan-card.tsx b/apps/sim/app/workspace/[workspaceId]/upgrade/components/plan-card/plan-card.tsx index c14dfdeeda4..90e13df381c 100644 --- a/apps/sim/app/workspace/[workspaceId]/upgrade/components/plan-card/plan-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/upgrade/components/plan-card/plan-card.tsx @@ -78,13 +78,15 @@ export function UpgradePlanCard({ >
-

{name}

+

{name}

{bannerText && {bannerText}}
- {price} + + {price} + {discountLabel && {discountLabel}}

{priceSubtext ?? '\u00A0'}

diff --git a/apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx b/apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx index 7ec5cadb581..a69dd3eea4f 100644 --- a/apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx +++ b/apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx @@ -192,7 +192,7 @@ export function Upgrade({ workspaceId }: UpgradeProps) {
-

+

{header}

{state.showUpgradePlans && ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx index d6a91f24477..ab22cdac69f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx @@ -131,7 +131,7 @@ function ChatFilePreview({ file, onRemove }: ChatFilePreviewProps) { ) : (
{file.name}
-
{formatFileSize(file.size)}
+
{formatFileSize(file.size)}
)} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx index 3d2e7e8b88a..f8cc36b1ef6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx @@ -208,7 +208,7 @@ export function CommandList() { {/* Right side: Keyboard Shortcut */}
) : ( {renderMarkedName(name ?? '', nameSearchRange)} diff --git a/packages/workflow-renderer/src/subflow/subflow-node-view.tsx b/packages/workflow-renderer/src/subflow/subflow-node-view.tsx index 4886960c4fb..8fe376b8a8e 100644 --- a/packages/workflow-renderer/src/subflow/subflow-node-view.tsx +++ b/packages/workflow-renderer/src/subflow/subflow-node-view.tsx @@ -620,7 +620,7 @@ export function SubflowNodeView({ >
diff --git a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx index cae87070f2f..8cd8d87a2ce 100644 --- a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx +++ b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx @@ -772,7 +772,7 @@ export function WorkflowBlockView({ diff --git a/packages/workflow-renderer/src/workflow-type.tsx b/packages/workflow-renderer/src/workflow-type.tsx index 22ed75db83e..80f8f11b9c9 100644 --- a/packages/workflow-renderer/src/workflow-type.tsx +++ b/packages/workflow-renderer/src/workflow-type.tsx @@ -169,7 +169,7 @@ export function BlockTileView({ /> ) : ( fallbackLabel && ( - {fallbackLabel} + {fallbackLabel} ) )}