11"use client" ;
22
33import * as React from "react" ;
4- import { ChevronDown } from "lucide-react" ;
54
65import { Checkbox } from "@/components/ui/checkbox" ;
76import { cn } from "@/lib/utils" ;
@@ -19,134 +18,61 @@ export type GroupedUserConsentsProps = {
1918 groupLabel : string ;
2019 groupHint ?: string ;
2120 items : GroupedConsentItem [ ] ;
22- defaultExpanded ?: boolean ;
2321 className ?: string ;
2422} ;
2523
2624/**
27- * Renders a group of User-level consent checkboxes with a parent toggle that
28- * checks/unchecks all children at once and shows an indeterminate state when
29- * only some children are checked. When only one item is provided, the parent
30- * wrapper collapses into a single flat checkbox to avoid superfluous nesting.
25+ * Renders a section of User-level consent checkboxes under a static group
26+ * heading. Each item is an independent checkbox — there is no parent toggle
27+ * and no collapse behavior.
3128 */
3229export function GroupedUserConsents ( {
3330 groupLabel,
3431 groupHint,
3532 items,
36- defaultExpanded = false ,
3733 className,
3834} : GroupedUserConsentsProps ) {
39- const parentInputId = React . useId ( ) ;
40- const [ expanded , setExpanded ] = React . useState ( defaultExpanded ) ;
35+ const reactId = React . useId ( ) ;
4136
4237 if ( items . length === 0 ) return null ;
4338
44- if ( items . length === 1 ) {
45- const item = items [ 0 ] ;
46- const id = `${ parentInputId } -${ item . key } ` ;
47- return (
48- < div className = { cn ( "flex items-start space-x-3" , className ) } >
49- < Checkbox
50- id = { id }
51- checked = { item . checked }
52- onCheckedChange = { ( value ) => item . onCheckedChange ( value === true ) }
53- />
54- < div className = "flex-1" >
55- < label
56- htmlFor = { id }
57- className = "text-sm text-foreground cursor-pointer"
58- >
59- { item . label }
60- </ label >
61- { item . hint ? (
62- < p className = "text-xs text-muted-foreground mt-1" > { item . hint } </ p >
63- ) : null }
64- </ div >
65- </ div >
66- ) ;
67- }
68-
69- const allChecked = items . every ( ( i ) => i . checked ) ;
70- const noneChecked = items . every ( ( i ) => ! i . checked ) ;
71- const parentState : boolean | "indeterminate" = allChecked
72- ? true
73- : noneChecked
74- ? false
75- : "indeterminate" ;
76-
77- const handleParentChange = ( ) => {
78- const next = ! allChecked ;
79- items . forEach ( ( i ) => i . onCheckedChange ( next ) ) ;
80- } ;
81-
8239 return (
83- < div className = { cn ( "space-y-2" , className ) } >
84- < div className = "flex items-start space-x-3" >
85- < Checkbox
86- id = { parentInputId }
87- checked = { parentState }
88- onCheckedChange = { handleParentChange }
89- />
90- < div className = "flex-1" >
91- < div className = "flex items-center gap-2" >
92- < label
93- htmlFor = { parentInputId }
94- className = "text-sm text-foreground cursor-pointer"
95- >
96- { groupLabel }
97- </ label >
98- < button
99- type = "button"
100- onClick = { ( ) => setExpanded ( ( prev ) => ! prev ) }
101- aria-expanded = { expanded }
102- aria-controls = { `${ parentInputId } -details` }
103- aria-label = { expanded ? "Collapse details" : "Expand details" }
104- className = "text-muted-foreground hover:text-foreground transition-colors"
105- >
106- < ChevronDown
107- className = { cn (
108- "size-4 transition-transform" ,
109- expanded && "rotate-180" ,
110- ) }
111- />
112- </ button >
113- </ div >
114- { groupHint ? (
115- < p className = "text-xs text-muted-foreground mt-1" > { groupHint } </ p >
116- ) : null }
117- </ div >
40+ < div className = { cn ( "space-y-3" , className ) } >
41+ < div >
42+ < p className = "text-sm text-foreground" > { groupLabel } </ p >
43+ { groupHint ? (
44+ < p className = "text-xs text-muted-foreground mt-1" > { groupHint } </ p >
45+ ) : null }
11846 </ div >
119- { expanded ? (
120- < div id = { `${ parentInputId } -details` } className = "pl-7 space-y-3" >
121- { items . map ( ( item ) => {
122- const id = `${ parentInputId } -${ item . key } ` ;
123- return (
124- < div key = { item . key } className = "flex items-start space-x-3" >
125- < Checkbox
126- id = { id }
127- checked = { item . checked }
128- onCheckedChange = { ( value ) =>
129- item . onCheckedChange ( value === true )
130- }
131- />
132- < div className = "flex-1" >
133- < label
134- htmlFor = { id }
135- className = "text-sm text-foreground cursor-pointer"
136- >
137- { item . label }
138- </ label >
139- { item . hint ? (
140- < p className = "text-xs text-muted-foreground mt-1" >
141- { item . hint }
142- </ p >
143- ) : null }
144- </ div >
47+ < div className = "space-y-3" >
48+ { items . map ( ( item ) => {
49+ const id = `${ reactId } -${ item . key } ` ;
50+ return (
51+ < div key = { item . key } className = "flex items-start space-x-3" >
52+ < Checkbox
53+ id = { id }
54+ checked = { item . checked }
55+ onCheckedChange = { ( value ) =>
56+ item . onCheckedChange ( value === true )
57+ }
58+ />
59+ < div className = "flex-1" >
60+ < label
61+ htmlFor = { id }
62+ className = "text-sm text-foreground cursor-pointer"
63+ >
64+ { item . label }
65+ </ label >
66+ { item . hint ? (
67+ < p className = "text-xs text-muted-foreground mt-1" >
68+ { item . hint }
69+ </ p >
70+ ) : null }
14571 </ div >
146- ) ;
147- } ) }
148- </ div >
149- ) : null }
72+ </ div >
73+ ) ;
74+ } ) }
75+ </ div >
15076 </ div >
15177 ) ;
15278}
0 commit comments