@@ -740,6 +740,112 @@ export const Disabled = {
740740 } ,
741741} ;
742742
743+ // ── Responsive (Many Items) ──
744+
745+ export const Responsive = {
746+ parameters : {
747+ docs : {
748+ description : {
749+ story :
750+ 'Toggle groups with many items remain usable on narrow viewports. ' +
751+ 'The default behavior uses horizontal scrolling (hidden scrollbar, touch-friendly). ' +
752+ 'The `--wrap` modifier wraps items to multiple rows instead.' ,
753+ } ,
754+ } ,
755+ } ,
756+ render : ( ) => `
757+ <div class="ct-stack" style="--ct-stack-space: var(--space-6); max-width: 320px;">
758+ <div>
759+ <p style="margin: 0 0 var(--space-3); font-size: var(--font-size-sm); color: var(--color-text-secondary);">Default: horizontal scroll (6 items, 320px container)</p>
760+ <div class="ct-toggle-group" role="group" aria-label="Scrollable filters" data-testid="scroll-group">
761+ <button class="ct-toggle-group__item" type="button" aria-pressed="true" tabindex="0">All</button>
762+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Design</button>
763+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Dev</button>
764+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Marketing</button>
765+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Sales</button>
766+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Support</button>
767+ </div>
768+ </div>
769+
770+ <div>
771+ <p style="margin: 0 0 var(--space-3); font-size: var(--font-size-sm); color: var(--color-text-secondary);">Wrap variant (same items, wraps to rows)</p>
772+ <div class="ct-toggle-group ct-toggle-group--wrap" role="group" aria-label="Wrapped filters" data-testid="wrap-group">
773+ <button class="ct-toggle-group__item" type="button" aria-pressed="true" tabindex="0">All</button>
774+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Design</button>
775+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Dev</button>
776+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Marketing</button>
777+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Sales</button>
778+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Support</button>
779+ </div>
780+ </div>
781+
782+ <div>
783+ <p style="margin: 0 0 var(--space-3); font-size: var(--font-size-sm); color: var(--color-text-secondary);">Separated + Wrap (8 items)</p>
784+ <div class="ct-toggle-group ct-toggle-group--separated ct-toggle-group--wrap" role="group" aria-label="Tag filters" data-testid="sep-wrap-group">
785+ <button class="ct-toggle-group__item" type="button" aria-pressed="true" tabindex="0">React</button>
786+ <button class="ct-toggle-group__item" type="button" aria-pressed="true" tabindex="-1">Vue</button>
787+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Angular</button>
788+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Svelte</button>
789+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Solid</button>
790+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Lit</button>
791+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Preact</button>
792+ <button class="ct-toggle-group__item" type="button" aria-pressed="false" tabindex="-1">Qwik</button>
793+ </div>
794+ </div>
795+ </div>` ,
796+ play : async ( { canvasElement } ) => {
797+ const scrollGroup = canvasElement . querySelector ( '[data-testid="scroll-group"]' ) ;
798+ const wrapGroup = canvasElement . querySelector ( '[data-testid="wrap-group"]' ) ;
799+ const sepWrapGroup = canvasElement . querySelector ( '[data-testid="sep-wrap-group"]' ) ;
800+
801+ initToggleGroupKeyboard ( scrollGroup ) ;
802+ initToggleGroupKeyboard ( wrapGroup ) ;
803+ initToggleGroupKeyboard ( sepWrapGroup ) ;
804+ initToggleGroupSelect ( scrollGroup ) ;
805+ initToggleGroupSelect ( wrapGroup ) ;
806+ initToggleGroupSelect ( sepWrapGroup ) ;
807+
808+ // All 6 items exist and are in the DOM (not clipped away)
809+ const scrollItems = scrollGroup . querySelectorAll ( '.ct-toggle-group__item' ) ;
810+ expect ( scrollItems ) . toHaveLength ( 6 ) ;
811+
812+ const wrapItems = wrapGroup . querySelectorAll ( '.ct-toggle-group__item' ) ;
813+ expect ( wrapItems ) . toHaveLength ( 6 ) ;
814+
815+ const sepItems = sepWrapGroup . querySelectorAll ( '.ct-toggle-group__item' ) ;
816+ expect ( sepItems ) . toHaveLength ( 8 ) ;
817+
818+ // Scroll group: overflow-x is auto (scrollable, not clipped)
819+ const scrollStyle = getComputedStyle ( scrollGroup ) ;
820+ expect ( scrollStyle . overflowX ) . toBe ( 'auto' ) ;
821+
822+ // Wrap group: has flex-wrap
823+ const wrapStyle = getComputedStyle ( wrapGroup ) ;
824+ expect ( wrapStyle . flexWrap ) . toBe ( 'wrap' ) ;
825+
826+ // All items are focusable via keyboard navigation
827+ scrollItems [ 0 ] . focus ( ) ;
828+ expect ( scrollItems [ 0 ] ) . toHaveFocus ( ) ;
829+ await userEvent . keyboard ( '{ArrowRight}' ) ;
830+ expect ( scrollItems [ 1 ] ) . toHaveFocus ( ) ;
831+
832+ // Navigate through remaining items and wrap around
833+ for ( let i = 2 ; i < 6 ; i ++ ) {
834+ await userEvent . keyboard ( '{ArrowRight}' ) ;
835+ }
836+ // After 5 ArrowRights total (from index 0): at index 5 (last)
837+ expect ( scrollItems [ 5 ] ) . toHaveFocus ( ) ;
838+ // One more wraps to first
839+ await userEvent . keyboard ( '{ArrowRight}' ) ;
840+ expect ( scrollItems [ 0 ] ) . toHaveFocus ( ) ;
841+
842+ // Wrap group keyboard nav works too
843+ wrapItems [ 0 ] . focus ( ) ;
844+ await userEvent . keyboard ( '{ArrowRight}' ) ;
845+ expect ( wrapItems [ 1 ] ) . toHaveFocus ( ) ;
846+ } ,
847+ } ;
848+
743849// ── All Variants Overview ──
744850
745851export const AllVariants = {
0 commit comments