7onic 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +228 -58
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10192,6 +10192,9 @@ import { cn } from '@/lib/utils'
10192
10192
  type BreadcrumbContextValue = {
10193
10193
  size?: 'sm' | 'default' | 'lg'
10194
10194
  separator?: React.ReactNode
10195
+ maxItems?: number
10196
+ itemsBeforeCollapse?: number
10197
+ itemsAfterCollapse?: number
10195
10198
  }
10196
10199
 
10197
10200
  const BreadcrumbContext = React.createContext<BreadcrumbContextValue>({})
@@ -10242,17 +10245,17 @@ export interface BreadcrumbProps extends React.ComponentPropsWithoutRef<'nav'> {
10242
10245
  separator?: React.ReactNode
10243
10246
  /** Size of the breadcrumb */
10244
10247
  size?: 'sm' | 'default' | 'lg'
10245
- /** Max items before collapsing (undefined = no collapse) */
10248
+ /** Collapse the middle items into an ellipsis when there are more items than this (undefined = no collapse) */
10246
10249
  maxItems?: number
10247
- /** Items visible before the ellipsis when collapsed */
10250
+ /** Items kept before the ellipsis when collapsed */
10248
10251
  itemsBeforeCollapse?: number
10249
- /** Items visible after the ellipsis when collapsed */
10252
+ /** Items kept after the ellipsis when collapsed */
10250
10253
  itemsAfterCollapse?: number
10251
10254
  }
10252
10255
 
10253
10256
  const Breadcrumb = React.forwardRef<HTMLElement, BreadcrumbProps>(
10254
- ({ className, separator, size = 'default', ...props }, ref) => (
10255
- <BreadcrumbContext.Provider value={{ size, separator }}>
10257
+ ({ className, separator, size = 'default', maxItems, itemsBeforeCollapse = 1, itemsAfterCollapse = 1, ...props }, ref) => (
10258
+ <BreadcrumbContext.Provider value={{ size, separator, maxItems, itemsBeforeCollapse, itemsAfterCollapse }}>
10256
10259
  <nav
10257
10260
  ref={ref}
10258
10261
  aria-label="breadcrumb"
@@ -10267,8 +10270,8 @@ Breadcrumb.displayName = 'Breadcrumb'
10267
10270
  // \u2500\u2500\u2500 BreadcrumbList \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
10268
10271
 
10269
10272
  const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<'ol'>>(
10270
- ({ className, ...props }, ref) => {
10271
- const { size = 'default' } = useBreadcrumbContext()
10273
+ ({ className, children, ...props }, ref) => {
10274
+ const { size = 'default', maxItems, itemsBeforeCollapse = 1, itemsAfterCollapse = 1 } = useBreadcrumbContext()
10272
10275
  const sizeClass = breadcrumbSizeMap[size]
10273
10276
  return (
10274
10277
  <ol
@@ -10281,7 +10284,9 @@ const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWi
10281
10284
  className
10282
10285
  )}
10283
10286
  {...props}
10284
- />
10287
+ >
10288
+ {collapseItems(children, maxItems, itemsBeforeCollapse, itemsAfterCollapse)}
10289
+ </ol>
10285
10290
  )
10286
10291
  }
10287
10292
  )
@@ -10385,6 +10390,61 @@ const BreadcrumbEllipsis = React.forwardRef<HTMLSpanElement, BreadcrumbEllipsisP
10385
10390
  )
10386
10391
  BreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis'
10387
10392
 
10393
+ // \u2500\u2500\u2500 Collapse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
10394
+
10395
+ // Items are often rendered from a list as <Fragment>{separator}{item}</Fragment>,
10396
+ // so fragments are unwrapped before counting.
10397
+ function flattenFragments(children: React.ReactNode): React.ReactNode[] {
10398
+ return React.Children.toArray(children).flatMap((node) =>
10399
+ React.isValidElement<{ children?: React.ReactNode }>(node) && node.type === React.Fragment
10400
+ ? flattenFragments(node.props.children).map((child) =>
10401
+ // Prefix keys so children of different fragments stay unique
10402
+ React.isValidElement(child) ? React.cloneElement(child, { key: \`\${node.key}\${child.key}\` }) : child
10403
+ )
10404
+ : [node]
10405
+ )
10406
+ }
10407
+
10408
+ // Groups children into segments (the separator before an item + the item),
10409
+ // then keeps the first \`before\` and last \`after\` segments around one ellipsis
10410
+ // item. The user's own separators are reused, so custom separators survive.
10411
+ function collapseItems(children: React.ReactNode, maxItems: number | undefined, before: number, after: number) {
10412
+ if (maxItems === undefined) return children
10413
+
10414
+ const nodes = flattenFragments(children)
10415
+ const segments: { separator: React.ReactNode; nodes: React.ReactNode[] }[] = []
10416
+ let pendingSeparator: React.ReactNode = null
10417
+ for (const node of nodes) {
10418
+ const type = React.isValidElement(node) ? node.type : null
10419
+ if (type === BreadcrumbSeparator) {
10420
+ pendingSeparator = node
10421
+ } else if (type === BreadcrumbItem) {
10422
+ segments.push({ separator: pendingSeparator, nodes: [node] })
10423
+ pendingSeparator = null
10424
+ } else if (segments.length > 0) {
10425
+ segments[segments.length - 1].nodes.push(node)
10426
+ } else {
10427
+ segments.push({ separator: null, nodes: [node] })
10428
+ }
10429
+ }
10430
+
10431
+ if (segments.length <= maxItems || before + after >= segments.length) return children
10432
+
10433
+ const head = segments.slice(0, before)
10434
+ const tail = segments.slice(segments.length - after)
10435
+ const render = (list: typeof segments) => list.flatMap((seg) => (seg.separator ? [seg.separator, ...seg.nodes] : seg.nodes))
10436
+ const ellipsisSeparator = segments[before].separator
10437
+
10438
+ return [
10439
+ ...render(head),
10440
+ ...(ellipsisSeparator ? [ellipsisSeparator] : []),
10441
+ <BreadcrumbItem key="breadcrumb-collapsed">
10442
+ <BreadcrumbEllipsis />
10443
+ </BreadcrumbItem>,
10444
+ ...render(tail),
10445
+ ]
10446
+ }
10447
+
10388
10448
  // \u2500\u2500\u2500 Exports \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
10389
10449
 
10390
10450
  export {
@@ -12302,6 +12362,13 @@ const ChatInput = React.forwardRef<HTMLDivElement, ChatInputRootProps>(
12302
12362
  )
12303
12363
  ChatInput.displayName = 'ChatInput'
12304
12364
 
12365
+ // Writes a node into a callback or object ref. Kept outside the component: React
12366
+ // Compiler rules flag assigning \`.current\` on refs that come from props or context.
12367
+ function assignRef<T>(ref: React.Ref<T> | undefined, value: T | null) {
12368
+ if (typeof ref === 'function') ref(value)
12369
+ else if (ref) (ref as React.MutableRefObject<T | null>).current = value
12370
+ }
12371
+
12305
12372
  // ============================================================================
12306
12373
  // ChatInputField \u2014 Auto-resizing textarea
12307
12374
  // ============================================================================
@@ -12336,12 +12403,8 @@ const ChatInputField = React.forwardRef<HTMLTextAreaElement, ChatInputFieldProps
12336
12403
  const setRef = React.useCallback(
12337
12404
  (el: HTMLTextAreaElement | null) => {
12338
12405
  internalRef.current = el
12339
- ctx._fieldRef.current = el
12340
- if (typeof forwardedRef === 'function') {
12341
- forwardedRef(el)
12342
- } else if (forwardedRef) {
12343
- (forwardedRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = el
12344
- }
12406
+ assignRef(ctx._fieldRef, el)
12407
+ assignRef(forwardedRef, el)
12345
12408
  },
12346
12409
  // eslint-disable-next-line react-hooks/exhaustive-deps
12347
12410
  [forwardedRef]
@@ -12749,7 +12812,6 @@ export interface ChatMessageAvatarProps {
12749
12812
  className?: string
12750
12813
  }
12751
12814
 
12752
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
12753
12815
  export interface ChatMessageContentProps extends React.HTMLAttributes<HTMLDivElement> {}
12754
12816
 
12755
12817
  export interface ChatMessageFooterProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -12986,7 +13048,7 @@ import * as React from 'react'
12986
13048
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
12987
13049
  import { cva, type VariantProps } from 'class-variance-authority'
12988
13050
  import { cn } from '@/lib/utils'
12989
- import { useFieldContext } from './field'
13051
+ import { useFieldContext, useFieldControl } from './field'
12990
13052
 
12991
13053
  // Color maps for checked/indeterminate state
12992
13054
  const checkboxColorMap = {
@@ -13068,7 +13130,11 @@ const Checkbox = React.forwardRef<
13068
13130
  const resolvedSize = size || 'default'
13069
13131
  const resolvedDisabled = disabled ?? fieldContext?.disabled
13070
13132
  const generatedId = React.useId()
13071
- const checkboxId = id ?? fieldContext?.id ?? generatedId
13133
+ // With its own label the checkbox is one item of a group: keep a unique id. Only a
13134
+ // bare checkbox labelled by FieldLabel takes the Field id.
13135
+ const hasOwnLabel = Boolean(label)
13136
+ const checkboxId = id ?? (hasOwnLabel ? generatedId : fieldContext?.id ?? generatedId)
13137
+ useFieldControl(hasOwnLabel ? 'group' : 'single', Boolean(fieldContext) && (hasOwnLabel || !id))
13072
13138
 
13073
13139
  const checkbox = (
13074
13140
  <CheckboxPrimitive.Root
@@ -13580,9 +13646,9 @@ import { cn } from '@/lib/utils'
13580
13646
  type DropdownMenuRadius = 'md' | 'lg' | 'xl'
13581
13647
 
13582
13648
  const contentRadiusMap: Record<DropdownMenuRadius, string> = {
13583
- md: 'rounded-md', // 6px
13649
+ md: 'rounded-md', // 6px (default)
13584
13650
  lg: 'rounded-lg', // 8px
13585
- xl: 'rounded-xl', // 12px (default)
13651
+ xl: 'rounded-xl', // 12px
13586
13652
  }
13587
13653
 
13588
13654
  const itemRadiusMap: Record<DropdownMenuRadius, string> = {
@@ -13641,7 +13707,7 @@ const DropdownMenuStyleContext = React.createContext<{
13641
13707
  radius: DropdownMenuRadius
13642
13708
  flush: boolean
13643
13709
  size: DropdownMenuSize
13644
- }>({ radius: 'lg', flush: false, size: 'md' })
13710
+ }>({ radius: 'md', flush: false, size: 'md' })
13645
13711
 
13646
13712
  // Root \u2014 state management (controlled / uncontrolled)
13647
13713
  const DropdownMenu = DropdownMenuPrimitive.Root
@@ -13934,16 +14000,42 @@ import { cn } from '@/lib/utils'
13934
14000
  // Field Context for sharing state between components
13935
14001
  interface FieldContextValue {
13936
14002
  id: string
14003
+ /** id of the FieldLabel, for aria-labelledby on grouped controls */
14004
+ labelId: string
13937
14005
  error?: boolean
13938
14006
  disabled?: boolean
14007
+ /** A FieldLabel is rendered inside this Field */
14008
+ hasLabel: boolean
14009
+ /** The Field holds labelled checkboxes / switches / a radio group and no single input */
14010
+ isGroup: boolean
14011
+ registerLabel: () => () => void
14012
+ registerControl: (kind: FieldControlKind) => () => void
13939
14013
  }
13940
14014
 
14015
+ type FieldControlKind = 'single' | 'group' | 'labelled'
14016
+
13941
14017
  const FieldContext = React.createContext<FieldContextValue | null>(null)
13942
14018
 
13943
14019
  function useFieldContext() {
13944
14020
  return React.useContext(FieldContext)
13945
14021
  }
13946
14022
 
14023
+ // Controls tell their Field what they are, so FieldLabel can point at the right thing.
14024
+ // 'single': one input that takes the Field id (Input, Textarea, a bare Checkbox / Switch);
14025
+ // FieldLabel targets it with htmlFor.
14026
+ // 'group': a Checkbox / Switch with its own label keeps a unique id; the Field becomes
14027
+ // role="group" named by FieldLabel instead of every item sharing one id.
14028
+ // 'labelled': a RadioGroup names itself from FieldLabel (aria-labelledby).
14029
+ // A single input wins: with one present, FieldLabel keeps pointing at it.
14030
+ function useFieldControl(kind: FieldControlKind, enabled: boolean) {
14031
+ const context = useFieldContext()
14032
+ const register = context?.registerControl
14033
+ React.useEffect(() => {
14034
+ if (!enabled || !register) return
14035
+ return register(kind)
14036
+ }, [enabled, register, kind])
14037
+ }
14038
+
13947
14039
  // Field wrapper
13948
14040
  const fieldVariants = cva('flex flex-col', {
13949
14041
  variants: {
@@ -13970,11 +14062,33 @@ export interface FieldProps
13970
14062
  const Field = React.forwardRef<HTMLDivElement, FieldProps>(
13971
14063
  ({ className, gap, error, disabled, children, ...props }, ref) => {
13972
14064
  const id = React.useId()
14065
+ const labelId = \`\${id}-label\`
14066
+ const [labelCount, setLabelCount] = React.useState(0)
14067
+ const [counts, setCounts] = React.useState({ single: 0, group: 0, labelled: 0 })
14068
+
14069
+ const registerLabel = React.useCallback(() => {
14070
+ setLabelCount((n) => n + 1)
14071
+ return () => setLabelCount((n) => n - 1)
14072
+ }, [])
14073
+ const registerControl = React.useCallback((kind: FieldControlKind) => {
14074
+ setCounts((c) => ({ ...c, [kind]: c[kind] + 1 }))
14075
+ return () => setCounts((c) => ({ ...c, [kind]: c[kind] - 1 }))
14076
+ }, [])
14077
+
14078
+ const hasLabel = labelCount > 0
14079
+ const isGroup = counts.single === 0 && (counts.group > 0 || counts.labelled > 0)
14080
+ const asGroup = counts.single === 0 && counts.group > 0
14081
+ const contextValue = React.useMemo(
14082
+ () => ({ id, labelId, error, disabled, hasLabel, isGroup, registerLabel, registerControl }),
14083
+ [id, labelId, error, disabled, hasLabel, isGroup, registerLabel, registerControl]
14084
+ )
13973
14085
 
13974
14086
  return (
13975
- <FieldContext.Provider value={{ id, error, disabled }}>
14087
+ <FieldContext.Provider value={contextValue}>
13976
14088
  <div
13977
14089
  ref={ref}
14090
+ role={asGroup ? 'group' : undefined}
14091
+ aria-labelledby={asGroup && hasLabel ? labelId : undefined}
13978
14092
  className={cn(fieldVariants({ gap }), className)}
13979
14093
  data-error={error || undefined}
13980
14094
  data-disabled={disabled || undefined}
@@ -13996,11 +14110,15 @@ export interface FieldLabelProps extends React.LabelHTMLAttributes<HTMLLabelElem
13996
14110
  const FieldLabel = React.forwardRef<HTMLLabelElement, FieldLabelProps>(
13997
14111
  ({ className, required, children, ...props }, ref) => {
13998
14112
  const context = useFieldContext()
14113
+ const registerLabel = context?.registerLabel
14114
+ React.useEffect(() => registerLabel?.(), [registerLabel])
13999
14115
 
14000
14116
  return (
14001
14117
  <label
14002
14118
  ref={ref}
14003
- htmlFor={context?.id}
14119
+ id={context?.labelId}
14120
+ // Grouped controls have their own labels; the group title targets no input
14121
+ htmlFor={context && !context.isGroup ? context.id : undefined}
14004
14122
  className={cn(
14005
14123
  'text-md font-semibold text-foreground', // 14px - primary field identifier
14006
14124
  context?.disabled && 'opacity-50 cursor-not-allowed',
@@ -14061,7 +14179,7 @@ const FieldCharCount = React.forwardRef<
14061
14179
  })
14062
14180
  FieldCharCount.displayName = 'FieldCharCount'
14063
14181
 
14064
- export { Field, FieldLabel, FieldError, FieldCharCount, useFieldContext }
14182
+ export { Field, FieldLabel, FieldError, FieldCharCount, useFieldContext, useFieldControl }
14065
14183
  `,
14066
14184
  type: "ui"
14067
14185
  }]
@@ -14211,7 +14329,7 @@ export { IconButton, iconButtonVariants }
14211
14329
  import * as React from 'react'
14212
14330
  import { cva, type VariantProps } from 'class-variance-authority'
14213
14331
  import { cn } from '@/lib/utils'
14214
- import { useFieldContext } from './field'
14332
+ import { useFieldContext, useFieldControl } from './field'
14215
14333
 
14216
14334
  const inputVariants = cva(
14217
14335
  [
@@ -14320,6 +14438,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
14320
14438
  const resolvedError = error ?? fieldContext?.error
14321
14439
  const resolvedState = resolvedError ? 'error' : state
14322
14440
  const inputId = id ?? fieldContext?.id
14441
+ useFieldControl('single', Boolean(fieldContext) && !id)
14323
14442
  const isDisabled = props.disabled ?? fieldContext?.disabled
14324
14443
 
14325
14444
  // Keyboard focus detection (when focusRing: false, auto-show ring on Tab navigation)
@@ -15444,10 +15563,13 @@ const NavigationMenuItem = React.forwardRef<HTMLLIElement, NavigationMenuItemPro
15444
15563
  const { orientation, collapsed } = useNavigationMenuContext()
15445
15564
  const [open, setOpen] = React.useState(defaultOpen)
15446
15565
 
15447
- // Auto-close sub-menus when collapsed
15448
- React.useEffect(() => {
15566
+ // Auto-close sub-menus when collapsed. Adjusted during render (React's pattern for
15567
+ // state that follows a prop) instead of in an effect, which re-rendered once more.
15568
+ const [prevCollapsed, setPrevCollapsed] = React.useState(collapsed)
15569
+ if (collapsed !== prevCollapsed) {
15570
+ setPrevCollapsed(collapsed)
15449
15571
  if (collapsed) setOpen(false)
15450
- }, [collapsed])
15572
+ }
15451
15573
 
15452
15574
  if (orientation === 'horizontal') {
15453
15575
  return (
@@ -15765,20 +15887,37 @@ const NavigationMenuGroup = React.forwardRef<HTMLDivElement, NavigationMenuGroup
15765
15887
  const { size, collapsed } = useNavigationMenuContext()
15766
15888
  const s = navigationMenuSizeMap[size]
15767
15889
 
15890
+ const labelId = React.useId()
15891
+ const showLabel = Boolean(label) && !collapsed
15892
+
15893
+ // Groups sit inside NavigationMenuList's <ul>, so the group is wrapped in an <li> to
15894
+ // keep the list structure valid. The spacing class stays on that <li>: it relies on
15895
+ // :first-child among the list's children.
15768
15896
  return (
15769
- <div ref={ref} role="group" className={cn(label && !collapsed && s.groupMargin, className)} {...props}>
15770
- {label && !collapsed && (
15771
- <div className={cn(
15772
- 'font-semibold text-text-subtle uppercase tracking-wider select-none',
15773
- s.groupLabel,
15774
- )}>
15775
- {label}
15776
- </div>
15777
- )}
15778
- <ul className="flex flex-col gap-0.5">
15779
- {children}
15780
- </ul>
15781
- </div>
15897
+ <li className={cn(showLabel && s.groupMargin)}>
15898
+ <div
15899
+ ref={ref}
15900
+ role="group"
15901
+ aria-labelledby={showLabel && !props['aria-label'] ? labelId : undefined}
15902
+ className={className}
15903
+ {...props}
15904
+ >
15905
+ {showLabel && (
15906
+ <div
15907
+ id={labelId}
15908
+ className={cn(
15909
+ 'font-semibold text-text-subtle uppercase tracking-wider select-none',
15910
+ s.groupLabel,
15911
+ )}
15912
+ >
15913
+ {label}
15914
+ </div>
15915
+ )}
15916
+ <ul className="flex flex-col gap-0.5">
15917
+ {children}
15918
+ </ul>
15919
+ </div>
15920
+ </li>
15782
15921
  )
15783
15922
  }
15784
15923
  )
@@ -16474,11 +16613,15 @@ PaginationEllipsis.displayName = 'PaginationEllipsis'
16474
16613
 
16475
16614
  // \u2500\u2500\u2500 PaginationItems (Convenience) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
16476
16615
 
16616
+ // Renders the <li> items straight into PaginationContent's <ul>: a wrapper element
16617
+ // between them breaks the list structure for assistive technology (a \`display:
16618
+ // contents\` div used to sit here). The div props and ref stay in the signature for
16619
+ // compatibility but have no element to attach to.
16477
16620
  const PaginationItems = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
16478
- ({ className, ...props }, ref) => {
16621
+ (_props, _ref) => {
16479
16622
  const ctx = usePaginationContext()
16480
16623
  return (
16481
- <div ref={ref} className={cn('contents', className)} {...props}>
16624
+ <>
16482
16625
  {ctx.paginationRange.map((item, index) => (
16483
16626
  <PaginationItem key={\`\${item}-\${index}\`}>
16484
16627
  {item === 'dots' ? (
@@ -16488,7 +16631,7 @@ const PaginationItems = React.forwardRef<HTMLDivElement, React.ComponentPropsWit
16488
16631
  )}
16489
16632
  </PaginationItem>
16490
16633
  ))}
16491
- </div>
16634
+ </>
16492
16635
  )
16493
16636
  }
16494
16637
  )
@@ -17205,7 +17348,7 @@ import * as React from 'react'
17205
17348
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
17206
17349
  import { cva } from 'class-variance-authority'
17207
17350
  import { cn } from '@/lib/utils'
17208
- import { useFieldContext } from './field'
17351
+ import { useFieldContext, useFieldControl } from './field'
17209
17352
 
17210
17353
  // Color maps for checked state
17211
17354
  const radioColorMap = {
@@ -17257,6 +17400,9 @@ const RadioGroup = React.forwardRef<
17257
17400
  >(({ className, orientation, size = 'default', weight = 'bold', color = 'default', disabled, ...props }, ref) => {
17258
17401
  const fieldContext = useFieldContext()
17259
17402
  const resolvedDisabled = disabled ?? fieldContext?.disabled
17403
+ // Inside a Field the FieldLabel names the radio group (items keep their own labels)
17404
+ useFieldControl('labelled', Boolean(fieldContext))
17405
+ const labelledBy = fieldContext?.hasLabel && !props['aria-label'] ? fieldContext.labelId : undefined
17260
17406
 
17261
17407
  return (
17262
17408
  <RadioGroupContext.Provider value={{ size, weight, color, disabled: resolvedDisabled }}>
@@ -17265,6 +17411,7 @@ const RadioGroup = React.forwardRef<
17265
17411
  className={cn(radioGroupVariants({ orientation }), className)}
17266
17412
  orientation={orientation || undefined}
17267
17413
  disabled={resolvedDisabled}
17414
+ aria-labelledby={labelledBy}
17268
17415
  {...props}
17269
17416
  />
17270
17417
  </RadioGroupContext.Provider>
@@ -17605,7 +17752,7 @@ export { Segmented, SegmentedItem, segmentedVariants, segmentedItemVariants }
17605
17752
  "select": {
17606
17753
  name: "select",
17607
17754
  dependencies: ["@radix-ui/react-select"],
17608
- registryDependencies: [],
17755
+ registryDependencies: ["field"],
17609
17756
  reverseDependencies: [],
17610
17757
  namespace: false,
17611
17758
  description: "",
@@ -17616,6 +17763,7 @@ export { Segmented, SegmentedItem, segmentedVariants, segmentedItemVariants }
17616
17763
  import * as React from 'react'
17617
17764
  import * as SelectPrimitive from '@radix-ui/react-select'
17618
17765
  import { cn } from '@/lib/utils'
17766
+ import { useFieldContext, useFieldControl } from './field'
17619
17767
 
17620
17768
  // Radius (matches Input \u2014 set at Root, propagates to Trigger and Content)
17621
17769
  type SelectRadius = 'none' | 'sm' | 'base' | 'default' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'
@@ -17744,19 +17892,24 @@ const SelectStyleContext = React.createContext<{
17744
17892
  flush: boolean
17745
17893
  }>({ contentRadius: 'xl', itemSize: 'md', flush: false })
17746
17894
 
17747
- // Root \u2014 state management + size/radius context
17895
+ // Root \u2014 state management + size/radius context. Inside a Field it inherits the
17896
+ // Field's disabled state, like Input and Textarea.
17748
17897
  const Select = ({
17749
17898
  size = 'default',
17750
17899
  radius = 'default',
17900
+ disabled,
17751
17901
  ...props
17752
17902
  }: React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root> & {
17753
17903
  size?: SelectSize
17754
17904
  radius?: SelectRadius
17755
- }) => (
17756
- <SelectRootContext.Provider value={{ size, radius }}>
17757
- <SelectPrimitive.Root {...props} />
17758
- </SelectRootContext.Provider>
17759
- )
17905
+ }) => {
17906
+ const fieldContext = useFieldContext()
17907
+ return (
17908
+ <SelectRootContext.Provider value={{ size, radius }}>
17909
+ <SelectPrimitive.Root disabled={disabled ?? fieldContext?.disabled} {...props} />
17910
+ </SelectRootContext.Provider>
17911
+ )
17912
+ }
17760
17913
 
17761
17914
  // Value \u2014 displays selected value or placeholder
17762
17915
  const SelectValue = SelectPrimitive.Value
@@ -17768,11 +17921,15 @@ const SelectGroup = SelectPrimitive.Group
17768
17921
  const SelectTrigger = React.forwardRef<
17769
17922
  React.ElementRef<typeof SelectPrimitive.Trigger>,
17770
17923
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
17771
- >(({ className, children, ...props }, ref) => {
17924
+ >(({ className, children, id, ...props }, ref) => {
17772
17925
  const { size, radius } = React.useContext(SelectRootContext)
17926
+ // Inside a Field the trigger takes the Field id, so FieldLabel names it
17927
+ const fieldContext = useFieldContext()
17928
+ useFieldControl('single', Boolean(fieldContext) && !id)
17773
17929
  return (
17774
17930
  <SelectPrimitive.Trigger
17775
17931
  ref={ref}
17932
+ id={id ?? fieldContext?.id}
17776
17933
  className={cn(
17777
17934
  'flex w-full items-center justify-between bg-background text-foreground hover:bg-background-muted',
17778
17935
  'border border-border',
@@ -18154,7 +18311,7 @@ export { Skeleton, skeletonVariants }
18154
18311
  "slider": {
18155
18312
  name: "slider",
18156
18313
  dependencies: ["@radix-ui/react-slider"],
18157
- registryDependencies: [],
18314
+ registryDependencies: ["field"],
18158
18315
  reverseDependencies: [],
18159
18316
  namespace: false,
18160
18317
  description: "",
@@ -18166,6 +18323,7 @@ import * as React from 'react'
18166
18323
  import * as SliderPrimitive from '@radix-ui/react-slider'
18167
18324
  import { cva, type VariantProps } from 'class-variance-authority'
18168
18325
  import { cn } from '@/lib/utils'
18326
+ import { useFieldContext, useFieldControl } from './field'
18169
18327
 
18170
18328
  // Slider root variants
18171
18329
  const sliderVariants = cva(
@@ -18241,8 +18399,13 @@ export interface SliderProps
18241
18399
  const Slider = React.forwardRef<
18242
18400
  React.ElementRef<typeof SliderPrimitive.Root>,
18243
18401
  SliderProps
18244
- >(({ className, size, color = 'default', value, defaultValue, onValueChange, showTooltip = 'never', formatLabel, startContent, endContent, orientation, ...props }, ref) => {
18402
+ >(({ className, size, color = 'default', value, defaultValue, onValueChange, showTooltip = 'never', formatLabel, startContent, endContent, orientation, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }, ref) => {
18245
18403
  const resolvedSize = size || 'default'
18404
+ // The thumbs carry role="slider", so the accessible name goes on them, not on the
18405
+ // Root. Inside a Field the FieldLabel names the slider unless a name is given.
18406
+ const fieldContext = useFieldContext()
18407
+ useFieldControl('labelled', Boolean(fieldContext))
18408
+ const thumbLabelledBy = ariaLabelledBy ?? (!ariaLabel && fieldContext?.hasLabel ? fieldContext.labelId : undefined)
18246
18409
  const thumbCount = value?.length ?? defaultValue?.length ?? 1
18247
18410
  const hasTooltip = showTooltip !== 'never'
18248
18411
  const hasWrapper = !!(startContent || endContent)
@@ -18295,6 +18458,8 @@ const Slider = React.forwardRef<
18295
18458
  {Array.from({ length: thumbCount }).map((_, i) => (
18296
18459
  <SliderPrimitive.Thumb
18297
18460
  key={i}
18461
+ aria-label={ariaLabel}
18462
+ aria-labelledby={thumbLabelledBy}
18298
18463
  className={cn(
18299
18464
  'group/thumb block rounded-full border-2 shadow-sm', sliderColorMap[color].thumb,
18300
18465
  isVertical
@@ -18988,7 +19153,7 @@ import * as React from 'react'
18988
19153
  import * as SwitchPrimitive from '@radix-ui/react-switch'
18989
19154
  import { cva, type VariantProps } from 'class-variance-authority'
18990
19155
  import { cn } from '@/lib/utils'
18991
- import { useFieldContext } from './field'
19156
+ import { useFieldContext, useFieldControl } from './field'
18992
19157
 
18993
19158
  // Switch track variants
18994
19159
  const switchVariants = cva(
@@ -19089,7 +19254,11 @@ const Switch = React.forwardRef<
19089
19254
  const resolvedSize = size || 'default'
19090
19255
  const resolvedDisabled = disabled ?? fieldContext?.disabled
19091
19256
  const generatedId = React.useId()
19092
- const switchId = id ?? fieldContext?.id ?? generatedId
19257
+ // With its own label the switch is one item of a group: keep a unique id. Only a
19258
+ // bare switch labelled by FieldLabel takes the Field id.
19259
+ const hasOwnLabel = Boolean(label || startLabel || endLabel)
19260
+ const switchId = id ?? (hasOwnLabel ? generatedId : fieldContext?.id ?? generatedId)
19261
+ useFieldControl(hasOwnLabel ? 'group' : 'single', Boolean(fieldContext) && (hasOwnLabel || !id))
19093
19262
 
19094
19263
  const switchEl = (
19095
19264
  <SwitchPrimitive.Root
@@ -19801,7 +19970,7 @@ export {
19801
19970
  import * as React from 'react'
19802
19971
  import { cva, type VariantProps } from 'class-variance-authority'
19803
19972
  import { cn } from '@/lib/utils'
19804
- import { useFieldContext } from './field'
19973
+ import { useFieldContext, useFieldControl } from './field'
19805
19974
 
19806
19975
  const textareaVariants = cva(
19807
19976
  [
@@ -19882,6 +20051,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
19882
20051
  const resolvedError = error ?? fieldContext?.error
19883
20052
  const resolvedState = resolvedError ? 'error' : state
19884
20053
  const textareaId = id ?? fieldContext?.id
20054
+ useFieldControl('single', Boolean(fieldContext) && !id)
19885
20055
  const isDisabled = props.disabled ?? fieldContext?.disabled
19886
20056
 
19887
20057
  // Keyboard focus detection (when focusRing: false, auto-show ring on Tab navigation)
@@ -21727,7 +21897,7 @@ function showCompanionHints(names) {
21727
21897
 
21728
21898
  // cli/src/index.ts
21729
21899
  var import_picocolors4 = __toESM(require_picocolors());
21730
- var VERSION = "0.1.19";
21900
+ var VERSION = "0.1.21";
21731
21901
  var HELP = `
21732
21902
  ${import_picocolors4.default.bold("7onic")} \u2014 Add 7onic design system components to your project
21733
21903
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "7onic",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "CLI for adding 7onic design system components to your project",
5
5
  "bin": {
6
6
  "7onic": "./dist/index.js"