Skip to content

Commit e00172b

Browse files
authored
feat(blade): Changes in components for X migration (#2448)
* chore: add styled props to iconbutton * chore: button group changes * chore: expose drawer ref * chore: expose event from radio group onchange * chore: fix tests * chore: add onClick to sidenavlink * chore: add onClick to sidenav collapsible click * Create young-dragons-poke.md
1 parent db5c906 commit e00172b

20 files changed

+139
-100
lines changed

.changeset/young-dragons-poke.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@razorpay/blade": minor
3+
---
4+
5+
feat(blade): Changes in components for X migration
6+
7+
**Drawer:**
8+
- Changed the drawer's showOverlay behaviour to not mandate the overlay on level2 stacking
9+
- Exposed ref
10+
11+
**RadioGroup:**
12+
- Exposed event in onChange
13+
14+
**ButtonGroup:**
15+
- Added styled props
16+
- Added support for Tooltip inside ButtonGroup
17+
18+
**IconButton:**
19+
- Added styledProps
20+
21+
**SideNavLink:**
22+
- Added onClick

packages/blade/src/components/Button/IconButton/IconButton.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { BladeCommonEvents } from '~components/types';
99
import type { Platform } from '~utils';
1010
import type { SubtleOrIntense } from '~tokens/theme/theme';
1111
import { makeAnalyticsAttribute } from '~utils/makeAnalyticsAttribute';
12+
import type { StyledPropsBlade } from '~components/Box/styledProps';
1213

1314
type IconButtonProps = {
1415
/**
@@ -46,6 +47,7 @@ type IconButtonProps = {
4647
_tabIndex?: number;
4748
} & DataAnalyticsAttribute &
4849
BladeCommonEvents &
50+
StyledPropsBlade &
4951
Platform.Select<{
5052
web: {
5153
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
@@ -108,6 +110,7 @@ const _IconButton: React.ForwardRefRenderFunction<BladeElementRef, IconButtonPro
108110
onTouchEnd={onTouchEnd}
109111
onTouchStart={onTouchStart}
110112
{...makeAnalyticsAttribute(rest)}
113+
{...rest}
111114
/>
112115
);
113116
};

packages/blade/src/components/Button/IconButton/StyledIconButton.web.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getFocusRingStyles } from '~utils/getFocusRingStyles';
1313
import { throwBladeError } from '~utils/logger';
1414
import getIn from '~utils/lodashButBetter/get';
1515
import { makeAnalyticsAttribute } from '~utils/makeAnalyticsAttribute';
16+
import { useStyledProps } from '~components/Box/styledProps';
1617

1718
type StyledButtonProps = {
1819
emphasis: SubtleOrIntense;
@@ -23,7 +24,7 @@ type StyledButtonProps = {
2324
const StyledButton = styled.button<StyledButtonProps>((props) => {
2425
const { theme, emphasis } = props;
2526
const motionToken = theme.motion;
26-
27+
const styledPropsCSSObject = useStyledProps(props);
2728
const emphasisColor = emphasis === 'intense' ? 'gray' : 'staticWhite';
2829

2930
if (__DEV__) {
@@ -74,6 +75,7 @@ const StyledButton = styled.button<StyledButtonProps>((props) => {
7475
'&:active': {
7576
color: theme.colors.interactive.icon[emphasisColor].subtle,
7677
},
78+
...styledPropsCSSObject,
7779
};
7880
});
7981

@@ -121,6 +123,7 @@ const StyledIconButton = React.forwardRef<HTMLButtonElement, StyledIconButtonPro
121123
{...makeAccessible({ label: accessibilityLabel })}
122124
{...metaAttribute({ name: MetaConstants.IconButton, testID })}
123125
{...makeAnalyticsAttribute(rest)}
126+
{...rest}
124127
>
125128
<Icon size={size} color={isDisabled ? 'interactive.icon.gray.disabled' : 'currentColor'} />
126129
</StyledButton>

packages/blade/src/components/Button/IconButton/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { IconComponent } from '~components/Icons';
33
import type { DataAnalyticsAttribute, RemoveUndefinedFromUnion, TestID } from '~utils/types';
44
import type { BladeCommonEvents } from '~components/types';
55
import type { SubtleOrIntense } from '~tokens/theme/theme';
6+
import type { StyledPropsBlade } from '~components/Box/styledProps';
67

78
export type StyledIconButtonProps = {
89
icon: IconComponent;
@@ -15,4 +16,5 @@ export type StyledIconButtonProps = {
1516
onClick?: IconButtonProps['onClick'];
1617
} & TestID &
1718
BladeCommonEvents &
18-
DataAnalyticsAttribute;
19+
DataAnalyticsAttribute &
20+
StyledPropsBlade;

packages/blade/src/components/ButtonGroup/ButtonGroup.web.tsx

+7-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { ReactElement } from 'react';
21
import React from 'react';
32
import styled from 'styled-components';
43
import type { ButtonGroupProps } from './types';
@@ -13,10 +12,9 @@ import type { DotNotationToken } from '~utils/lodashButBetter/get';
1312
import getIn from '~utils/lodashButBetter/get';
1413
import { getBackgroundColorToken } from '~components/Button/BaseButton/BaseButton';
1514
import type { Theme } from '~components/BladeProvider';
16-
import { throwBladeError } from '~utils/logger';
17-
import { isValidAllowedChildren } from '~utils/isValidAllowedChildren';
1815
import type { BladeElementRef } from '~utils/types';
1916
import { makeAnalyticsAttribute } from '~utils/makeAnalyticsAttribute';
17+
import { useVerifyAllowedChildren } from '~utils/useVerifyAllowedChildren';
2018

2119
const getDividerColorToken = ({
2220
color,
@@ -74,6 +72,12 @@ const _ButtonGroup = (
7472
isFullWidth,
7573
};
7674

75+
useVerifyAllowedChildren({
76+
allowedComponents: ['Button', 'Dropdown', 'Tooltip', 'Popover'],
77+
componentName: 'ButtonGroup',
78+
children,
79+
});
80+
7781
return (
7882
<ButtonGroupProvider value={contextValue}>
7983
<StyledButtonGroup
@@ -88,26 +92,6 @@ const _ButtonGroup = (
8892
role="group"
8993
>
9094
{React.Children.map(children, (child, index) => {
91-
if (__DEV__) {
92-
// throw error if child is not a button or dropdown with button trigger
93-
/* eslint-disable no-restricted-properties */
94-
if (
95-
!isValidAllowedChildren(child, 'Button') &&
96-
!(
97-
isValidAllowedChildren(child, 'Dropdown') &&
98-
(child as ReactElement).props.children.some((c: ReactElement) =>
99-
isValidAllowedChildren(c, 'DropdownButton'),
100-
)
101-
)
102-
) {
103-
throwBladeError({
104-
moduleName: 'ButtonGroup',
105-
message: `Only "Button" or "Dropdown" component with Button trigger are allowed as children.`,
106-
});
107-
}
108-
/* eslint-enable no-restricted-properties */
109-
}
110-
11195
return (
11296
<>
11397
{child}

packages/blade/src/components/ButtonGroup/__tests__/ButtonGroup.web.test.tsx

+1-35
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { Button } from '~components/Button/Button';
77
import { ChevronDownIcon, PlusIcon } from '~components/Icons';
88
import { Dropdown, DropdownButton, DropdownOverlay } from '~components/Dropdown';
99
import { ActionList, ActionListItem } from '~components/ActionList';
10-
import { AutoComplete } from '~components/Input/DropdownInputTriggers';
1110

1211
beforeAll(() => jest.spyOn(console, 'error').mockImplementation());
1312
afterAll(() => jest.restoreAllMocks());
@@ -168,40 +167,7 @@ describe('<ButtonGroup />', () => {
168167
</ButtonGroup>,
169168
),
170169
).toThrowError(
171-
'[Blade: ButtonGroup]: Only "Button" or "Dropdown" component with Button trigger are allowed as children.',
172-
);
173-
});
174-
175-
it('should throw error with invalid dropdown children', () => {
176-
expect(() =>
177-
renderWithTheme(
178-
<ButtonGroup>
179-
<Button icon={PlusIcon}>Payout</Button>
180-
<Dropdown selectionType="single">
181-
<AutoComplete
182-
label="City"
183-
placeholder="Select your City"
184-
name="action"
185-
onChange={({ name, values }) => {
186-
console.log({ name, values });
187-
}}
188-
onInputValueChange={({ name, value }) => {
189-
console.log({ name, value });
190-
}}
191-
/>
192-
<DropdownOverlay>
193-
<ActionList>
194-
<ActionListItem title="Mumbai" value="mumbai" />
195-
<ActionListItem title="Pune" value="pune" />
196-
<ActionListItem title="Bangalore" value="bangalore" />
197-
<ActionListItem title="Mysore" value="mysore" />
198-
</ActionList>
199-
</DropdownOverlay>
200-
</Dropdown>
201-
</ButtonGroup>,
202-
),
203-
).toThrowError(
204-
'[Blade: ButtonGroup]: Only "Button" or "Dropdown" component with Button trigger are allowed as children.',
170+
'[Blade: ButtonGroup]: Only `Button, Dropdown, Tooltip, Popover` components are accepted in `ButtonGroup` children',
205171
);
206172
});
207173
});

packages/blade/src/components/ButtonGroup/docs/ButtonGroup.stories.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getStyledPropsArgTypes } from '~components/Box/BaseBox/storybookArgType
1010
import { RefreshIcon, ShareIcon, DownloadIcon, ChevronDownIcon, PlusIcon } from '~components/Icons';
1111
import { Dropdown, DropdownButton, DropdownOverlay } from '~components/Dropdown';
1212
import { ActionList, ActionListItem } from '~components/ActionList';
13+
import { Tooltip } from '~components/Tooltip';
1314

1415
const Page = (): React.ReactElement => {
1516
return (
@@ -76,7 +77,9 @@ const ButtonGroupDropdownTemplate: StoryFn<typeof ButtonGroupComponent> = (args)
7677
return (
7778
<Box display="flex" alignItems="center" justifyContent="center">
7879
<ButtonGroupComponent {...args}>
79-
<Button icon={PlusIcon}>Payout</Button>
80+
<Tooltip content="Create a new payout">
81+
<Button icon={PlusIcon}>Payout</Button>
82+
</Tooltip>
8083
<Dropdown>
8184
<DropdownButton icon={ChevronDownIcon} />
8285
<DropdownOverlay defaultPlacement="bottom-end">

packages/blade/src/components/ButtonGroup/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { StyledPropsBlade } from '~components/Box/styledProps';
12
import type { ButtonProps } from '~components/Button/Button';
23
import type { DataAnalyticsAttribute } from '~utils/types';
34

@@ -38,7 +39,8 @@ type ButtonGroupProps = {
3839
* Test ID for automation
3940
*/
4041
testID?: string;
41-
} & DataAnalyticsAttribute;
42+
} & DataAnalyticsAttribute &
43+
StyledPropsBlade;
4244

4345
type StyledButtonGroupProps = Pick<
4446
ButtonGroupProps,

packages/blade/src/components/Drawer/Drawer.web.tsx

+21-15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { metaAttribute, MetaConstants } from '~utils/metaAttribute';
2121
import { useId } from '~utils/useId';
2222
import { useVerifyAllowedChildren } from '~utils/useVerifyAllowedChildren';
2323
import { makeAnalyticsAttribute } from '~utils/makeAnalyticsAttribute';
24+
import type { BladeElementRef } from '~utils/types';
25+
import { mergeRefs } from '~utils/useMergeRefs';
2426

2527
const SHOW_DRAWER = 'show-drawer';
2628

@@ -65,18 +67,21 @@ const DrawerOverlay = styled(FloatingOverlay)(({ theme }) => {
6567
};
6668
});
6769

68-
const _Drawer = ({
69-
isOpen,
70-
onDismiss,
71-
zIndex = componentZIndices.drawer,
72-
children,
73-
accessibilityLabel,
74-
showOverlay = true,
75-
initialFocusRef,
76-
isLazy = true,
77-
testID,
78-
...rest
79-
}: DrawerProps): React.ReactElement => {
70+
const _Drawer: React.ForwardRefRenderFunction<BladeElementRef, DrawerProps> = (
71+
{
72+
isOpen,
73+
onDismiss,
74+
zIndex = componentZIndices.drawer,
75+
children,
76+
accessibilityLabel,
77+
showOverlay = true,
78+
initialFocusRef,
79+
isLazy = true,
80+
testID,
81+
...rest
82+
},
83+
ref,
84+
) => {
8085
const closeButtonRef = React.useRef<HTMLDivElement>(null);
8186
const [zIndexState, setZIndexState] = React.useState<number>(zIndex);
8287

@@ -155,7 +160,7 @@ const _Drawer = ({
155160
{...makeAnalyticsAttribute(rest)}
156161
zIndex={zIndexState}
157162
>
158-
{showOverlay || stackingLevel === 2 ? (
163+
{showOverlay ? (
159164
<DrawerOverlay
160165
onClick={() => {
161166
onDismiss();
@@ -184,7 +189,7 @@ const _Drawer = ({
184189
height="100%"
185190
display="flex"
186191
flexDirection="column"
187-
ref={refs.setFloating}
192+
ref={mergeRefs(ref, refs.setFloating)}
188193
onKeyDown={(event) => {
189194
if (event?.key === 'Escape' || event?.code === 'Escape') {
190195
onDismiss();
@@ -238,7 +243,8 @@ const _Drawer = ({
238243
*
239244
*
240245
*/
241-
const Drawer = assignWithoutSideEffects(_Drawer, {
246+
const Drawer = assignWithoutSideEffects(React.forwardRef(_Drawer), {
247+
displayName: 'Drawer',
242248
componentId: drawerComponentIds.Drawer,
243249
});
244250

packages/blade/src/components/Radio/Radio.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ const _Radio: React.ForwardRefRenderFunction<BladeElementRef, RadioProps> = (
8888
const isReactNative = getPlatformType() === 'react-native';
8989
const _size = groupProps.size ?? size;
9090

91-
const handleChange: OnChange = ({ isChecked, value }) => {
91+
const handleChange: OnChange = ({ isChecked, value, event }) => {
9292
if (isChecked) {
93-
groupProps?.state?.setValue(value!);
93+
groupProps?.state?.setValue(value!, event);
9494
} else {
9595
groupProps?.state?.removeValue();
9696
}

packages/blade/src/components/Radio/RadioGroup/RadioGroup.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,15 @@ type RadioGroupProps = {
7676
/**
7777
* The callback invoked when any of the radio's state changes
7878
*/
79-
onChange?: ({ name, value }: { name: string | undefined; value: string }) => void;
79+
onChange?: ({
80+
name,
81+
value,
82+
event,
83+
}: {
84+
name: string | undefined;
85+
value: string;
86+
event: React.ChangeEvent<HTMLInputElement>;
87+
}) => void;
8088
/**
8189
* The name of the input field in a radio
8290
* (Useful for form submission).

packages/blade/src/components/Radio/RadioGroup/useRadioGroup.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type UseRadioGroupProps = Pick<
2222

2323
export type State = {
2424
value: string;
25-
setValue(value: string): void;
25+
setValue(value: string, event: React.ChangeEvent<Element>): void;
2626
removeValue(): void;
2727
isChecked(value: string): boolean;
2828
};
@@ -52,18 +52,20 @@ const useRadioGroup = ({
5252
const [checkedValue, setValue] = useControllableState({
5353
value,
5454
defaultValue,
55-
onChange: (v: string) => onChange?.({ value: v, name: fallbackName }),
55+
onChange: (v, event) => {
56+
onChange?.({ value: v, name: fallbackName, event });
57+
},
5658
});
5759

5860
const state = React.useMemo<State>(() => {
5961
return {
6062
value: checkedValue,
61-
setValue(v: string): void {
63+
setValue(v, event): void {
6264
if (isDisabled) {
6365
return;
6466
}
6567

66-
setValue(() => v);
68+
setValue(() => v, false, event);
6769
},
6870
removeValue(): void {
6971
if (isDisabled) {

packages/blade/src/components/Radio/useRadio.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type OnChange = ({
1414
value,
1515
}: {
1616
isChecked: boolean;
17-
event?: React.ChangeEvent;
17+
event: React.ChangeEvent;
1818
value?: string;
1919
}) => void;
2020

0 commit comments

Comments
 (0)