@@ -81,6 +81,59 @@ function ProviderPreviewIcon({ providerId }: { providerId?: string }) {
8181 )
8282}
8383
84+ interface FeatureToggleItemProps {
85+ feature : PermissionFeature
86+ enabled : boolean
87+ color : string
88+ isInView : boolean
89+ delay : number
90+ textClassName : string
91+ transition : Record < string , unknown >
92+ onToggle : ( ) => void
93+ }
94+
95+ function FeatureToggleItem ( {
96+ feature,
97+ enabled,
98+ color,
99+ isInView,
100+ delay,
101+ textClassName,
102+ transition,
103+ onToggle,
104+ } : FeatureToggleItemProps ) {
105+ return (
106+ < motion . div
107+ key = { feature . key }
108+ role = 'button'
109+ tabIndex = { 0 }
110+ aria-label = { `Toggle ${ feature . name } ` }
111+ aria-pressed = { enabled }
112+ className = 'flex cursor-pointer items-center gap-2 rounded-[4px] py-0.5'
113+ initial = { { opacity : 0 , x : - 6 } }
114+ animate = { isInView ? { opacity : 1 , x : 0 } : { } }
115+ transition = { { delay, ...transition } }
116+ onClick = { onToggle }
117+ onKeyDown = { ( e ) => {
118+ if ( e . key === 'Enter' || e . key === ' ' ) {
119+ e . preventDefault ( )
120+ onToggle ( )
121+ }
122+ } }
123+ whileTap = { { scale : 0.98 } }
124+ >
125+ < CheckboxIcon checked = { enabled } color = { color } />
126+ < ProviderPreviewIcon providerId = { feature . providerId } />
127+ < span
128+ className = { textClassName }
129+ style = { { color : enabled ? '#F6F6F6AA' : '#F6F6F640' } }
130+ >
131+ { feature . name }
132+ </ span >
133+ </ motion . div >
134+ )
135+ }
136+
84137export function AccessControlPanel ( ) {
85138 const ref = useRef ( null )
86139 const isInView = useInView ( ref , { once : true , margin : '-40px' } )
@@ -101,45 +154,21 @@ export function AccessControlPanel() {
101154 { category . label }
102155 </ span >
103156 < div className = 'mt-2 grid grid-cols-2 gap-x-4 gap-y-2' >
104- { category . features . map ( ( feature , featIdx ) => {
105- const enabled = accessState [ feature . key ]
106-
107- return (
108- < motion . div
109- key = { feature . key }
110- role = 'button'
111- tabIndex = { 0 }
112- aria-label = { `Toggle ${ feature . name } ` }
113- aria-pressed = { enabled }
114- className = 'flex cursor-pointer items-center gap-2 rounded-[4px] py-0.5'
115- initial = { { opacity : 0 , x : - 6 } }
116- animate = { isInView ? { opacity : 1 , x : 0 } : { } }
117- transition = { {
118- delay : 0.05 + ( offsetBefore + featIdx ) * 0.04 ,
119- duration : 0.3 ,
120- } }
121- onClick = { ( ) =>
122- setAccessState ( ( prev ) => ( { ...prev , [ feature . key ] : ! prev [ feature . key ] } ) )
123- }
124- onKeyDown = { ( e ) => {
125- if ( e . key === 'Enter' || e . key === ' ' ) {
126- e . preventDefault ( )
127- setAccessState ( ( prev ) => ( { ...prev , [ feature . key ] : ! prev [ feature . key ] } ) )
128- }
129- } }
130- whileTap = { { scale : 0.98 } }
131- >
132- < CheckboxIcon checked = { enabled } color = { category . color } />
133- < ProviderPreviewIcon providerId = { feature . providerId } />
134- < span
135- className = 'truncate font-[430] font-season text-[13px] leading-none tracking-[0.02em]'
136- style = { { color : enabled ? '#F6F6F6AA' : '#F6F6F640' } }
137- >
138- { feature . name }
139- </ span >
140- </ motion . div >
141- )
142- } ) }
157+ { category . features . map ( ( feature , featIdx ) => (
158+ < FeatureToggleItem
159+ key = { feature . key }
160+ feature = { feature }
161+ enabled = { accessState [ feature . key ] }
162+ color = { category . color }
163+ isInView = { isInView }
164+ delay = { 0.05 + ( offsetBefore + featIdx ) * 0.04 }
165+ textClassName = 'truncate font-[430] font-season text-[13px] leading-none tracking-[0.02em]'
166+ transition = { { duration : 0.3 } }
167+ onToggle = { ( ) =>
168+ setAccessState ( ( prev ) => ( { ...prev , [ feature . key ] : ! prev [ feature . key ] } ) )
169+ }
170+ />
171+ ) ) }
143172 </ div >
144173 </ div >
145174 )
@@ -155,48 +184,26 @@ export function AccessControlPanel() {
155184 </ span >
156185 < div className = 'mt-2 grid grid-cols-2 gap-x-4 gap-y-2' >
157186 { category . features . map ( ( feature , featIdx ) => {
158- const enabled = accessState [ feature . key ]
159187 const currentIndex =
160188 PERMISSION_CATEGORIES . slice ( 0 , catIdx ) . reduce (
161189 ( sum , c ) => sum + c . features . length ,
162190 0
163191 ) + featIdx
164192
165193 return (
166- < motion . div
194+ < FeatureToggleItem
167195 key = { feature . key }
168- role = 'button'
169- tabIndex = { 0 }
170- aria-label = { `Toggle ${ feature . name } ` }
171- aria-pressed = { enabled }
172- className = 'flex cursor-pointer items-center gap-2 rounded-[4px] py-0.5'
173- initial = { { opacity : 0 , x : - 6 } }
174- animate = { isInView ? { opacity : 1 , x : 0 } : { } }
175- transition = { {
176- delay : 0.1 + currentIndex * 0.04 ,
177- duration : 0.3 ,
178- ease : [ 0.25 , 0.46 , 0.45 , 0.94 ] ,
179- } }
180- onClick = { ( ) =>
196+ feature = { feature }
197+ enabled = { accessState [ feature . key ] }
198+ color = { category . color }
199+ isInView = { isInView }
200+ delay = { 0.1 + currentIndex * 0.04 }
201+ textClassName = 'truncate font-[430] font-season text-[11px] leading-none tracking-[0.02em] transition-opacity duration-200'
202+ transition = { { duration : 0.3 , ease : [ 0.25 , 0.46 , 0.45 , 0.94 ] } }
203+ onToggle = { ( ) =>
181204 setAccessState ( ( prev ) => ( { ...prev , [ feature . key ] : ! prev [ feature . key ] } ) )
182205 }
183- onKeyDown = { ( e ) => {
184- if ( e . key === 'Enter' || e . key === ' ' ) {
185- e . preventDefault ( )
186- setAccessState ( ( prev ) => ( { ...prev , [ feature . key ] : ! prev [ feature . key ] } ) )
187- }
188- } }
189- whileTap = { { scale : 0.98 } }
190- >
191- < CheckboxIcon checked = { enabled } color = { category . color } />
192- < ProviderPreviewIcon providerId = { feature . providerId } />
193- < span
194- className = 'truncate font-[430] font-season text-[11px] leading-none tracking-[0.02em] transition-opacity duration-200'
195- style = { { color : enabled ? '#F6F6F6AA' : '#F6F6F640' } }
196- >
197- { feature . name }
198- </ span >
199- </ motion . div >
206+ />
200207 )
201208 } ) }
202209 </ div >
0 commit comments