From 861ded08d0fdf48e0cc723a0a92c8f31afc7d2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Grimm?= Date: Thu, 14 May 2026 20:29:27 -0500 Subject: [PATCH 01/12] feat: add ControlPresentation primitive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A presentational layout shell for input-like controls. Provides border chrome, focus/hover/disabled/readonly/invalid styling, and two slots (startSlot, endSlot) flanking an arbitrary focusable control passed as children. Forwards a ref to the wrapper and forwards click events to the inner control (toggleable via forwardClickToControl). State-driven styling fires from :has() selectors that match three signaling conventions — native HTML, ARIA, and data-attr — on the inner control. The single source of truth is the a11y attributes the control needs anyway. :invalid is deliberately omitted (fires pre-interaction). Spacing is explicit conditional padding: 10px outer on each side by default; left shrinks to 6px when startSlot is present (with 6px gap to control); right shrinks to 4px when endSlot is present (with 6px gap). ControlActionButton ships alongside as a compact 24x24 button variant sized to fit the chrome alongside a 16px icon glyph. Co-Authored-By: Claude Opus 4.7 (1M context) --- .react-compiler.rec.json | 56 ++-- .../control-action-button.tsx | 48 +++ .../control-presentation.module.css | 136 +++++++++ .../control-presentation.stories.tsx | 286 ++++++++++++++++++ .../control-presentation.tsx | 108 +++++++ src/control-presentation/index.ts | 2 + src/index.ts | 1 + src/styles/design-tokens.css | 9 +- 8 files changed, 617 insertions(+), 29 deletions(-) create mode 100644 src/control-presentation/control-action-button.tsx create mode 100644 src/control-presentation/control-presentation.module.css create mode 100644 src/control-presentation/control-presentation.stories.tsx create mode 100644 src/control-presentation/control-presentation.tsx create mode 100644 src/control-presentation/index.ts diff --git a/.react-compiler.rec.json b/.react-compiler.rec.json index 6c27c5cf..ed8cd246 100644 --- a/.react-compiler.rec.json +++ b/.react-compiler.rec.json @@ -1,30 +1,30 @@ { - "recordVersion": 1, - "react-compiler-version": "1.0.0", - "files": { - "src/checkbox-field/checkbox-field.tsx": { - "CompileError": 1 - }, - "src/checkbox-field/use-fork-ref.ts": { - "CompileError": 1 - }, - "src/components/keyboard-shortcut/keyboard-shortcut.tsx": { - "CompileError": 1 - }, - "src/hooks/use-previous/use-previous.ts": { - "CompileError": 1 - }, - "src/menu/menu.tsx": { - "CompileError": 2 - }, - "src/tabs/tabs.tsx": { - "CompileError": 4 - }, - "src/tooltip/tooltip.tsx": { - "CompileError": 1 - }, - "src/utils/common-helpers.ts": { - "CompileError": 2 - } + "recordVersion": 1, + "react-compiler-version": "1.0.0", + "files": { + "src/checkbox-field/checkbox-field.tsx": { + "CompileError": 1 + }, + "src/checkbox-field/use-fork-ref.ts": { + "CompileError": 1 + }, + "src/components/keyboard-shortcut/keyboard-shortcut.tsx": { + "CompileError": 1 + }, + "src/hooks/use-previous/use-previous.ts": { + "CompileError": 1 + }, + "src/menu/menu.tsx": { + "CompileError": 2 + }, + "src/tabs/tabs.tsx": { + "CompileError": 4 + }, + "src/tooltip/tooltip.tsx": { + "CompileError": 1 + }, + "src/utils/common-helpers.ts": { + "CompileError": 2 } -} + } +} \ No newline at end of file diff --git a/src/control-presentation/control-action-button.tsx b/src/control-presentation/control-action-button.tsx new file mode 100644 index 00000000..25c6b0b3 --- /dev/null +++ b/src/control-presentation/control-action-button.tsx @@ -0,0 +1,48 @@ +import * as React from 'react' + +import classNames from 'classnames' + +import { Button, IconButton } from '../button' + +import styles from './control-presentation.module.css' + +import type { ComponentProps } from 'react' + +export type ControlActionButtonProps = + | ({ + children: React.ReactElement + } & Omit, 'variant' | 'size'>) + | ({ + icon?: React.ReactElement + } & Omit, 'variant' | 'size'>) + +/** + * A compact action button intended for `ControlPresentation`'s `endSlot`. Wraps + * Reactist's `Button` / `IconButton` with a 24×24, 3px-radius variant sized to fit + * the field chrome alongside a 16px icon glyph. + */ +export const ControlActionButton = React.forwardRef( + function ControlActionButton({ exceptionallySetClassName, ...props }, ref) { + return 'children' in props ? ( +