UNPKG

@zohodesk/components

Version:

Dot UI is a customizable React component library built to deliver a clean, accessible, and developer-friendly UI experience. It offers a growing set of reusable components designed to align with modern design systems and streamline application development

416 lines (314 loc) 18 kB
# Color Helpers Tailwind-inspired color utility classes and a `colorHelper` for DOT components. --- ## File Structure ``` colorHelpers/ ├── background/ │ └── backgroundColor.module.css # Background color utilities ├── text/ │ └── textColor.module.css # Text color utilities ├── border/ │ └── borderColor.module.css # Border color utilities ├── constants/ │ └── index.js # PALETTE_CONFIG (palette → tone mapping) ├── colorHelper.js # getPaletteClasses + getBgClass, getTextClass, getBorderClass └── paletteUtilities.README.md # This file ``` --- ## Available Colors All three CSS files share the same 7 color tones: | Tone | Token Family | Use Case | |------|--------------|----------| | **default** | `--zdt_v1_palette_default_*` | Default/neutral actions | | **grey** | `--zdt_v1_palette_grey_*` | Secondary, muted elements | | **blue** | `--zdt_v1_palette_blue_*` | Information, primary actions | | **green** | `--zdt_v1_palette_green_*` | Success, positive status | | **orange** | `--zdt_v1_palette_orange_*` | Warning, caution | | **red** | `--zdt_v1_palette_red_*` | Error, danger, destructive actions | | **yellow** | `--zdt_v1_palette_yellow_*` | Alerts, highlights | --- ## Available Utilities ### Background (`background/backgroundColor.module.css`) Per color (`default`, `grey`, `blue`, `green`, `orange`, `red`, `yellow`): | Class Pattern | Description | |---------------|-------------| | `.bg-{color}` | Base background | | `.bg-{color}-light` | Light background variant | | `.hover\:bg-{color}:hover` | Hover state | | `.hover\:bg-{color}-light:hover` | Light hover state | | `.focus\:bg-{color}:focus` | Focus state | | `.focus\:bg-{color}-light:focus` | Light focus state | | `.active\:bg-{color}:active` | Active state | | `.active\:bg-{color}-light:active` | Light active state | | `.selected\:bg-{color}` | Selected state | | `.selected\:bg-{color}-light` | Light selected state | **Utility classes:** `.bg-transparent`, `.bg-white`, `.bg-black` ### Text (`text/textColor.module.css`) Per color (`default`, `grey`, `blue`, `green`, `orange`, `red`, `yellow`): | Class Pattern | Description | |---------------|-------------| | `.text-{color}` | Base text color | | `.hover\:text-{color}:hover` | Hover state | | `.focus\:text-{color}:focus` | Focus state | | `.active\:text-{color}:active` | Active state | | `.selected\:text-{color}` | Selected state | **Special:** `.text-brand-filled` (+ hover/focus/active/selected) — uses `--zdt_v1_palette_default_filled_text` for filled default backgrounds. **Utility classes:** `.text-transparent`, `.text-white`, `.text-black` > **Note:** Text has no `-light` variant. The `colorHelper` switches to `filledTextTone` (e.g., `white`) when the background is filled. ### Border (`border/borderColor.module.css`) Per color (`default`, `grey`, `blue`, `green`, `orange`, `red`, `yellow`): | Class Pattern | Description | |---------------|-------------| | `.border-{color}` | Base border | | `.border-{color}-light` | Light border variant (resting state only) | | `.hover\:border-{color}:hover` | Hover state | | `.focus\:border-{color}:focus` | Focus state | | `.active\:border-{color}:active` | Active state | | `.selected\:border-{color}` | Selected state | **Utility classes:** `.border-transparent`, `.border-white`, `.border-black` > **Note:** Border state classes only use the base tone — no `-light` state variants. The `-light` class is available for the resting state only. --- ## Direct CSS Usage (Examples) Import only the CSS modules you need and compose classes directly. ### Example 1: Status Tag (text + background) ```javascript import bgStyle from './background/backgroundColor.module.css'; import textStyle from './text/textColor.module.css'; function StatusTag({ status }) { const className = compileClassNames({ [bgStyle['bg-green-light']]: status === 'success', [bgStyle['bg-red-light']]: status === 'error', [bgStyle['bg-orange-light']]: status === 'warning', [textStyle['text-green']]: status === 'success', [textStyle['text-red']]: status === 'error', [textStyle['text-orange']]: status === 'warning', }); return <span className={className}>{status}</span>; } ``` ### Example 2: Bordered Card (border + background) ```javascript import bgStyle from './background/backgroundColor.module.css'; import borderStyle from './border/borderColor.module.css'; function Card({ children, variant }) { const className = compileClassNames({ [bgStyle['bg-blue-light']]: variant === 'info', [bgStyle['bg-red-light']]: variant === 'error', [borderStyle['border-blue']]: variant === 'info', [borderStyle['border-red']]: variant === 'error', }); return <div className={className}>{children}</div>; } ``` ### Example 3: Interactive Button (text + background + border + hover) ```javascript import bgStyle from './background/backgroundColor.module.css'; import textStyle from './text/textColor.module.css'; import borderStyle from './border/borderColor.module.css'; function Button({ label, variant }) { // Primary: blue bg, white text, blue border const primaryClasses = compileClassNames({ [bgStyle['bg-blue']]: true, [bgStyle['hover:bg-blue']]: true, [textStyle['text-white']]: true, [borderStyle['border-blue']]: true, [borderStyle['hover:border-blue']]: true, }); // Danger: red bg, white text, red border const dangerClasses = compileClassNames({ [bgStyle['bg-red']]: true, [bgStyle['hover:bg-red']]: true, [textStyle['text-white']]: true, [borderStyle['border-red']]: true, [borderStyle['hover:border-red']]: true, }); return <button className={variant === 'danger' ? dangerClasses : primaryClasses}>{label}</button>; } ``` --- ## Using `colorHelper.js` ### Named Exports Three utility functions return individual CSS module classes: ```javascript import { getBgClass, getTextClass, getBorderClass } from './colorHelper'; ``` #### `getBgClass({ state?, tone, isLighter? })` Returns a background CSS class from `backgroundColor.module.css`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `tone` | `string` | Yes | Color tone (`default`, `grey`, `blue`, `green`, `orange`, `red`, `yellow`, `white`, `black`) | | `state` | `string` | No | State prefix (`hover`, `focus`, `active`, `selected`). Omit for base class. | | `isLighter` | `boolean` | No | `true` → appends `-light` to the tone | **Generated key pattern:** `[state:]bg-{tone}[-light]` ```javascript getBgClass({ tone: 'blue' }) // → paletteBgStyle['bg-blue'] getBgClass({ tone: 'blue', isLighter: true }) // → paletteBgStyle['bg-blue-light'] getBgClass({ state: 'hover', tone: 'red' }) // → paletteBgStyle['hover:bg-red'] getBgClass({ state: 'hover', tone: 'red', isLighter: true }) // → paletteBgStyle['hover:bg-red-light'] ``` #### `getTextClass({ state?, textTone })` Returns a text CSS class from `textColor.module.css`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `textTone` | `string` | Yes | Text tone (`brand`, `grey`, `blue`, `green`, `orange`, `red`, `yellow`, `white`, `black`, `brand-filled`) | | `state` | `string` | No | State prefix (`hover`, `focus`, `active`, `selected`). Omit for base class. | > **Note:** `textTone` has no `isLighter` equivalent. Use `filledTextTone` (e.g. `white`) directly when needed. **Generated key pattern:** `[state:]text-{textTone}` ```javascript getTextClass({ textTone: 'green' }) // → paletteTextStyle['text-green'] getTextClass({ state: 'hover', textTone: 'white' }) // → paletteTextStyle['hover:text-white'] getTextClass({ textTone: 'brand-filled' }) // → paletteTextStyle['text-brand-filled'] ``` #### `getBorderClass({ state?, tone, isLighter? })` Returns a border CSS class from `borderColor.module.css`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `tone` | `string` | Yes | Color tone (`default`, `grey`, `blue`, `green`, `orange`, `red`, `yellow`) | | `state` | `string` | No | State prefix (`hover`, `focus`, `active`, `selected`). Omit for base class. | | `isLighter` | `boolean` | No | `true` → appends `-light` to the tone (resting state only by convention) | **Generated key pattern:** `[state:]border-{tone}[-light]` ```javascript getBorderClass({ tone: 'blue' }) // → paletteBorderStyle['border-blue'] getBorderClass({ tone: 'blue', isLighter: true }) // → paletteBorderStyle['border-blue-light'] getBorderClass({ state: 'hover', tone: 'red' }) // → paletteBorderStyle['hover:border-red'] ``` #### Internal Helpers (not exported) - **`toTone(tone, isLighter)`** — returns `'{tone}-light'` if `isLighter` is truthy, else `'{tone}'` - **`withState(state, key)`** — returns `'{state}:{key}'` if `state` is truthy, else `'{key}'` --- ### Default Export: `getPaletteClasses` For components accepting `palette`, `bgAppearance`, `borderAppearance`, and `paletteShade` props. ```javascript import getPaletteClasses from './colorHelper'; ``` #### Palette → Tone Mapping (from `PALETTE_CONFIG`) | Palette | Tone (bg/border) | Text Tone | Filled Text Tone | |---------|-------------------|-----------|-------------------| | `default` | `brand` | `brand` | `brand-filled` | | `primary` | `blue` | `blue` | `white` | | `secondary` | `grey` | `grey` | `white` | | `danger` | `red` | `red` | `white` | | `success` | `green` | `green` | `white` | Unknown palettes return `DUMMY_OBJECT` (empty frozen object). #### Parameters | Parameter | Values | Effect | |-----------|--------|--------| | `palette` | `default` \| `primary` \| `secondary` \| `danger` \| `success` | Selects the color tone config from `PALETTE_CONFIG` | | `bgAppearance` | `'none'` \| `'default'` \| other | `'default'` → base bg + state bg; `'none'` → no bg, no state bg, no filled text logic; other → no base bg but state bg + filled text logic | | `isSelected` | `boolean` | `true` → folds selected-state classes into `bg`, `text`, and `border` (replaces their resting values with the `selected` state class) | | `borderAppearance` | `'none'` \| `'onHover'` \| other | `'none'` → transparent base, no state borders; `'onHover'` → transparent base, with state borders; other → visible base + state borders | | `paletteShade` | `'lighter'` \| other | `'lighter'` → `-light` variant for bg and base border; also keeps text as `textTone` instead of `filledTextTone` | #### Text Tone Logic The text color depends on both `bgAppearance` and `paletteShade`: | Condition | Base text (`text`) | State text (`hoverText`, etc.) | |---|---|---| | `bgAppearance: 'default'` + default shade | `filledTextTone` (e.g. `white`) | `filledTextTone` | | `bgAppearance: 'default'` + `'lighter'` shade | `textTone` (e.g. `blue`) | `textTone` | | `bgAppearance:` other (not `'none'`) + default shade | `textTone` | `filledTextTone` | | `bgAppearance:` other (not `'none'`) + `'lighter'` shade | `textTone` | `textTone` | | `bgAppearance: 'none'` | `textTone` | `textTone` | > **Key insight:** `baseTextTone = isFilledBg && !isLighter ? filledTextTone : textTone`. `stateTextTone = hasBgState && !isLighter ? filledTextTone : textTone`. This means state text can differ from base text when `bgAppearance` is not `'default'` but also not `'none'`. #### Return Value ```javascript { bg, // isSelected → selected bg (if bgAppearance !== 'none'); else base bg (only if bgAppearance === 'default') hoverBg, // getBgClass({ state: 'hover', tone, isLighter }) — if bgAppearance !== 'none' focusBg, // getBgClass({ state: 'focus', tone, isLighter }) — if bgAppearance !== 'none' activeBg, // getBgClass({ state: 'active', tone, isLighter }) — if bgAppearance !== 'none' text, // isSelected → selected text class; else getTextClass({ textTone: baseTextTone }) hoverText, // getTextClass({ state: 'hover', textTone: stateTextTone }) focusText, // getTextClass({ state: 'focus', textTone: stateTextTone }) activeText, // getTextClass({ state: 'active', textTone: stateTextTone }) border, // isSelected → selected border (if borderAppearance !== 'none'); else resting border hoverBorder, // getBorderClass({ state: 'hover', tone }) — if borderAppearance !== 'none' focusBorder, // getBorderClass({ state: 'focus', tone }) — if borderAppearance !== 'none' activeBorder // getBorderClass({ state: 'active', tone }) — if borderAppearance !== 'none' } ``` > **Note:** State border classes always use the base tone (no `isLighter`), even when the resting border uses `-light`. #### Example: Filled Primary Button ```javascript const classes = getPaletteClasses({ palette: 'primary', bgAppearance: 'default', borderAppearance: 'visible', paletteShade: 'default', }); // classes.bg → 'bg-blue' (bgAppearance: 'default' + default shade) // classes.hoverBg → 'hover:bg-blue' // classes.text → 'text-white' (filledTextTone — bgAppearance: 'default' + not lighter) // classes.hoverText → 'hover:text-white' (filledTextTone — hasBgState + not lighter) // classes.border → 'border-blue' (visible appearance) // classes.hoverBorder → 'hover:border-blue' ``` #### Example: Light Danger Tag ```javascript const classes = getPaletteClasses({ palette: 'danger', bgAppearance: 'default', borderAppearance: 'none', paletteShade: 'lighter', }); // classes.bg → 'bg-red-light' (bgAppearance: 'default' + lighter shade) // classes.hoverBg → 'hover:bg-red-light' // classes.text → 'text-red' (textTone — lighter shade overrides filledTextTone) // classes.hoverText → 'hover:text-red' (textTone — lighter shade) // classes.border → 'border-transparent' (borderAppearance: 'none') // classes.hoverBorder → undefined (no state borders with 'none') ``` #### Example: Ghost Secondary Button ```javascript const classes = getPaletteClasses({ palette: 'secondary', bgAppearance: 'none', borderAppearance: 'onHover', paletteShade: 'default', }); // classes.bg → undefined (not filled) // classes.hoverBg → undefined (bgAppearance: 'none') // classes.text → 'text-grey' (textTone — not filled) // classes.hoverText → 'hover:text-grey' (textTone — bgAppearance 'none' → no filled text) // classes.border → 'border-transparent' (onHover → transparent base) // classes.hoverBorder → 'hover:border-grey' (onHover → state borders present) ``` #### Example: Hover-fill Button (bgAppearance: non-standard value) ```javascript const classes = getPaletteClasses({ palette: 'primary', bgAppearance: 'hover-fill', // any value that isn't 'none' or 'default' borderAppearance: 'visible', paletteShade: 'default', }); // classes.bg → undefined (not 'default' → no base bg) // classes.hoverBg → 'hover:bg-blue' (not 'none' → state bg present) // classes.text → 'text-blue' (baseTextTone — not filled, so textTone) // classes.hoverText → 'hover:text-white' (stateTextTone — hasBgState + not lighter → filledTextTone!) // classes.border → 'border-blue' (visible) // classes.hoverBorder → 'hover:border-blue' ``` > This shows a key behavior: the base text is the normal tone, but **state text switches to `filledTextTone`** because the hover/state background will be filled (dark). > **Note on `isSelected`:** Pass `isSelected: true` to fold the selected state into `bg`, `text`, and `border`. Instead of the resting class, each returns its `selected:` variant class. There are no separate `selectedBg`/`selectedText`/`selectedBorder` return keys. --- ## Behavior Summary | Property | Lighter shade (`paletteShade: 'lighter'`) | Default shade | |----------|-------------------------------------------|---------------| | **Background** | Uses `-light` variant (e.g. `bg-blue-light`) | Uses base (e.g. `bg-blue`) | | **Base text** | Always `textTone` (e.g. `text-blue`) | `filledTextTone` when filled (e.g. `text-white`), else `textTone` | | **State text** | Always `textTone` (e.g. `hover:text-blue`) | `filledTextTone` when `bgAppearance !== 'none'` (e.g. `hover:text-white`), else `textTone` | | **Border (base)** | Uses `-light` variant if not transparent (e.g. `border-blue-light`) | Uses base if not transparent (e.g. `border-blue`) | | **Border (states)** | Always base tone (e.g. `hover:border-blue`) | Always base tone | --- ## FAQ **Q: Why do text classes have no `-light` variant?** A: Text on a light background should stay the base color for readability. When the background is filled (dark), text switches to a contrasting tone (e.g. `white`) instead. **Q: Why do border state classes not use `-light`?** A: Border hover/focus/active/selected states always use the full-intensity base tone for visibility, even when the resting border uses a lighter shade. **Q: What happens if I pass an unknown palette to `getPaletteClasses`?** A: It returns `DUMMY_OBJECT` (an empty frozen object), so no classes are applied. **Q: Can I use CSS classes directly without `getPaletteClasses`?** A: Yes. The CSS modules are standalone. `getPaletteClasses` is a convenience for components that accept palette/appearance/shade props. **Q: When does state text differ from base text?** A: When `bgAppearance` is neither `'none'` nor `'filled'` with default (non-lighter) shade. Base text uses `textTone` (colored), but state text uses `filledTextTone` (e.g. `white`) — because the hover/state will show a filled background. --- **Last Updated:** March 2026