diff --git a/.changeset/curly-trainers-walk.md b/.changeset/curly-trainers-walk.md new file mode 100644 index 000000000..f6844a834 --- /dev/null +++ b/.changeset/curly-trainers-walk.md @@ -0,0 +1,5 @@ +--- +"@cube-dev/ui-kit": patch +--- + +Fix minor issues with input styling. diff --git a/.changeset/famous-items-shop.md b/.changeset/famous-items-shop.md new file mode 100644 index 000000000..b9c2c29f6 --- /dev/null +++ b/.changeset/famous-items-shop.md @@ -0,0 +1,5 @@ +--- +"@cube-dev/ui-kit": patch +--- + +Expose shouldFocusWrap for ListBox, FilterListBox, and FilterPicker to control whether keyboard navigation should wrap around. diff --git a/src/components/fields/ComboBox/ComboBox.tsx b/src/components/fields/ComboBox/ComboBox.tsx index 63cd732fe..0ee06729e 100644 --- a/src/components/fields/ComboBox/ComboBox.tsx +++ b/src/components/fields/ComboBox/ComboBox.tsx @@ -437,6 +437,7 @@ export const ComboBox = forwardRef(function ComboBox( }} data-size={size} > + {prefix ?
{prefix}
: null} ( {...modAttrs(mods)} data-size={size} /> - {prefix ?
{prefix}
: null}
{suffixPosition === 'before' ? suffix : null} {validationState || isLoading ? ( diff --git a/src/components/fields/DatePicker/DateInputBase.tsx b/src/components/fields/DatePicker/DateInputBase.tsx index 879130ec6..b7e5746b6 100644 --- a/src/components/fields/DatePicker/DateInputBase.tsx +++ b/src/components/fields/DatePicker/DateInputBase.tsx @@ -32,6 +32,11 @@ const DateInputElement = tasty({ role: 'presentation', styles: { ...DEFAULT_INPUT_STYLES, + height: { + '': '(@size-md - 2bw)', + '[data-size="small"]': '(@size-sm - 2bw)', + '[data-size="large"]': '(@size-lg - 2bw)', + }, display: 'flex', flow: 'row', placeItems: 'center start', diff --git a/src/components/fields/FilterListBox/FilterListBox.stories.tsx b/src/components/fields/FilterListBox/FilterListBox.stories.tsx index 6953f9dd4..f854d3051 100644 --- a/src/components/fields/FilterListBox/FilterListBox.stories.tsx +++ b/src/components/fields/FilterListBox/FilterListBox.stories.tsx @@ -135,6 +135,13 @@ const meta: Meta = { defaultValue: { summary: false }, }, }, + shouldFocusWrap: { + control: { type: 'boolean' }, + description: 'Whether keyboard navigation should wrap around', + table: { + defaultValue: { summary: false }, + }, + }, /* State */ isLoading: { diff --git a/src/components/fields/FilterListBox/FilterListBox.tsx b/src/components/fields/FilterListBox/FilterListBox.tsx index 6663bd640..99d115785 100644 --- a/src/components/fields/FilterListBox/FilterListBox.tsx +++ b/src/components/fields/FilterListBox/FilterListBox.tsx @@ -84,8 +84,8 @@ const SearchInputElement = tasty({ ...DEFAULT_INPUT_STYLES, fill: '#clear', padding: { - '': '.5x 1.5x', - prefix: '.5x 1.5x .5x .5x', + '': '0 1.5x', + prefix: '0 1.5x 0 .5x', }, }, }); @@ -194,6 +194,7 @@ export const FilterListBox = forwardRef(function FilterListBox< description, styles, focusOnHover, + shouldFocusWrap, labelSuffix, selectedKey, defaultSelectedKey, @@ -607,7 +608,7 @@ export const FilterListBox = forwardRef(function FilterListBox< const newIndex = currentIndex + direction; if (newIndex >= 0 && newIndex < visibleKeys.length) { nextKey = visibleKeys[newIndex]; - } else { + } else if (shouldFocusWrap) { // Wrap around nextKey = isArrowDown ? visibleKeys[0] @@ -715,6 +716,7 @@ export const FilterListBox = forwardRef(function FilterListBox< focused: isFocused, loading: !!isLoading, searchable: true, + prefix: !!isLoading, ...externalMods, }), [ @@ -784,7 +786,14 @@ export const FilterListBox = forwardRef(function FilterListBox< }; const searchInput = ( - + + {isLoading && ( +
+
+ {isLoading ? : null} +
+
+ )} - {isLoading && ( -
-
- {isLoading ? : null} -
-
- )}
); @@ -856,6 +858,7 @@ export const FilterListBox = forwardRef(function FilterListBox< listRef={listRef} stateRef={listStateRef} listStyles={listStyles} + shouldFocusWrap={shouldFocusWrap} optionStyles={optionStyles} sectionStyles={sectionStyles} headingStyles={headingStyles} diff --git a/src/components/fields/FilterPicker/FilterPicker.stories.tsx b/src/components/fields/FilterPicker/FilterPicker.stories.tsx index 6ba29f12f..1cb4da01d 100644 --- a/src/components/fields/FilterPicker/FilterPicker.stories.tsx +++ b/src/components/fields/FilterPicker/FilterPicker.stories.tsx @@ -161,6 +161,13 @@ const meta: Meta = { defaultValue: { summary: false }, }, }, + shouldFocusWrap: { + control: { type: 'boolean' }, + description: 'Whether keyboard navigation should wrap around', + table: { + defaultValue: { summary: false }, + }, + }, /* State */ isDisabled: { diff --git a/src/components/fields/FilterPicker/FilterPicker.tsx b/src/components/fields/FilterPicker/FilterPicker.tsx index bfca09dcb..37685351a 100644 --- a/src/components/fields/FilterPicker/FilterPicker.tsx +++ b/src/components/fields/FilterPicker/FilterPicker.tsx @@ -1,12 +1,17 @@ -import { FocusableRef } from '@react-types/shared'; -import React, { +import { + Children, + cloneElement, ForwardedRef, forwardRef, + MutableRefObject, ReactElement, ReactNode, + useCallback, + useEffect, useRef, useState, } from 'react'; +import { FocusScope, useKeyboard } from 'react-aria'; import { Section as BaseSection, Item } from 'react-stately'; import { useWarn } from '../../../_internal/hooks/use-warn'; @@ -92,7 +97,7 @@ export interface CubeFilterPickerProps | false; /** Optional ref to access internal ListBox state (from FilterListBox) */ - listStateRef?: React.MutableRefObject; + listStateRef?: MutableRefObject; /** Mods for the FilterPicker */ mods?: Record; } @@ -149,6 +154,7 @@ export const FilterPicker = forwardRef(function FilterPicker( type = 'outline', theme = 'default', labelSuffix, + shouldFocusWrap, children, selectedKey, defaultSelectedKey, @@ -242,7 +248,7 @@ export const FilterPicker = forwardRef(function FilterPicker( const labels: string[] = []; const extractLabels = (nodes: ReactNode): void => { - React.Children.forEach(nodes, (child: any) => { + Children.forEach(nodes, (child: any) => { if (!child || typeof child !== 'object') return; if (child.type === Item) { @@ -267,7 +273,7 @@ export const FilterPicker = forwardRef(function FilterPicker( // Modified extractLabels to track which keys we've processed const extractLabelsWithTracking = (nodes: ReactNode): void => { - React.Children.forEach(nodes, (child: any) => { + Children.forEach(nodes, (child: any) => { if (!child || typeof child !== 'object') return; if (child.type === Item) { @@ -326,7 +332,7 @@ export const FilterPicker = forwardRef(function FilterPicker( multiple: (effectiveSelectedKeys ?? []).map(normalizeKeyValue), }); - React.useEffect(() => { + useEffect(() => { latestSelectionRef.current = { single: effectiveSelectedKey != null @@ -341,7 +347,7 @@ export const FilterPicker = forwardRef(function FilterPicker( }>({ single: null, multiple: [] }); // Function to sort children with selected items on top - const getSortedChildren = React.useCallback(() => { + const getSortedChildren = useCallback(() => { if (!children) return children; // When the popover is **closed** we don't want to trigger any resorting – @@ -396,7 +402,7 @@ export const FilterPicker = forwardRef(function FilterPicker( // Helper function to sort children array const sortChildrenArray = (childrenArray: ReactNode[]): ReactNode[] => { const cloneWithNormalizedKey = (item: any) => - React.cloneElement(item, { + cloneElement(item, { key: normalizeKeyValue(item.key), }); @@ -442,7 +448,7 @@ export const FilterPicker = forwardRef(function FilterPicker( // Create new section with sorted children, preserving React element properly unselected.push( - React.cloneElement(child, { + cloneElement(child, { ...child.props, children: [...selectedItems, ...unselectedItems], }), @@ -464,7 +470,7 @@ export const FilterPicker = forwardRef(function FilterPicker( }; // Sort the children - const childrenArray = React.Children.toArray(children); + const childrenArray = Children.toArray(children); const sortedChildren = sortChildrenArray(childrenArray); // Cache the sorted order when popover opens @@ -485,17 +491,6 @@ export const FilterPicker = forwardRef(function FilterPicker( // We only provide the sorted original children const finalChildren = getSortedChildren(); - // Function to close popover and focus trigger button - const closeAndFocus = React.useCallback((close: () => void) => { - close(); - // Use setTimeout to ensure the popover closes first, then focus the trigger - setTimeout(() => { - if (triggerRef.current) { - triggerRef.current.focus(); - } - }, 0); - }, []); - const renderTriggerContent = () => { // When there is a selection and a custom summary renderer is provided – use it. if (hasSelection && typeof renderSummary === 'function') { @@ -545,7 +540,7 @@ export const FilterPicker = forwardRef(function FilterPicker( // The trigger is rendered as a function so we can access the dialog state const renderTrigger = (state) => { // Track popover open/close state to control sorting - React.useEffect(() => { + useEffect(() => { if (state.isOpen !== isPopoverOpen) { setIsPopoverOpen(state.isOpen); if (!state.isOpen) { @@ -557,6 +552,16 @@ export const FilterPicker = forwardRef(function FilterPicker( } }, [state.isOpen, isPopoverOpen]); + // Add keyboard support for arrow keys to open the popover + const { keyboardProps } = useKeyboard({ + onKeyDown: (e) => { + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !state.isOpen) { + e.preventDefault(); + state.open(); + } + }, + }); + return ( @@ -129,6 +129,6 @@ Each component includes comprehensive documentation: ## Next Steps 1. **Explore Components** - Browse the sidebar to see all available components -2. **Learn Tasty** - Visit the [Tasty documentation](/docs/tasty-api--docs) to master the styling system +2. **Learn Tasty** - Visit the [Tasty documentation](/docs/tasty-documentation--docs) to master the styling system 3. **Build Forms** - Check out [Form documentation](/docs/forms-form--docs) for form development patterns 4. **Accessibility** - Review accessibility guidelines for inclusive design diff --git a/src/stories/Tasty.docs.mdx b/src/stories/Tasty.docs.mdx index 18c7271eb..d03510d20 100644 --- a/src/stories/Tasty.docs.mdx +++ b/src/stories/Tasty.docs.mdx @@ -125,9 +125,9 @@ const FlexibleBox = tasty({ }); // Usage - style properties become direct props - @@ -135,11 +135,11 @@ const FlexibleBox = tasty({ // Equivalent to using styles prop: - Content @@ -155,15 +155,15 @@ const FlexibleBox = tasty({ **Style Prop Priority:** ```jsx // Direct props override styles prop - // Both direct props and styles can be used together - ``` @@ -220,7 +220,7 @@ Configure breakpoints and use responsive arrays: // Use responsive arrays // Large screens (≥1200px): 4x -// Medium screens (640px-1199px): 2x +// Medium screens (640px-1199px): 2x // Small screens (<640px): 1x ``` @@ -258,7 +258,7 @@ flow: 'row dense', // grid-auto-flow: row dense Works across all layout types: ```jsx gap: '2x', // Standard gap -gap: '1x 2x', // Row gap, column gap +gap: '1x 2x', // Row gap, column gap gap: true, // Default: '1x' // Flex/Grid: Uses native CSS gap @@ -272,7 +272,7 @@ gap: true, // Default: '1x' Enhanced syntax with directional modifiers: ```jsx padding: '2x', // All sides -padding: '2x 1x', // Vertical, horizontal +padding: '2x 1x', // Vertical, horizontal padding: '2x top', // Top only padding: '1x left right', // Left and right padding: true, // Default: '1x' @@ -330,7 +330,7 @@ border: 'dashed #danger left right', // Left/right borders border: '#purple', // Color only (uses defaults for width/style) // Default values: -// - Width: '1bw' → 'var(--border-width)' +// - Width: '1bw' → 'var(--border-width)' // - Style: 'solid' // - Color: 'var(--border-color)' // Available styles: none, hidden, dotted, dashed, solid, double, groove, ridge, inset, outset @@ -366,7 +366,7 @@ preset: 'h2 strong', // Bold heading 2 // Available presets: // Headings: h1, h2, h3, h4, h5, h6 -// Text: t1, t2, t2m, t3, t3m, t4, t4m +// Text: t1, t2, t2m, t3, t3m, t4, t4m // Paragraphs: p1, p2, p3, p4 // Main text: m1, m2, m3 // Captions: c1, c2, tag @@ -376,7 +376,7 @@ preset: 'h2 strong', // Bold heading 2 Unified alignment control for flex and grid: ```jsx align: 'center', // align-items & align-content: center -justify: 'space-between', // justify-items & justify-content: space-between +justify: 'space-between', // justify-items & justify-content: space-between place: 'center', // place-items & place-content: center // Sets both *-items and *-content properties @@ -402,13 +402,13 @@ inset: true, // Default: 0 Semantic transition names: ```jsx transition: 'fill 0.2s', // Background transitions -transition: 'fade 0.15s ease-in', // Mask transitions +transition: 'fade 0.15s ease-in', // Mask transitions transition: 'theme 0.3s', // All theme properties transition: 'border 0.2s, radius 0.3s', // Multiple properties // Available semantic names: // - fade: mask -// - fill: background-color +// - fill: background-color // - border: border, box-shadow // - radius: border-radius // - theme: color, background-color, box-shadow, border, border-radius, outline @@ -538,7 +538,7 @@ Style inner elements using `data-element` attributes: const Card = tasty({ styles: { padding: '4x', - + // Sub-element styles Title: { // Styles data-element="Title" preset: 'h3', @@ -602,7 +602,7 @@ const Component = tasty({ // Define custom properties '@local-spacing': '2x', '@theme-color': 'rgb(255 128 0)', - + // Use custom properties padding: '@local-spacing', color: '@theme-color', @@ -732,7 +732,7 @@ styles: { ```jsx // ✅ Use native style prop for dynamic values - @@ -802,8 +802,8 @@ const Button = tasty({ - **Interactivity**: See [Modifiers & State Binding](#modifiers--state-binding) for hover, focus, and custom states - **Theming**: See [Variants & Theming](#variants--theming) for component variations - **Performance**: See [Performance Tips](#performance-tips-) for optimization techniques -- **Create Component Guide**: See [CreateComponent.docs.mdx](/docs/tasty-createcomponent--docs) for component creation patterns +- **Create Component Guide**: See [CreateComponent.docs.mdx](/docs/tasty-create-component--docs) for component creation patterns --- -> **Need Help?** This documentation covers the most important `tasty` features. For additional examples and component creation patterns, see the [Component Creation Guide](/docs/tasty-createcomponent--docs). +> **Need Help?** This documentation covers the most important `tasty` features. For additional examples and component creation patterns, see the [Component Creation Guide](/docs/tasty-create-component--docs).