UNPKG

@payfit/unity-components

Version:

394 lines (294 loc) 11.1 kB
--- name: unity-layout-and-styling description: > Load when composing Unity layouts or styling with uy: Tailwind utilities. Use it for Flex/Grid/Text choices, class merging/variants, and validating Unity token names before adding styles. type: core library: '@payfit/unity-components, @payfit/unity-themes' library_version: '2.x' sources: - 'PayFit/hr-apps:libs/shared/unity/components/src/components/flex/Flex.tsx' - 'PayFit/hr-apps:libs/shared/unity/components/src/components/grid/Grid.tsx' - 'PayFit/hr-apps:libs/shared/unity/components/src/components/text/Text.tsx' - 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/tailwind-merge.ts' - 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/tailwind-variants.ts' - 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/cn.ts' - 'PayFit/hr-apps:libs/shared/unity/themes/src/scripts/build.ts' --- Layout primitives, the `uy:` utility-class system, and the variant/merge tools used inside `@payfit/unity-components`. ## Setup ```tsx import { Card, Flex, Grid, Text } from '@payfit/unity-components' export function PayslipSummary() { return ( <Card> <Flex direction="col" gap="200" className="uy:p-300"> <Text variant="h3" asElement="h2"> Payslip </Text> <Grid cols={12} className="uy:gap-200 uy:md:gap-300"> <Flex direction="col" className="uy:col-span-12 uy:md:col-span-6"> <Text variant="overline">Gross</Text> <Text variant="bodyLargeStrong">€ 4,200.00</Text> </Flex> <Flex direction="col" className="uy:col-span-12 uy:md:col-span-6"> <Text variant="overline">Net</Text> <Text variant="bodyLargeStrong">€ 3,150.00</Text> </Flex> </Grid> </Flex> </Card> ) } ``` ## Core Patterns ### Flex for 1D, Grid for 2D `Flex` is for one-dimensional rows/columns; `Grid` is for the 12-column (or 6-column) two-dimensional layout. Each exposes layout props; everything else goes via `className` with `uy:` utilities. ```tsx import { Flex, FlexItem, Grid, GridItem } from '@payfit/unity-components' // Flex props: asElement, inline, direction, isReversed, wrap, // gap, gapX, gapY, justify, align, alignContent, className <Flex direction="row" gap="200" justify="between" align="center"> <FlexItem grow="1">Left</FlexItem> <FlexItem>Right</FlexItem> </Flex> // Grid props: asElement, inline, cols (6 | 12), rows, areas, flow, // justifyItems, alignItems, className // GridItem positions via colSpan/colStart/colEnd OR area (mutually exclusive) <Grid cols={12} className="uy:gap-200"> <GridItem colSpan={8}>Main</GridItem> <GridItem colSpan={4}>Aside</GridItem> </Grid> ``` ### Responsive classes via uy:md: There is no responsive prop-object API. Responsive behavior is driven by TailwindCSS v4 modifiers on `className`. ```tsx <Flex gap="100" className="uy:md:gap-200 uy:lg:gap-300"> <span>Item</span> </Flex> <Grid cols={12} className="uy:grid-cols-1 uy:md:grid-cols-2 uy:lg:grid-cols-3" /> ``` ### Variants with uyTv `uyTv` from `@payfit/unity-themes` is the pre-configured tailwind-variants factory. It applies the Unity `twMergeConfig` so variant collisions resolve against Unity tokens. Export the variant function and derive its typed props with `VariantProps`. ```tsx import type { VariantProps } from '@payfit/unity-themes' import { uyTv } from '@payfit/unity-themes' export const callout = uyTv({ base: 'uy:inline-flex uy:items-center uy:gap-100 uy:rounded-100 uy:px-200 uy:py-100', variants: { intent: { info: 'uy:bg-surface-primary-default uy:text-content-inverted-default', danger: 'uy:bg-surface-danger-default uy:text-content-inverted-default', neutral: 'uy:bg-surface-neutral-default uy:text-content-neutral-default', }, size: { sm: 'uy:typography-body-small', md: 'uy:typography-body', }, }, defaultVariants: { intent: 'info', size: 'md' }, }) export type CalloutVariantProps = VariantProps<typeof callout> ``` ### Class merging with uyMerge `uyMerge` is `tailwind-merge` configured with Unity's class groups. Use it whenever an external `className` may collide with internal classes. ```tsx import { uyMerge } from '@payfit/unity-themes' uyMerge('uy:p-100', 'uy:p-200') // → 'uy:p-200' uyMerge('uy:bg-surface-primary-default', 'uy:bg-surface-danger-default') // → 'uy:bg-surface-danger-default' uyMerge('uy:p-100', 'uy:px-200', 'uy:py-300') // p, px, py don't collide ``` ### Conditional classes with cn / classNames / clsx `cn`, `classNames`, and `clsx` are aliases for the same Unity-configured helper in `@payfit/unity-themes`. Use them for ad-hoc conditional strings — not for component-scoped variant APIs. ```tsx import { cn } from '@payfit/unity-themes' function Row({ isActive, className, }: { isActive: boolean className?: string }) { return ( <div className={cn( 'uy:flex uy:items-center uy:px-200 uy:py-100', isActive && 'uy:bg-surface-primary-default', className, )} /> ) } ``` ### Typography with `<Text>` Use `<Text variant=...>` instead of a `<div>` + typography class. `Text` picks a semantic element from the variant (e.g. `variant="h1"``<h1>`), applies the typography variant, and exposes `color`, `isTruncated`, `lineClamp`, and `maxWidthCh`. ```tsx import { Text } from '@payfit/unity-components' <Text variant="h1" color="content.primary">Title</Text> <Text variant="body" color="content.neutral">Description</Text> // Override the semantic element when needed: <Text variant="h1" asElement="h2">Visual h1, semantic h2</Text> ``` ### Data-attribute pseudo-states Several Unity components expose internal state via `data-hovered`, `data-pressed`, `data-focus-visible`, etc. Target the component-owned state attribute rather than the native CSS pseudo-state. ```tsx import { ListViewItem } from '@payfit/unity-components' ;<ListViewItem className="uy:data-[hovered=true]:bg-surface-primary-hover" /> ``` ## Common Mistakes ### CRITICAL Use bare Tailwind classes without uy: prefix Wrong: ```tsx <Flex className="flex gap-4 p-3"> … </Flex> ``` Correct: ```tsx <Flex gap="100" className="uy:p-300"> {' '} …{' '} </Flex> ``` Unity CSS is built with `prefix(uy)`; bare classes are not in the compiled stylesheet and produce no styling. Source: themes/src/scripts/build.ts:298 (prefix(uy) import) ### HIGH Pass responsive prop objects (v0.x style) Wrong: ```tsx <Flex gap={{ initial: '100', md: '200' }} /> ``` Correct: ```tsx <Flex gap="100" className="uy:md:gap-200" /> ``` v1.x removed the responsive prop-object API. Use className with uy:md: et al. Source: components/flex/Flex.tsx:33; themes/docs/files/MIGRATION-v1.md ### HIGH Import twMerge from tailwind-merge directly Wrong: ```tsx import { twMerge } from 'tailwind-merge' twMerge('uy:p-100', 'uy:p-200') ``` Correct: ```tsx import { uyMerge } from '@payfit/unity-themes' uyMerge('uy:p-100', 'uy:p-200') // → 'uy:p-200' ``` The unconfigured twMerge has no knowledge of Unity tokens; class conflicts on uy:bg-surface-primary-default vs uy:bg-surface-danger-default are not resolved. Source: themes/src/utils/tailwind-merge.ts:1-7,75-77 ### HIGH Import tv from tailwind-variants directly Wrong: ```tsx import { tv } from 'tailwind-variants' export const button = tv({ base: 'uy:px-200', variants: {...} }) ``` Correct: ```tsx import { uyTv } from '@payfit/unity-themes' export const button = uyTv({ base: 'uy:px-200', variants: {...} }) ``` tv() is unconfigured; uyTv pre-applies the Unity twMergeConfig so variant conflict resolution understands Unity tokens. Source: themes/src/utils/tailwind-variants.ts:48-51 ### MEDIUM Use <div> + typography class instead of <Text> Wrong: ```tsx <div className="uy:typography-h1 uy:text-content-primary">Title</div> ``` Correct: ```tsx <Text variant="h1" color="content.primary"> Title </Text> ``` `<Text variant="h1">` auto-selects the correct semantic element (h1) and applies the Unity typography variant; `<div>` loses the semantics and the variant API. Source: components/text/Text.tsx:60-104,137-139 ### MEDIUM Use uy:hover: when component exposes data-\* state Wrong: ```tsx <ListViewItem className="uy:hover:bg-surface-primary-hover" /> ``` Correct: ```tsx <ListViewItem className="uy:data-[hovered=true]:bg-surface-primary-hover" /> ``` Some Unity components manage state via data-hovered, data-selected, etc. `uy:data-[hovered=true]:` targets the component-managed state and avoids drift. Source: themes/src/scripts/build.ts:303-307 (custom-variant for data attrs) ### HIGH Hallucinate token names that look plausible but do not exist Wrong: ```tsx <div className="uy:bg-primary-500 uy:text-gray-900 uy:border-blue-600" /> ``` Correct: ```tsx // Use Unity's semantic token names (verify against the live class index // in themes docs or the @theme block in dist/css/unity.css): <div className="uy:bg-surface-primary-default uy:text-content-primary uy:border-surface-primary-active" /> ``` Agents generate names that match standard Tailwind conventions but are not in the Unity token set; the class is absent from the compiled stylesheet, the element silently renders with no style. Source: maintainer interview; themes/dist/css/unity.css (@theme block enumerates valid tokens) ### MEDIUM Reach for cn() to compose variant classes when uyTv fits Wrong: ```tsx import { cn } from '@payfit/unity-themes' function Pill({ size, color, }: { size: 'sm' | 'lg' color: 'primary' | 'danger' }) { return ( <span className={cn( 'uy:inline-flex uy:items-center', size === 'sm' && 'uy:px-100 uy:text-xs', size === 'lg' && 'uy:px-200 uy:text-sm', color === 'primary' && 'uy:bg-surface-primary-default', color === 'danger' && 'uy:bg-surface-danger-default', )} /> ) } ``` Correct: ```tsx import type { VariantProps } from '@payfit/unity-themes' import { uyTv } from '@payfit/unity-themes' const pill = uyTv({ base: 'uy:inline-flex uy:items-center', variants: { size: { sm: 'uy:px-100 uy:text-xs', lg: 'uy:px-200 uy:text-sm' }, color: { primary: 'uy:bg-surface-primary-default', danger: 'uy:bg-surface-danger-default', }, }, }) type PillProps = VariantProps<typeof pill> function Pill(props: PillProps) { return <span className={pill(props)} /> } ``` cn() / classNames / clsx are for ad-hoc conditional class strings; component-scoped variant APIs with multiple axes (size × color × intent) belong in `uyTv`, which gives a typed `VariantProps` signature and pre-applied conflict resolution. Source: maintainer interview; themes/src/utils/tailwind-variants.ts ## See also - `unity-find-component` — the decision tree's "React Aria + uy: classes" branch when no Unity component fits. - `unity-contribute-component``uyTv` is the contributor's variant tool. - `unity-themes-tokens-and-docs` — token discipline if you need a new token rather than reusing an existing one.