# Tactile UI Components Tactile UI is Planet's portable React control layer. It was extracted from the Admin button, switch, scrollbar, and tooltip work, but the components themselves do not depend on Admin, AntD, Radix, Tailwind, or `an-*` classes. The immediate goal is stable in-repo usage; the structure is intentionally close to something that can later be published as an npm package. ## Design Goals - **Light tactile feel**: controls default to a white or themed surface, thin borders, and external shadow, matching the subtle depth of the Docs theme slider rather than large colored blocks or glow. - **Portable styling**: classes use the `tui-*` prefix and styles live in `frontend/src/components/tactile-ui/styles.css`. - **Low dependency surface**: components assume React and React DOM. Preset icons currently use `lucide-react`, and callers may also pass custom React nodes. - **Theme friendly**: CSS variables expose the default styling surface. Planet pages adapt the library through theme variables and props. - **Clear semantics**: unambiguous actions should prefer icon-only buttons with tooltips; strong-intent actions such as save, confirm, create, and run can keep text. ## Import In this repository: ```tsx import { TactileButton, TactileSwitch, ControlGroup } from '@/components/tactile-ui' import '@/components/tactile-ui/styles.css' ``` After a future package extraction: ```tsx import { TactileButton, TactileSwitch } from '@planet/tactile-ui' import '@planet/tactile-ui/styles.css' ``` ## Theme Tokens Core tokens are exposed as `--tui-*` CSS variables. Product themes should override variables instead of rewriting internal component classes. ```css :root { --tui-surface: #ffffff; --tui-surface-raised-hover: #f8fbff; --tui-border-soft: #d6dfeb; --tui-border-hover: #b8c6d9; --tui-text: #0f172a; --tui-primary: #2563eb; --tui-danger: #dc2626; } [data-theme='dark'] { --tui-surface: #111827; --tui-text: #e5edf8; } ``` Most controls also accept a `tactile` prop for local width, height, radius, background, border, and shadow overrides. Use local overrides for small special cases; use CSS variables for product-wide styling. ## Portals and Dark Theme `TactileTooltip` and the Admin `Dialog`, `Select`, and toast controls that use Tactile UI may render through a portal attached to `document.body`. Those nodes are not descendants of `.admin-theme-root[data-theme='dark']`, so dark tokens cannot rely only on an ancestor selector inside the Admin root. The Admin theme provider mirrors the active theme to `body[data-admin-theme]`. Shared styles need to support both selector paths: ```css [data-theme='dark'] .tui-button, body[data-admin-theme='dark'] .tui-button { --tui-surface: #172033; --tui-text: #e5edf8; } ``` When adding a portal-based control, first check whether it renders into body. If it does, add a `body[data-admin-theme='dark']` branch in that component's style entry, or reuse the already covered `--tui-*` / `--an-*` tokens. Avoid hard-coding a one-off dark modal style, because the same contrast problem can reappear in dropdowns, tooltips, toasts, and confirmation dialogs. ## `TactileButton` The button component covers regular buttons, icon buttons, strong-intent buttons, and link-like buttons. Common props: | Prop | Description | | --- | --- | | `variant` | `neutral`, `primary`, `danger`, `subtle`, or `ghost` | | `size` | `sm`, `md`, `lg`, or `icon` | | `shape` | `square` or `pill` | | `icon` | Preset icon name or a custom React node | | `iconOnly` | Fixed-size icon button; provide `tooltip` or `aria-label` | | `tooltip` | Rendered through a portal and offset away from the cursor | | `loading` | Disables the button and exposes `aria-disabled` | | `tactile` | Overrides size, radius, shadow, background, and dark-mode variables | ```tsx Save ``` `variant="neutral"` defaults to a white tactile button. Colored buttons should still keep the same height and external shadow instead of relying on page-specific CSS overrides. Colored button borders must not use the exact fill color. `primary`, `danger`, and future colored variants should use a lighter border from the same hue, such as `color-mix(in srgb, var(--tui-danger) 64%, white)`. The border still reads as part of the button color, but its visual weight is lower than the fill surface, so red or blue buttons do not look one outline larger than neutral buttons. Hover states should brighten rather than darken: mix a little white into the current fill color, and keep the hover border lighter than the hover fill. ## Icon Presets Preset icons are maintained in `tactileIconPresets`. Feature pages should call icons by semantic name so actions remain consistent across the console. Common semantics: | Name | Use | | --- | --- | | `refresh` | Refresh data | | `save` | Save | | `delete` | Delete | | `trigger` / `collect` | Trigger collection | | `start` / `play` | Start | | `stop` | Stop | | `connect` / `test` | Connectivity check | | `guide` | Credential guide | | `generate` | AI generation | | `reset` | Restore defaults | | `detail` | View details | | `copy` | Copy | | `upload` | Upload | Custom icons are also supported: ```tsx