Port all Livewire UI components to React - #23
Merged
Merged
Conversation
Configure ESLint 9 flat config with TypeScript and React support, Prettier for consistent formatting, Changesets for version management, and rewrite the CI/CD workflow from PHP/Pest to Node.js with lint, type-check, test, build, and automated npm publishing via Changesets. Closes #1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use npm run format:check in CI instead of direct npx prettier call - Remove redundant @typescript-eslint/eslint-plugin and @typescript-eslint/parser (already provided by typescript-eslint) - Remove unused vitest from react-laravel devDependencies Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ld-tooling-ci-cd enhancement: set up monorepo build tooling and CI/CD pipeline
Build the shared, framework-agnostic design token layer with all values derived from the Livewire component library's DaisyUI/Tailwind configuration. Includes: - Color palette (DaisyUI theme colors + dark mode overrides) - Color resolver utility (resolveColor, resolveContentColor, resolveAllColors) - Spacing scale (0–96 with named aliases) - Typography tokens (families, sizes, weights, line heights, letter spacing, presets) - Border radius scale + DaisyUI component radii - Shadow tokens (elevation, colored, glow + dark mode variants) - Animation tokens (durations, easings, expressive easings) - Glass helpers (base, frosted, liquid, transparent presets with style/className generators) - CSS custom properties stylesheet (importable via @artisanpack-ui/tokens/css) - Tailwind CSS plugin (importable via @artisanpack-ui/tokens/tailwind) - Shared types (DaisyColor, Size, GlassProps, ColorProps, FormFieldProps) - 69 tests across 10 test files React package types now re-export from tokens to avoid duplication. Closes #2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- @changesets/cli 2.27 -> 2.30 - prettier 3.4 -> 3.8 - typescript 5.7 -> 5.9.3 - clsx 2.1.0 -> 2.1.1 - tailwind-merge 2.6 -> 3.5 (major) - vitest 2.1 -> 4.1 (major) - @testing-library/jest-dom 6.6 -> 6.9 - @testing-library/react 16.1 -> 16.3 - @vitejs/plugin-react 4.3 -> 6.0 (major) - jsdom 27 -> 29 (major) - react/react-dom 19.0 -> 19.2 - @inertiajs/react 2.0 -> 3.0 (major) - tailwindcss peer dep updated to >=4.0.0 ESLint stays at 9.x (eslint-plugin-react doesn't support 10 yet). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add missing test assertion for shadows.base in shadow tests - Create GlassInlineStyleOptions type to prevent passing tint/tintOpacity to glassStyles() (tints require CSS pseudo-elements, use glassClassName) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add browser-path tests for color resolver (mock getComputedStyle) - Expand shadowsDark test to cover all elevation levels, not just sm - Add none key to shadowsDark for key parity with shadows - Fix tailwind plugin to match real plugin(handler, config?) API shape - Add TailwindPluginFn and TailwindPluginAPI types for typed contract - Replace Object.values loop with explicit pairwise font size comparisons - Separate glass option types: GlassClassOptions (tint, no dark) vs GlassInlineStyleOptions (dark, no tint) to prevent silent no-ops - Use import.meta.url in tsup config for reliable path resolution Skipped (verified not needed): - react-laravel peerDep: intentional scaffold for future adapter - CSS font-family quoting: values match Tailwind defaults exactly - types.ts GlassPreset import: IS used by GlassProps interface 73 tests pass (up from 69). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skip glass-tint-opacity class when tintOpacity is 0 (was clamping to 10) - Fix JSDoc referencing non-existent artisanPackPlugin export name - Skipped: fontSizes has no 'md' key so test coverage is already complete 74 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The @plugin directive requires a default export which this module doesn't provide. Replaced with a complete tailwind.config.ts usage example. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use a generic R parameter on TailwindPluginFn so createArtisanPackPlugin returns the concrete type from the plugin factory instead of unknown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ckage feat: implement @artisanpack-ui/tokens design token package
Add feedback component category directory with barrel export, tsup entry point, and package.json sub-path export. Create ThemeProvider context and useTheme hook for color scheme management (light/dark/system with media query listener). Export ColorScheme and ThemeContextValue types. Fix test-setup.ts hoisting issue with @testing-library/jest-dom. Add 8 tests covering all ThemeProvider behavior. Closes #3
…ackage-scaffolding Enhancement: Complete base package scaffolding
Port all form components to React with TypeScript and DaisyUI/Tailwind CSS styling: Button, Input, Textarea, Select, Checkbox, Radio, Toggle, Range, ColorPicker, DatePicker, File, Password, Pin (OTP), Editor, RichTextEditor. Each component supports ref forwarding, full TypeScript prop types, DaisyUI color variants, accessibility (ARIA attributes, keyboard navigation), and unit tests. Includes 75 new component tests. Closes #4
- Remove duplicate 'neutral' from Button color prop type - Add 'neutral' to Checkbox, Radio, Pin colorMaps - Fix hintId computation in Checkbox and Toggle (hint && !error) - Add aria-hidden to Input prefix/suffix spans - ColorPicker: notify onChange on random/clear, reset state on clear - File: call onChange in handleDrop for consumer notification - Radio: conditional aria-labelledby when label is present - Pin: add aria-describedby for error, stricter onComplete test assertion
- Button: tooltip now wraps both link and button branches - Checkbox: pass required to input, prevent rest from overriding ARIA - ColorPicker: make action buttons keyboard accessible, notify onChange on clear - File: omit value/defaultValue from props type - Radio: add required to first radio input for native validation - Toggle: add neutral to colorMap
- File: forward required prop to native file input elements - Radio: apply required to first enabled option instead of index 0 - Radio: card variant respects color prop instead of hard-coding primary
- Add aria-invalid to radiogroup fieldset when error exists - Attach forwarded ref to first enabled option instead of index 0
…ents feat: port 15 form components to React
Port the complete layout component category to React with TypeScript and DaisyUI/Tailwind CSS: Card, Modal, Tabs, Accordion, Collapse, Drawer, Dropdown, Divider, Stack, Grid, and Popover. Each component supports ref forwarding, full TypeScript prop types, controlled/uncontrolled state, DaisyUI color and size variants, and accessibility (ARIA attributes, keyboard navigation, focus management). Tabs supports vertical/vertical-right orientation with color and custom class props. Modal uses native <dialog> with focus restoration. Dropdown and Popover use DaisyUI CSS-based show/hide for reliability. Includes 131 unit tests across 11 test files (280 total passing). Closes #5
- Card: discriminated union props (CardDivProps | CardLinkProps) for correct ref/attribute types when link prop is present - Collapse: move aria-expanded from title div to the input elements where assistive tech expects it - Divider: type colorMap as Record<DaisyColor, string>, guard positionMap on label/children presence - Drawer: add focus trap (Tab/Shift+Tab cycling), focus first element on open, restore focus on close, forward aria-label/aria-labelledby to the dialog element - Dropdown: use cloneElement to attach aria/click/keyboard props directly to custom triggers (avoids nested focusable elements), properly type DropdownItem onClick as MouseEventHandler<HTMLButtonElement> - Modal: add aria-labelledby pointing to title heading, always preventDefault on cancel event to prevent DOM/React state desync - Popover: add onFocus/onBlur handlers for hover mode keyboard access, global Escape listener when open, type positionMap with Position type - Stack/Grid: replace dynamic template-string classes with static maps to prevent Tailwind CSS purging - Tabs: remove redundant !tab.disabled guard (button disabled prop already prevents clicks) - Accordion: displayName-based type guard for wrapped Collapse variants - Grid tests: add gapX-only and gapY-only fallback branch coverage - Collapse test: verify aria-expanded on input element
- Accordion: remove radio name prop to preserve checkbox semantics so clicking an open panel in single mode fires onChange and closes it - Collapse: add aria-labelledby on inputs pointing to title element ID so assistive tech announces the control name - Dropdown: target inner <button> in getMenuItemButtons() instead of <li> so keyboard focus lands on the interactive element; compose original trigger handlers with dropdown handlers via cloneElement - Popover: check relatedTarget in mouseLeave to avoid closing when moving between trigger and content; use showDelay for focus handler; remove redundant handlers on content div - Tabs: validate defaultTab/activeTab against selectable tabs with fallback to first enabled tab; use index-based IDs and data attributes for querySelector and aria references - Drawer: filter getFocusableElements for tabIndex >= 0, exclude hidden inputs and aria-hidden/invisible elements; forward aria-describedby to dialog element
- Accordion: use Collapse-relative indices (skip non-Collapse children); isCollapseElement is now a type predicate eliminating casts - Drawer: cancel requestAnimationFrame on cleanup to prevent stale focus; Tab trap reclaims focus when activeElement escapes panel - Dropdown: Escape restores focus to trigger via triggerRef; composed handlers respect defaultPrevented; default trigger is native <button>; role="menuitem" + aria-disabled moved from <li> to inner <button>, <li> gets role="none"; getMenuItemButtons selects button[role=menuitem] - Popover: add persistent prop to skip outside-click/Escape dismiss; extract and compose external onMouseEnter/Leave/Focus/Blur handlers from rest props; cloneElement trigger directly instead of wrapping in synthetic div[role=button]; remove dropdown-hover class to prevent DaisyUI :focus-within/:hover from overriding React state - Tabs: map enabledTabs index back to full tabs array index for querySelector focus targeting
- Popover: add Escape and outside-click dismissal tests; fix z-1 → z-[1] for valid Tailwind z-index utility - Accordion: compose consumer onOpenChange with handleToggle instead of overwriting it - Dropdown: early return in handleMenuKeyDown when items empty; capture triggerRef via event.currentTarget instead of injecting ref; narrow trigger prop to ReactElement | null; fix z-1 → z-[1] - Tabs: type variantMap as Record<Variant, string> and colorMap as Record<DaisyColor, string> for exhaustive compile-time checks; normalize uncontrolled internalTab when tabs change; guard handleSelect to only accept selectable tab names - Drawer: fix forward-Tab trap when activeElement is the panel container itself (activeIndex === -1)
- Accordion: normalize/dedupe currentOpen before state computation,
filter out-of-range indices against Collapse child count
- Drawer: remove unused useCallback import; add contenteditable and
inert support to getFocusableElements; add aria-hidden={!open} on
drawer-side so AT ignores closed panel
- Dropdown: guard setOpen to skip when state unchanged (prevents
duplicate onOpenChange); add JS hover handlers (mouseEnter/Leave)
so hover prop drives React state + ARIA; add Tab key handler in
menu to close without preventing default
- Popover: guard setOpen against duplicate calls; add inline
visibility override on content to prevent DaisyUI :focus-within
from revealing closed content; make hover-mode non-element fallback
focusable (span with tabIndex/role)
- Tabs: add useEffect to sync internalTab when tabs prop changes
and current selection becomes invalid
- Popover: add persistent-blocks-Escape test; stopPropagation on Escape to prevent parent dismissals; remove role="button" from hover-mode fallback span (focus-driven, not activation-driven) - Accordion: enforce single-open invariant — cleanOpen.slice(0,1) when multiple is false - Dropdown: extract and compose consumer onMouseEnter/onMouseLeave with internal hover handlers; gate dropdown-hover CSS class on uncontrolled mode only so controlled state isn't bypassed - Tabs: add JSDoc stability note on tabs prop recommending useMemo
- Popover: add Enter/Space keyboard activation tests for click mode; import MutableRefObject to avoid React namespace reference; stopPropagation on Escape to prevent parent dismissals - Accordion: hoist cleanOpen derivation above handleToggle for clearer reading order - Dropdown: import MutableRefObject; Tab key just closes without refocusing trigger (natural focus flow); Escape stopPropagation on both trigger and menu handlers; sync focusedIndex from document.activeElement when -1; closeFocusTrigger falls back to containerRef querySelector for hover/controlled scenarios
- Accordion: add role="group" on wrapper for screen-reader context - Popover: generate stable contentId via useId and wire aria-controls on all trigger paths pointing to the content div id; detect non-focusable intrinsic elements (div, span, etc.) and inject tabIndex=0 when cloning to ensure keyboard reachability
- Popover: restore focus to trigger on Escape dismiss via containerRef querySelector; replace denylist with allowlist of natively focusable intrinsics (FOCUSABLE_INTRINSICS); detect and wrap React.Fragment triggers in a focusable container; extract wrapInFocusable helper for consistent fallback rendering
- Calendar: fix nested button-in-button by splitting day cell into a
container div with separate day button and event dot buttons as siblings.
Use Record<DaisyColor, string> for type-safe color maps. Add tests for
minDate/maxDate constraints, event overflow indicator, and keyboard
activation on event dots.
- Carousel: clamp current index to valid range when slides shrink. Call
consumer onKeyDown/onTouchStart/onTouchEnd first and respect
defaultPrevented. Skip keyboard navigation when target is an interactive
element (input, button, etc.).
- Table: safely render non-primitive cell values via JSON.stringify instead
of casting to ReactNode which produces [object Object].
- Badge: add falsy value edge case test (value={0}), strengthen ref
assertion to verify HTMLSpanElement instance.
- Calendar: increase event dot touch target (w-4 h-4 wrapper around w-1.5 h-1.5 visual dot), use event.id as stable React key, add Space key assertion to keyboard test. - Carousel: clamp current via useEffect when slides.length changes, compute safeIndex once and use throughout (next/prev/render/indicators), guard window.matchMedia for jsdom compatibility, add anchor tags to isInteractiveTarget, call consumer handlers first with defaultPrevented check, pass safeIndex to renderSlide callback. - Table: preserve non-visible selections in toggleSelectAll/toggleSelect by operating on a copy of selectedKeys. Make sortable headers keyboard- accessible by rendering a native <button> inside <th> instead of making the <th> itself clickable.
- Calendar: sync viewDate when controlled value prop changes via
useEffect. Remove redundant custom onKeyDown from event dot buttons
(native <button> already fires onClick on Enter/Space, preventing
double-firing). Simplify handleEventDotInteraction signature.
- Carousel: clear touchStartRef on defaultPrevented early-return to
prevent stale coordinates causing ghost swipes. Add onTouchCancel
handler. Add anchor tags ('A') to isInteractiveTarget check.
- Table: derive canExpand from expandable && !!renderExpansion so
expansion UI only renders when both props are provided. Gate sort
button rendering on onSort presence to avoid dead controls in tab
order. Wrap JSON.stringify in try/catch (safeStringify) to handle
circular references gracefully.
- Calendar: expand multi-day events (endDate) across all dates in eventsByDate map. Render event dots as non-interactive <span> when onEventClick is not provided (removes dead buttons from tab order). Replace duplicate onEventClick test with controlled value → viewDate sync test. - Carousel: extract onTouchCancel from props to merge with internal handler instead of allowing ...rest to override it. - Table: render boolean cell values as "true"/"false" strings instead of silently swallowing them (React doesn't render booleans).
- Calendar: add role="img" and aria-label to non-interactive event dots so screen readers announce event names. Add tests for multi-day event expansion (endDate) and non-interactive dot rendering when onEventClick is absent. - Table: disable selection checkboxes (both header and per-row) when onSelectionChange is not provided, removing dead controls from the tab order.
…splay-components enhancement: Port Data Display components to React
Add 6 feedback components: Alert (dismissible, controlled/uncontrolled, severity-based aria-live), Loading (6 variants, color/size), Skeleton (text/card/image placeholders), EmptyState (icon, title, description, action, configurable heading tag), ErrorDisplay (retry action, custom icon/label), and Toast (context provider, useToast hook, auto-dismiss with configurable duration, 0 for persistent, max stacking, eviction timer cleanup). Includes 61 unit tests covering all components. Closes #8
- Remove unused createElement import from EmptyState - Fix Skeleton circle to normalize both dimensions from whichever is provided (width-only circle was previously broken) - Make Toast state updater pure by computing evictions before setToasts - Add aria-label="Notifications" to Toast container - Make ToastMessage role conditional: role="alert" for warning/error, role="status" for info/success - Add tests for new behaviors (circle width-only, toast roles, container aria-label)
- Rename EmptyState title/titleAs props to heading/headingAs to avoid collision with native HTML title attribute; omit title from base type so it can still be passed via ...rest for tooltips - Fix Toast show() race condition: use functional setToasts updater with evictedIdsRef instead of reading stale toastsRef.current outside the updater - Add test verifying default 5000ms auto-dismiss duration - Update dev app demo to use new heading prop Closes #8
Verify that headingAs renders the correct semantic element by asserting role='heading' at the expected level. Closes #8
…k-components-to-react Enhancement: Port feedback components to React
Add 5 utility components: Icon, ThemeToggle, Tooltip, Clipboard, and Markdown. Each component includes full TypeScript types, accessibility support (ARIA attributes, keyboard interaction), DaisyUI variant props, and comprehensive unit tests. - Icon: SVG wrapper with size/color/viewBox props and decorative vs labeled modes - ThemeToggle: Cycles light/dark/system using existing useTheme hook - Tooltip: DaisyUI tooltip with aria-describedby on hover/focus - Clipboard: Copy-to-clipboard with success feedback and onCopy callback - Markdown: Built-in lightweight parser with XSS protection and custom render support Closes #9 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix Tooltip bug: tooltip-open class now uses isOpen (computed state) instead of open (prop only), so it toggles on hover/focus - Fix Clipboard: extract onClick from rest props and merge with handleCopy so consumer onClick is not silently overridden - Fix ThemeToggle: extract onClick from rest props and compose with handleToggle to prevent override via spread - Fix Markdown XSS: decode HTML entities (numeric, hex, named) before URL safety check to prevent scheme obfuscation like javascript: - Add tests for tooltip-open hover/focus toggling - Add test for HTML-entity-encoded javascript: URL rejection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tooltip: - Fix accessibility: add sr-only span with role="tooltip" containing real DOM text for screen readers instead of relying on data-tip CSS - Merge aria-describedby with existing values instead of overwriting - Use separate tipId for aria-describedby to avoid id collision - Extract consumer id prop to prevent ...rest from overwriting it Clipboard: - Respect e.defaultPrevented after calling userOnClick so consumers can cancel the copy operation ThemeToggle: - Respect e.defaultPrevented after calling onClickProp - Import MouseEvent type explicitly from react - Map md size to 'btn-md' for consistency with Button component Markdown: - Fix decodeEntities ordering: decode & before numeric entities so &#x3a; sequences are fully resolved for URL safety checks - Add security JSDoc warning on renderMarkdown about dangerouslySetInnerHTML trust boundary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clipboard:
- Map md size to 'btn-md' for consistency with Button/ThemeToggle
Markdown:
- Strip ASCII C0 control characters in isSafeUrl before scheme checks
to prevent bypass via embedded newlines/tabs in URLs
- Narrow MarkdownProps to Omit<..., 'children' | 'dangerouslySetInnerHTML'>
to prevent consumer override of internal HTML injection
- Spread ...rest before component-controlled props so internal
dangerouslySetInnerHTML always takes precedence
ThemeToggle:
- Change aria-label from current-state ("Light mode") to action-oriented
("Switch to dark mode") computed dynamically from the modes array
- Lift nextIndex/nextMode computation out of handler for label access
Tooltip:
- Handle Fragment children by wrapping in a focusable span so event
handlers and ARIA attributes can be attached
- Accept plain ReactNode children (not just ReactElement)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ThemeToggle:
- Show non-actionable label when nextMode equals current colorScheme
(e.g., modes={['dark']} shows "dark mode (current)" not "Switch to")
- Add regression test for single-mode list
Tooltip:
- Move {...rest} spread before data-tip and id so consumer props
cannot override the component-controlled tip text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ggle Prevents consumer props from overriding aria-label, onClick, and type on the ThemeToggle button element. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-components enhancement: Port Utility components to React
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds a full React UI library and tokens package: 40+ components (data, form, layout, navigation, feedback, utility), comprehensive Vitest test suites, a design-tokens system (CSS vars, Tailwind plugin, color resolver, glass helpers), TypeScript typing reorganization, and developer tooling/CI updates (ESLint, Prettier, Changesets, Node-based CI). Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
Fix 17 potential issues: Button anchor forwarding, synthetic event dispatch in ColorPicker/File, Pin controlled sync, RichTextEditor cursor preservation, Toggle a11y, Carousel autoplay stability, Sparkline empty-array guard, Stat zero-change neutrality, Avatar dead code, Divider falsy-children check, Breadcrumbs aria-current, Pagination bounds validation, Sidebar callback stability, SpotlightSearch listener consolidation, use-theme SSR safety. Address ~30 nitpicks: test isolation with vi.stubGlobal, DaisyUI v4 menu-compact→menu-sm, exported consumer types, nested menu a11y roles, hex color casing, Tailwind v4 plugin docs, redundant code removal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 tasks
21 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@artisanpack-ui/tokensdesign token package with colors, spacing, typography, and Tailwind plugin (Create @artisanpack-ui/tokens design token package #2)@artisanpack-ui/reactbase package with component architecture and shared utilities (Set up @artisanpack-ui/react base package scaffolding #3)Test plan
npm run testfrom monorepo root)npm run build)npm run lint)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Accessibility
Chores
Tests