@juspay/blend-design-system 0.0.37-beta.4 → 0.0.37-beta.5
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.
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +2 -5
- package/dist/components/Breadcrumb/types.d.ts +6 -0
- package/dist/components/Charts/ChartUtils.d.ts +2 -0
- package/dist/components/Charts/types.d.ts +2 -2
- package/dist/components/DateRangePicker/types.d.ts +1 -1
- package/dist/components/DateRangePicker/utils.d.ts +2 -0
- package/dist/components/Directory/Directory.d.ts +1 -1
- package/dist/components/Directory/types.d.ts +1 -1
- package/dist/components/Directory/utils.d.ts +2 -0
- package/dist/components/Radio/StyledRadio.d.ts +0 -1
- package/dist/components/Sidebar/SidebarContent.d.ts +1 -1
- package/dist/components/Sidebar/types.d.ts +10 -1
- package/dist/components/Sidebar/utils.d.ts +1 -1
- package/dist/components/SidebarV2/SidebarV2Panel.d.ts +1 -1
- package/dist/components/SidebarV2/index.d.ts +1 -1
- package/dist/components/SidebarV2/types.d.ts +3 -0
- package/dist/components/Stepper/types.d.ts +2 -0
- package/dist/main.js +27657 -27314
- package/dist/tokens.js +17 -16
- package/lib/components/Avatar/Avatar.tsx +6 -1
- package/lib/components/AvatarGroup/AvatarGroup.tsx +1 -1
- package/lib/components/AvatarV2/AvatarV2.tsx +10 -1
- package/lib/components/Breadcrumb/Breadcrumb.tsx +9 -8
- package/lib/components/Breadcrumb/types.ts +12 -0
- package/lib/components/Button/ButtonBase.tsx +1 -1
- package/lib/components/Card/CardComponents.tsx +52 -17
- package/lib/components/Charts/ChartUtils.tsx +7 -0
- package/lib/components/Charts/Charts.tsx +4 -2
- package/lib/components/Charts/CoreChart.tsx +4 -2
- package/lib/components/Charts/types.tsx +2 -2
- package/lib/components/ChartsV2/ChartV2.tsx +1 -1
- package/lib/components/Checkbox/Checkbox.tsx +29 -7
- package/lib/components/CodeBlock/CodeBlock.tsx +47 -1
- package/lib/components/CodeBlock/codeBlock.token.ts +5 -5
- package/lib/components/CodeEditor/CodeEditor.tsx +26 -4
- package/lib/components/CodeEditor/MonacoEditorWrapper.tsx +13 -1
- package/lib/components/DataTable/DataTable.tsx +8 -0
- package/lib/components/DataTable/TableHeader/FilterComponents.tsx +4 -0
- package/lib/components/DateRangePicker/DateRangePicker.tsx +34 -17
- package/lib/components/DateRangePicker/types.ts +5 -5
- package/lib/components/DateRangePicker/utils.ts +5 -0
- package/lib/components/Directory/Directory.tsx +3 -2
- package/lib/components/Directory/types.ts +1 -1
- package/lib/components/Directory/utils.ts +6 -0
- package/lib/components/Drawer/components/DrawerBase.tsx +16 -0
- package/lib/components/Drawer/components/NestedSelectDrawer.tsx +13 -1
- package/lib/components/Drawer/components/SelectDrawer.tsx +9 -1
- package/lib/components/Inputs/OTPInput/OTPInput.tsx +5 -3
- package/lib/components/Menu/Menu.tsx +9 -1
- package/lib/components/Modal/useModal.ts +7 -0
- package/lib/components/Radio/Radio.tsx +12 -5
- package/lib/components/Radio/StyledRadio.tsx +33 -17
- package/lib/components/Sidebar/Sidebar.tsx +11 -1
- package/lib/components/Sidebar/SidebarContent.tsx +5 -2
- package/lib/components/Sidebar/TenantPanel.tsx +52 -34
- package/lib/components/Sidebar/types.ts +11 -1
- package/lib/components/Sidebar/utils.ts +1 -1
- package/lib/components/SidebarV2/SecondarySidebar.tsx +86 -44
- package/lib/components/SidebarV2/SidebarV2Panel.tsx +4 -2
- package/lib/components/SidebarV2/index.ts +1 -0
- package/lib/components/SidebarV2/types.ts +4 -0
- package/lib/components/StatCard/statcard.tokens.ts +1 -1
- package/lib/components/Stepper/VerticalStepper.tsx +209 -171
- package/lib/components/Stepper/types.ts +2 -0
- package/lib/components/StepperV2/Stepper/Steps.tsx +15 -1
- package/lib/components/Text/Text.tsx +1 -0
- package/lib/components/Upload/Upload.tsx +6 -0
- package/lib/components/Upload/components/FileListDisplay.tsx +159 -16
- package/lib/components/Upload/utils.ts +10 -2
- package/lib/context/ThemeProvider.tsx +19 -8
- package/lib/hooks/useDebounce.ts +9 -1
- package/package.json +1 -1
|
@@ -943,6 +943,14 @@ const DataTable = forwardRef(
|
|
|
943
943
|
|
|
944
944
|
const sortTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
945
945
|
|
|
946
|
+
useEffect(() => {
|
|
947
|
+
return () => {
|
|
948
|
+
if (sortTimeoutRef.current) {
|
|
949
|
+
clearTimeout(sortTimeoutRef.current)
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}, [])
|
|
953
|
+
|
|
946
954
|
const applySortConfig = (
|
|
947
955
|
field: keyof T,
|
|
948
956
|
newSortConfig: SortConfig | null
|
|
@@ -618,6 +618,8 @@ export const SingleSelectItems: React.FC<{
|
|
|
618
618
|
.header.filter
|
|
619
619
|
.sortOption.fontWeight,
|
|
620
620
|
flexGrow: 1,
|
|
621
|
+
overflowWrap: 'break-word',
|
|
622
|
+
minWidth: 0,
|
|
621
623
|
}}
|
|
622
624
|
>
|
|
623
625
|
{label}
|
|
@@ -824,6 +826,8 @@ export const MultiSelectItems: React.FC<{
|
|
|
824
826
|
.header.filter
|
|
825
827
|
.sortOption.fontWeight,
|
|
826
828
|
flexGrow: 1,
|
|
829
|
+
overflowWrap: 'break-word',
|
|
830
|
+
minWidth: 0,
|
|
827
831
|
}}
|
|
828
832
|
>
|
|
829
833
|
{label}
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
getPresetLabel,
|
|
26
26
|
getTodayInTimezone,
|
|
27
27
|
validateDateInput,
|
|
28
|
+
isControlledDateRange,
|
|
29
|
+
isValidDate,
|
|
28
30
|
} from './utils'
|
|
29
31
|
import CalendarGrid from './CalendarGrid'
|
|
30
32
|
import QuickRangeSelector from './QuickRangeSelector'
|
|
@@ -408,7 +410,7 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
408
410
|
|
|
409
411
|
const [selectedRange, setSelectedRange] = useState<
|
|
410
412
|
DateRange | undefined
|
|
411
|
-
>(value)
|
|
413
|
+
>(() => (isControlledDateRange(value) ? value : undefined))
|
|
412
414
|
const lastExternalValueRef = React.useRef<{
|
|
413
415
|
start: number | null
|
|
414
416
|
end: number | null
|
|
@@ -524,32 +526,44 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
524
526
|
|
|
525
527
|
const resetValues = useCallback(
|
|
526
528
|
(dateRangeObj?: DateRange) => {
|
|
527
|
-
|
|
529
|
+
const normalizedRange = isControlledDateRange(dateRangeObj)
|
|
530
|
+
? dateRangeObj
|
|
531
|
+
: undefined
|
|
532
|
+
|
|
533
|
+
setSelectedRange(normalizedRange)
|
|
528
534
|
setActivePreset(
|
|
529
|
-
|
|
530
|
-
? detectPresetFromRange(
|
|
535
|
+
normalizedRange
|
|
536
|
+
? detectPresetFromRange(normalizedRange, timezone)
|
|
531
537
|
: DateRangePreset.CUSTOM
|
|
532
538
|
)
|
|
533
539
|
setStartDate(
|
|
534
|
-
|
|
535
|
-
formatDate(
|
|
540
|
+
normalizedRange &&
|
|
541
|
+
formatDate(
|
|
542
|
+
normalizedRange.startDate,
|
|
543
|
+
dateFormat,
|
|
544
|
+
timezone
|
|
545
|
+
)
|
|
536
546
|
)
|
|
537
|
-
if (
|
|
547
|
+
if (normalizedRange?.endDate) {
|
|
538
548
|
setEndDate(
|
|
539
|
-
formatDate(
|
|
549
|
+
formatDate(
|
|
550
|
+
normalizedRange.endDate,
|
|
551
|
+
dateFormat,
|
|
552
|
+
timezone
|
|
553
|
+
)
|
|
540
554
|
)
|
|
541
|
-
} else if (!
|
|
555
|
+
} else if (!normalizedRange) {
|
|
542
556
|
setEndDate(undefined)
|
|
543
557
|
}
|
|
544
558
|
setStartTime(
|
|
545
|
-
|
|
546
|
-
formatDate(
|
|
559
|
+
normalizedRange &&
|
|
560
|
+
formatDate(normalizedRange.startDate, 'HH:mm', timezone)
|
|
547
561
|
)
|
|
548
|
-
if (
|
|
562
|
+
if (normalizedRange?.endDate) {
|
|
549
563
|
setEndTime(
|
|
550
|
-
formatDate(
|
|
564
|
+
formatDate(normalizedRange.endDate, 'HH:mm', timezone)
|
|
551
565
|
)
|
|
552
|
-
} else if (!
|
|
566
|
+
} else if (!normalizedRange) {
|
|
553
567
|
setEndTime(undefined)
|
|
554
568
|
}
|
|
555
569
|
setStartDateValidation({ isValid: true, error: 'none' })
|
|
@@ -562,7 +576,7 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
562
576
|
)
|
|
563
577
|
|
|
564
578
|
useEffect(() => {
|
|
565
|
-
if (!value) {
|
|
579
|
+
if (!isControlledDateRange(value)) {
|
|
566
580
|
if (lastExternalValueRef.current !== null) {
|
|
567
581
|
lastExternalValueRef.current = null
|
|
568
582
|
resetValues(undefined)
|
|
@@ -571,8 +585,11 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
|
|
|
571
585
|
}
|
|
572
586
|
|
|
573
587
|
const nextSignature = {
|
|
574
|
-
start: value.startDate.getTime()
|
|
575
|
-
end:
|
|
588
|
+
start: value.startDate.getTime(),
|
|
589
|
+
end:
|
|
590
|
+
value.endDate && isValidDate(value.endDate)
|
|
591
|
+
? value.endDate.getTime()
|
|
592
|
+
: null,
|
|
576
593
|
dateFormat,
|
|
577
594
|
}
|
|
578
595
|
|
|
@@ -194,11 +194,11 @@ export type CustomPresetDefinition = {
|
|
|
194
194
|
/**
|
|
195
195
|
* Presets configuration - can be predefined presets, custom configs, or custom definitions
|
|
196
196
|
*/
|
|
197
|
-
export type PresetsConfig =
|
|
198
|
-
| DateRangePreset
|
|
199
|
-
| CustomPresetConfig
|
|
200
|
-
| CustomPresetDefinition
|
|
201
|
-
|
|
197
|
+
export type PresetsConfig = (
|
|
198
|
+
| DateRangePreset
|
|
199
|
+
| CustomPresetConfig
|
|
200
|
+
| CustomPresetDefinition
|
|
201
|
+
)[]
|
|
202
202
|
|
|
203
203
|
// =============================================================================
|
|
204
204
|
// COMPONENT PROPS
|
|
@@ -256,6 +256,11 @@ export const isValidDate = (date: Date): boolean => {
|
|
|
256
256
|
return date instanceof Date && !isNaN(date.getTime())
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
/** True when a controlled `value` has a usable start date (avoids `.getTime()` on undefined). */
|
|
260
|
+
export const isControlledDateRange = (
|
|
261
|
+
value?: DateRange | null
|
|
262
|
+
): value is DateRange => !!value && isValidDate(value.startDate)
|
|
263
|
+
|
|
259
264
|
/**
|
|
260
265
|
* Formats time in 12-hour format
|
|
261
266
|
* @param date The date to format
|
|
@@ -5,19 +5,20 @@ import { createRef, useEffect, useRef } from 'react'
|
|
|
5
5
|
import type { DirectoryProps } from './types'
|
|
6
6
|
import Section from './Section'
|
|
7
7
|
import Block from '../Primitives/Block/Block'
|
|
8
|
-
import { handleSectionNavigation } from './utils'
|
|
8
|
+
import { handleSectionNavigation, normalizeDirectoryData } from './utils'
|
|
9
9
|
import { ActiveItemProvider } from './NavItem'
|
|
10
10
|
import { useResponsiveTokens } from '../../hooks/useResponsiveTokens'
|
|
11
11
|
import { DirectoryTokenType } from './directory.tokens'
|
|
12
12
|
|
|
13
13
|
const Directory = ({
|
|
14
|
-
directoryData,
|
|
14
|
+
directoryData: directoryDataProp,
|
|
15
15
|
idPrefix,
|
|
16
16
|
activeItem,
|
|
17
17
|
onActiveItemChange,
|
|
18
18
|
defaultActiveItem,
|
|
19
19
|
iconOnlyMode = false,
|
|
20
20
|
}: DirectoryProps) => {
|
|
21
|
+
const directoryData = normalizeDirectoryData(directoryDataProp)
|
|
21
22
|
const sectionRefs = useRef<Array<React.RefObject<HTMLDivElement | null>>>(
|
|
22
23
|
[]
|
|
23
24
|
)
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import type { DirectoryData } from './types'
|
|
2
|
+
|
|
3
|
+
export const normalizeDirectoryData = (
|
|
4
|
+
directoryData: DirectoryData[] | null
|
|
5
|
+
): DirectoryData[] => (Array.isArray(directoryData) ? directoryData : [])
|
|
6
|
+
|
|
1
7
|
export const handleSectionNavigation = (
|
|
2
8
|
direction: 'up' | 'down',
|
|
3
9
|
currentIndex: number,
|
|
@@ -299,6 +299,22 @@ export const Drawer = ({
|
|
|
299
299
|
string | undefined
|
|
300
300
|
>(undefined)
|
|
301
301
|
|
|
302
|
+
// Prevent focus from escaping to underlying content when a nested drawer is open
|
|
303
|
+
React.useLayoutEffect(() => {
|
|
304
|
+
const stopFocus = (e: FocusEvent) => {
|
|
305
|
+
// only intercept when a nested modal is open
|
|
306
|
+
if (document.querySelector('[data-modal]')) {
|
|
307
|
+
e.stopImmediatePropagation()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
document.addEventListener('focusin', stopFocus)
|
|
311
|
+
document.addEventListener('focusout', stopFocus)
|
|
312
|
+
return () => {
|
|
313
|
+
document.removeEventListener('focusin', stopFocus)
|
|
314
|
+
document.removeEventListener('focusout', stopFocus)
|
|
315
|
+
}
|
|
316
|
+
}, [])
|
|
317
|
+
|
|
302
318
|
// Screen reader announcement for drawer state changes
|
|
303
319
|
React.useEffect(() => {
|
|
304
320
|
if (open) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { useState, useMemo, createContext } from 'react'
|
|
3
|
+
import React, { useState, useMemo, createContext, useEffect } from 'react'
|
|
4
4
|
import {
|
|
5
5
|
Drawer,
|
|
6
6
|
DrawerPortal,
|
|
@@ -249,6 +249,11 @@ export const NestedMultiSelectDrawer = ({
|
|
|
249
249
|
}>
|
|
250
250
|
>([{ title: heading, items, selectedValues: internalSelectedValues }])
|
|
251
251
|
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
setInternalSelectedValues(selectedValues)
|
|
254
|
+
setNavigationStack([{ title: heading, items, selectedValues }])
|
|
255
|
+
}, [selectedValues])
|
|
256
|
+
|
|
252
257
|
const selectMobileOffset = {
|
|
253
258
|
top: '74px',
|
|
254
259
|
bottom: '0px',
|
|
@@ -579,6 +584,13 @@ export const NestedSingleSelectDrawer = ({
|
|
|
579
584
|
}>
|
|
580
585
|
>([{ title: heading, items, selectedValue: internalSelectedValue }])
|
|
581
586
|
|
|
587
|
+
useEffect(() => {
|
|
588
|
+
setInternalSelectedValue(selectedValue || '')
|
|
589
|
+
setNavigationStack([
|
|
590
|
+
{ title: heading, items, selectedValue: selectedValue || '' },
|
|
591
|
+
])
|
|
592
|
+
}, [selectedValue])
|
|
593
|
+
|
|
582
594
|
const selectMobileOffset = {
|
|
583
595
|
top: '74px',
|
|
584
596
|
bottom: '16px',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { useState, useMemo } from 'react'
|
|
3
|
+
import React, { useState, useMemo, useEffect } from 'react'
|
|
4
4
|
import {
|
|
5
5
|
Drawer,
|
|
6
6
|
DrawerPortal,
|
|
@@ -46,6 +46,10 @@ export const MultiSelectDrawer = ({
|
|
|
46
46
|
const [internalSelectedValues, setInternalSelectedValues] =
|
|
47
47
|
useState<string[]>(selectedValues)
|
|
48
48
|
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
setInternalSelectedValues(selectedValues)
|
|
51
|
+
}, [selectedValues])
|
|
52
|
+
|
|
49
53
|
const selectMobileOffset = {
|
|
50
54
|
top: '74px',
|
|
51
55
|
bottom: '16px',
|
|
@@ -386,6 +390,10 @@ export const SingleSelectDrawer = ({
|
|
|
386
390
|
selectedValue || ''
|
|
387
391
|
)
|
|
388
392
|
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
setInternalSelectedValue(selectedValue || '')
|
|
395
|
+
}, [selectedValue])
|
|
396
|
+
|
|
389
397
|
const selectMobileOffset = {
|
|
390
398
|
top: '74px',
|
|
391
399
|
bottom: '0px',
|
|
@@ -74,15 +74,17 @@ const OTPInput = ({
|
|
|
74
74
|
.join(' ') || undefined
|
|
75
75
|
|
|
76
76
|
useEffect(() => {
|
|
77
|
-
if (!disabled) return
|
|
78
77
|
const val = value || ''
|
|
79
78
|
const otpArray = val.split('').slice(0, length)
|
|
80
79
|
const paddedOtp = [
|
|
81
80
|
...otpArray,
|
|
82
81
|
...new Array(Math.max(length - otpArray.length, 0)).fill(''),
|
|
83
82
|
]
|
|
84
|
-
setOtp(
|
|
85
|
-
|
|
83
|
+
setOtp((prevOtp) => {
|
|
84
|
+
if (prevOtp.join('') === paddedOtp.join('')) return prevOtp
|
|
85
|
+
return paddedOtp
|
|
86
|
+
})
|
|
87
|
+
}, [value, length])
|
|
86
88
|
|
|
87
89
|
useEffect(() => {
|
|
88
90
|
setOtp((prevOtp) => {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
MenuSide,
|
|
8
8
|
type MenuItemType,
|
|
9
9
|
} from './types'
|
|
10
|
-
import React, { useState, useRef, useMemo, useCallback } from 'react'
|
|
10
|
+
import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react'
|
|
11
11
|
import { filterMenuGroups } from './utils'
|
|
12
12
|
import MenuItem from './MenuItem'
|
|
13
13
|
import Block from '../Primitives/Block/Block'
|
|
@@ -115,6 +115,14 @@ const Menu = ({
|
|
|
115
115
|
onOpenChange?.(newOpen)
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
return () => {
|
|
120
|
+
if (timeoutRef.current) {
|
|
121
|
+
clearTimeout(timeoutRef.current)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}, [])
|
|
125
|
+
|
|
118
126
|
const handleOutsideInteraction = useCallback((e: Event) => {
|
|
119
127
|
if (justOpenedRef.current) {
|
|
120
128
|
e.preventDefault()
|
|
@@ -22,6 +22,12 @@ export const useModal = (isOpen: boolean, onClose: () => void) => {
|
|
|
22
22
|
const container = getPortalContainer()
|
|
23
23
|
setPortalContainer(container)
|
|
24
24
|
|
|
25
|
+
document.body.style.setProperty(
|
|
26
|
+
'pointer-events',
|
|
27
|
+
'auto',
|
|
28
|
+
'important'
|
|
29
|
+
)
|
|
30
|
+
|
|
25
31
|
let animationFrame2: number | null = null
|
|
26
32
|
const animationFrame1 = requestAnimationFrame(() => {
|
|
27
33
|
animationFrame2 = requestAnimationFrame(() => {
|
|
@@ -44,6 +50,7 @@ export const useModal = (isOpen: boolean, onClose: () => void) => {
|
|
|
44
50
|
cancelAnimationFrame(animationFrame2)
|
|
45
51
|
}
|
|
46
52
|
document.removeEventListener('keydown', handleEscapeKey)
|
|
53
|
+
document.body.style.removeProperty('pointer-events')
|
|
47
54
|
}
|
|
48
55
|
} else {
|
|
49
56
|
// Start exit animation
|
|
@@ -16,7 +16,7 @@ import { getTruncatedText } from '../../global-utils/GlobalUtils'
|
|
|
16
16
|
export const Radio = ({
|
|
17
17
|
id,
|
|
18
18
|
checked,
|
|
19
|
-
defaultChecked
|
|
19
|
+
defaultChecked,
|
|
20
20
|
onChange,
|
|
21
21
|
disabled = false,
|
|
22
22
|
required = false,
|
|
@@ -45,12 +45,20 @@ export const Radio = ({
|
|
|
45
45
|
subtextId && customAriaDescribedBy
|
|
46
46
|
? `${customAriaDescribedBy} ${subtextId}`
|
|
47
47
|
: subtextId || customAriaDescribedBy
|
|
48
|
+
const isControlled = checked !== undefined
|
|
49
|
+
const checkedProps = isControlled
|
|
50
|
+
? { checked }
|
|
51
|
+
: { defaultChecked: defaultChecked ?? false }
|
|
48
52
|
|
|
49
53
|
return (
|
|
50
54
|
<Block
|
|
51
55
|
data-radio={children ?? 'radio'}
|
|
52
56
|
data-status={disabled ? 'disabled' : 'enabled'}
|
|
53
|
-
data-state={
|
|
57
|
+
data-state={
|
|
58
|
+
(isControlled ? checked : defaultChecked)
|
|
59
|
+
? 'checked'
|
|
60
|
+
: 'unchecked'
|
|
61
|
+
}
|
|
54
62
|
data-id={value ?? ''}
|
|
55
63
|
display="flex"
|
|
56
64
|
alignItems={subtext ? 'flex-start' : 'center'}
|
|
@@ -60,14 +68,13 @@ export const Radio = ({
|
|
|
60
68
|
type="radio"
|
|
61
69
|
id={uniqueId}
|
|
62
70
|
name={name}
|
|
63
|
-
|
|
64
|
-
defaultChecked={defaultChecked}
|
|
71
|
+
{...checkedProps}
|
|
65
72
|
disabled={disabled}
|
|
66
73
|
required={required}
|
|
74
|
+
readOnly={isControlled && !onChange ? true : undefined}
|
|
67
75
|
onChange={onChange}
|
|
68
76
|
size={size}
|
|
69
77
|
$isDisabled={disabled}
|
|
70
|
-
$isChecked={checked || false}
|
|
71
78
|
$error={error}
|
|
72
79
|
$tokens={radioTokens}
|
|
73
80
|
style={getErrorShakeStyle(shouldShake)}
|
|
@@ -7,7 +7,6 @@ import { radioAnimations } from './radio.animations'
|
|
|
7
7
|
export const StyledRadioInput = styled.input<{
|
|
8
8
|
size: RadioSize
|
|
9
9
|
$isDisabled: boolean
|
|
10
|
-
$isChecked: boolean
|
|
11
10
|
$error?: boolean
|
|
12
11
|
$tokens: RadioTokensType
|
|
13
12
|
}>`
|
|
@@ -22,17 +21,17 @@ export const StyledRadioInput = styled.input<{
|
|
|
22
21
|
padding: 0;
|
|
23
22
|
flex-shrink: 0;
|
|
24
23
|
|
|
25
|
-
${({ size, $
|
|
24
|
+
${({ size, $isDisabled, $tokens }) => {
|
|
26
25
|
const state = $isDisabled ? 'disabled' : 'default'
|
|
27
|
-
const
|
|
26
|
+
const inactiveIndicator = $tokens.indicator.inactive
|
|
27
|
+
const activeIndicator = $tokens.indicator.active
|
|
28
28
|
|
|
29
29
|
return css`
|
|
30
30
|
${radioAnimations}
|
|
31
31
|
|
|
32
|
-
background-color: ${
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
${$tokens.indicator[indicatorState].borderColor[state]};
|
|
32
|
+
background-color: ${inactiveIndicator.backgroundColor[state]};
|
|
33
|
+
border: ${$tokens.borderWidth.inactive[state]}px solid
|
|
34
|
+
${inactiveIndicator.borderColor[state]};
|
|
36
35
|
width: ${$tokens.height[size]};
|
|
37
36
|
height: ${$tokens.height[size]};
|
|
38
37
|
|
|
@@ -41,28 +40,45 @@ export const StyledRadioInput = styled.input<{
|
|
|
41
40
|
width: 50%;
|
|
42
41
|
height: 50%;
|
|
43
42
|
border-radius: 50%;
|
|
44
|
-
background-color:
|
|
45
|
-
|
|
46
|
-
: 'transparent'};
|
|
47
|
-
transform: ${$isChecked ? 'scale(1)' : 'scale(0)'};
|
|
43
|
+
background-color: transparent;
|
|
44
|
+
transform: scale(0);
|
|
48
45
|
transition:
|
|
49
46
|
transform 250ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
50
47
|
background-color 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
51
48
|
}
|
|
49
|
+
|
|
50
|
+
&:checked {
|
|
51
|
+
background-color: ${activeIndicator.backgroundColor[state]};
|
|
52
|
+
border: ${$tokens.borderWidth.active[state]}px solid
|
|
53
|
+
${activeIndicator.borderColor[state]};
|
|
54
|
+
|
|
55
|
+
&::after {
|
|
56
|
+
background-color: ${$tokens.activeIndicator.active
|
|
57
|
+
.backgroundColor[state]};
|
|
58
|
+
transform: scale(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
&:focus-visible {
|
|
53
|
-
outline: 2px solid
|
|
54
|
-
${$tokens.indicator[indicatorState].borderColor[state]};
|
|
63
|
+
outline: 2px solid ${inactiveIndicator.borderColor[state]};
|
|
55
64
|
outline-offset: 2px;
|
|
56
65
|
/* WCAG 2.4.7 Focus Visible (AA): Focus indicator must be visible
|
|
57
66
|
* WCAG 1.4.11 Non-text Contrast (AA): Focus outline must have contrast ratio ≥3:1 against adjacent colors
|
|
58
67
|
* Manual verification recommended for all states */
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
&:checked:focus-visible {
|
|
71
|
+
outline-color: ${activeIndicator.borderColor[state]};
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
&:not(:disabled):hover {
|
|
62
|
-
background-color: ${
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
background-color: ${inactiveIndicator.backgroundColor.hover};
|
|
76
|
+
border-color: ${inactiveIndicator.borderColor.hover};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&:checked:not(:disabled):hover {
|
|
80
|
+
background-color: ${activeIndicator.backgroundColor.hover};
|
|
81
|
+
border-color: ${activeIndicator.borderColor.hover};
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
cursor: ${$isDisabled ? 'not-allowed' : 'pointer'};
|
|
@@ -323,12 +323,22 @@ const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
|
|
|
323
323
|
passive: true,
|
|
324
324
|
})
|
|
325
325
|
|
|
326
|
-
|
|
326
|
+
let resizeTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
327
|
+
|
|
328
|
+
const handleResize = () => {
|
|
329
|
+
if (resizeTimeoutId) {
|
|
330
|
+
clearTimeout(resizeTimeoutId)
|
|
331
|
+
}
|
|
332
|
+
resizeTimeoutId = setTimeout(updateBlurState, 50)
|
|
333
|
+
}
|
|
327
334
|
window.addEventListener('resize', handleResize, { passive: true })
|
|
328
335
|
|
|
329
336
|
return () => {
|
|
330
337
|
scrollingElement.removeEventListener('scroll', updateBlurState)
|
|
331
338
|
window.removeEventListener('resize', handleResize)
|
|
339
|
+
if (resizeTimeoutId) {
|
|
340
|
+
clearTimeout(resizeTimeoutId)
|
|
341
|
+
}
|
|
332
342
|
}
|
|
333
343
|
}, [isExpanded, data, isHovering])
|
|
334
344
|
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import styled from 'styled-components'
|
|
3
3
|
import Block from '../Primitives/Block/Block'
|
|
4
4
|
import Directory from '../Directory/Directory'
|
|
5
|
+
import { normalizeDirectoryData } from '../Directory/utils'
|
|
5
6
|
import SidebarHeader from './SidebarHeader'
|
|
6
7
|
import SidebarFooter from './SidebarFooter'
|
|
7
8
|
import type { DirectoryData } from '../Directory/types'
|
|
@@ -35,7 +36,7 @@ export type SidebarContentProps = {
|
|
|
35
36
|
sidebarNavId?: string
|
|
36
37
|
showTopBlur: boolean
|
|
37
38
|
showBottomBlur: boolean
|
|
38
|
-
data: DirectoryData[]
|
|
39
|
+
data: DirectoryData[] | null
|
|
39
40
|
idPrefix: string
|
|
40
41
|
activeItem?: string | null
|
|
41
42
|
onActiveItemChange?: (item: string | null) => void
|
|
@@ -66,6 +67,8 @@ const SidebarContent: React.FC<SidebarContentProps> = ({
|
|
|
66
67
|
setIsHovering,
|
|
67
68
|
sidebarState = 'expanded',
|
|
68
69
|
}) => {
|
|
70
|
+
const directoryData = normalizeDirectoryData(data)
|
|
71
|
+
|
|
69
72
|
return (
|
|
70
73
|
<Block
|
|
71
74
|
data-element="sidebar-content"
|
|
@@ -104,7 +107,7 @@ const SidebarContent: React.FC<SidebarContentProps> = ({
|
|
|
104
107
|
onMouseEnter={() => setIsHovering?.(true)}
|
|
105
108
|
>
|
|
106
109
|
<Directory
|
|
107
|
-
directoryData={
|
|
110
|
+
directoryData={directoryData}
|
|
108
111
|
idPrefix={idPrefix}
|
|
109
112
|
activeItem={activeItem}
|
|
110
113
|
onActiveItemChange={onActiveItemChange}
|
|
@@ -13,6 +13,7 @@ import { arrangeTenants } from './utils'
|
|
|
13
13
|
import type { TenantItem } from './types'
|
|
14
14
|
import { Tooltip } from '../Tooltip'
|
|
15
15
|
import { TooltipSide, TooltipSize } from '../Tooltip/types'
|
|
16
|
+
import { Badge, BadgeColor, BadgeSize } from '../Badge'
|
|
16
17
|
import { useResponsiveTokens } from '../../hooks/useResponsiveTokens'
|
|
17
18
|
import type { SidebarTokenType } from './sidebar.tokens'
|
|
18
19
|
|
|
@@ -125,6 +126,56 @@ const TenantItem: React.FC<{
|
|
|
125
126
|
}> = ({ tenant, isSelected, onSelect }) => {
|
|
126
127
|
const tokens = useResponsiveTokens<SidebarTokenType>('SIDEBAR')
|
|
127
128
|
|
|
129
|
+
const tenantButton = (
|
|
130
|
+
<PrimitiveButton
|
|
131
|
+
data-element="sidebar-section"
|
|
132
|
+
data-id={tenant.label}
|
|
133
|
+
data-status={isSelected ? 'selected' : 'not selected'}
|
|
134
|
+
type="button"
|
|
135
|
+
onClick={onSelect}
|
|
136
|
+
backgroundColor={tokens.leftPanel.item.backgroundColor.default}
|
|
137
|
+
width={tokens.leftPanel.item.width}
|
|
138
|
+
height={tokens.leftPanel.item.width}
|
|
139
|
+
borderRadius={tokens.leftPanel.item.borderRadius}
|
|
140
|
+
display="flex"
|
|
141
|
+
alignItems="center"
|
|
142
|
+
justifyContent="center"
|
|
143
|
+
cursor="pointer"
|
|
144
|
+
border={
|
|
145
|
+
isSelected
|
|
146
|
+
? tokens.leftPanel.item.border.active
|
|
147
|
+
: tokens.leftPanel.item.border.default
|
|
148
|
+
}
|
|
149
|
+
aria-label={`Select tenant: ${tenant.label}`}
|
|
150
|
+
aria-pressed={isSelected}
|
|
151
|
+
style={{
|
|
152
|
+
transition: 'all 75ms ease',
|
|
153
|
+
}}
|
|
154
|
+
_hover={{
|
|
155
|
+
backgroundColor: tokens.leftPanel.item.backgroundColor.hover,
|
|
156
|
+
outline: isSelected
|
|
157
|
+
? tokens.leftPanel.item.border.active
|
|
158
|
+
: tokens.leftPanel.item.border.hover,
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<span aria-hidden="true">{tenant.icon}</span>
|
|
162
|
+
</PrimitiveButton>
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const trigger = tenant.badge ? (
|
|
166
|
+
<Badge
|
|
167
|
+
text={tenant.badge.text.slice(0, 2)}
|
|
168
|
+
size={BadgeSize.SM}
|
|
169
|
+
color={BadgeColor.NEUTRAL}
|
|
170
|
+
position={'bottom-right'}
|
|
171
|
+
isCircular
|
|
172
|
+
>
|
|
173
|
+
{tenantButton}
|
|
174
|
+
</Badge>
|
|
175
|
+
) : (
|
|
176
|
+
tenantButton
|
|
177
|
+
)
|
|
178
|
+
|
|
128
179
|
return (
|
|
129
180
|
<Tooltip
|
|
130
181
|
content={tenant.label}
|
|
@@ -132,40 +183,7 @@ const TenantItem: React.FC<{
|
|
|
132
183
|
delayDuration={500}
|
|
133
184
|
size={TooltipSize.SMALL}
|
|
134
185
|
>
|
|
135
|
-
|
|
136
|
-
data-element="sidebar-section"
|
|
137
|
-
data-id={tenant.label}
|
|
138
|
-
data-status={isSelected ? 'selected' : 'not selected'}
|
|
139
|
-
type="button"
|
|
140
|
-
onClick={onSelect}
|
|
141
|
-
backgroundColor={tokens.leftPanel.item.backgroundColor.default}
|
|
142
|
-
width={tokens.leftPanel.item.width}
|
|
143
|
-
height={tokens.leftPanel.item.width}
|
|
144
|
-
borderRadius={tokens.leftPanel.item.borderRadius}
|
|
145
|
-
display="flex"
|
|
146
|
-
alignItems="center"
|
|
147
|
-
justifyContent="center"
|
|
148
|
-
cursor="pointer"
|
|
149
|
-
border={
|
|
150
|
-
isSelected
|
|
151
|
-
? tokens.leftPanel.item.border.active
|
|
152
|
-
: tokens.leftPanel.item.border.default
|
|
153
|
-
}
|
|
154
|
-
aria-label={`Select tenant: ${tenant.label}`}
|
|
155
|
-
aria-pressed={isSelected}
|
|
156
|
-
style={{
|
|
157
|
-
transition: 'all 75ms ease',
|
|
158
|
-
}}
|
|
159
|
-
_hover={{
|
|
160
|
-
backgroundColor:
|
|
161
|
-
tokens.leftPanel.item.backgroundColor.hover,
|
|
162
|
-
outline: isSelected
|
|
163
|
-
? tokens.leftPanel.item.border.active
|
|
164
|
-
: tokens.leftPanel.item.border.hover,
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
<span aria-hidden="true">{tenant.icon}</span>
|
|
168
|
-
</PrimitiveButton>
|
|
186
|
+
{trigger}
|
|
169
187
|
</Tooltip>
|
|
170
188
|
)
|
|
171
189
|
}
|