Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/mobile/src/controls/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Control, type ControlBaseProps, type ControlIconProps } from './Control

export type CheckboxBaseProps<CheckboxValue extends string> = Omit<
ControlBaseProps<CheckboxValue>,
'controlColor'
'controlColor' | 'controlSize' | 'dotSize'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is frustrating how we currently do control on mobile, I think we should refactor to match web style in the future. Unless there is a good reason I'm not aware of.

> & {
/**
* Sets the checked/active color of the checkbox.
Expand All @@ -23,6 +23,11 @@ export type CheckboxBaseProps<CheckboxValue extends string> = Omit<
* @default 100
*/
borderWidth?: ThemeVars.BorderWidth;
/**
* Sets the outer checkbox control size in pixels.
* @default theme.controlSize.checkboxSize
*/
controlSize?: number;
};

export type CheckboxProps<CheckboxValue extends string> = CheckboxBaseProps<CheckboxValue>;
Expand All @@ -42,10 +47,11 @@ const CheckboxIcon = memo(
animatedScaleValue,
animatedOpacityValue,
testID,
controlSize,
}: React.PropsWithChildren<ControlIconProps>) => {
const filled = checked || indeterminate;
const theme = useTheme();
const checkboxSize = theme.controlSize.checkboxSize;
const checkboxSize = controlSize ?? theme.controlSize.checkboxSize;
const iconPadding = checkboxSize / 5;
const iconSize = checkboxSize - iconPadding;

Expand Down
4 changes: 3 additions & 1 deletion packages/mobile/src/controls/CheckboxCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type CheckboxCellBaseProps<CheckboxValue extends string> = {
rowGap?: ThemeVars.Space;
pressedBorderColor?: ThemeVars.Color;
pressedBorderWidth?: ThemeVars.BorderWidth;
} & Omit<ControlBaseProps<CheckboxValue>, 'style' | 'children' | 'title'> &
} & Omit<ControlBaseProps<CheckboxValue>, 'style' | 'children' | 'title' | 'dotSize'> &
Omit<PressableBaseProps, 'children' | 'noScaleOnPress'>;

export type CheckboxCellProps<CheckboxValue extends string> =
Expand Down Expand Up @@ -61,6 +61,7 @@ const CheckboxCellWithRef = forwardRef(function CheckboxCell<CheckboxValue exten
background = 'bg',
borderColor = 'bgLine',
controlColor,
controlSize,
accessibilityLabel,
accessibilityHint,
testID,
Expand Down Expand Up @@ -219,6 +220,7 @@ const CheckboxCellWithRef = forwardRef(function CheckboxCell<CheckboxValue exten
accessible={false}
checked={!!checked}
controlColor={controlColor}
controlSize={controlSize}
disabled={disabled}
indeterminate={indeterminate}
pointerEvents="none"
Expand Down
17 changes: 17 additions & 0 deletions packages/mobile/src/controls/Control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type ControlIconProps = SharedProps & {
borderRadius?: ThemeVars.BorderRadius;
borderWidth?: ThemeVars.BorderWidth;
elevation?: ElevationLevels;
controlSize?: number;
dotSize?: number;
animatedScaleValue: Animated.Value;
animatedOpacityValue: Animated.Value;
accessible?: boolean;
Expand Down Expand Up @@ -71,6 +73,15 @@ export type ControlBaseProps<ControlValue extends string> = Omit<
controlColor?: ThemeVars.Color;
/** Sets the elevation/drop shadow of the control. */
elevation?: ElevationLevels;
/**
* Sets the control size in pixels.
*/
controlSize?: number;
/**
* Sets the inner dot size in pixels.
* @default 2/3 of controlSize
*/
dotSize?: number;
style?: ViewStyle;
};

Expand Down Expand Up @@ -111,6 +122,8 @@ const ControlWithRef = forwardRef(function ControlWithRef<ControlValue extends s
borderColor,
borderRadius,
borderWidth,
controlSize,
dotSize,
...props
}: ControlProps<ControlValue>,
ref: React.ForwardedRef<View>,
Expand Down Expand Up @@ -230,7 +243,9 @@ const ControlWithRef = forwardRef(function ControlWithRef<ControlValue extends s
borderWidth={borderWidth}
checked={checked}
controlColor={controlColor}
controlSize={controlSize}
disabled={pressDisabled}
dotSize={dotSize}
elevation={elevation}
indeterminate={indeterminate}
pressed={pressed}
Expand Down Expand Up @@ -264,7 +279,9 @@ const ControlWithRef = forwardRef(function ControlWithRef<ControlValue extends s
borderWidth,
checked,
controlColor,
controlSize,
disabled,
dotSize,
elevation,
getLabelStyle,
iconWrapperStyles,
Expand Down
31 changes: 25 additions & 6 deletions packages/mobile/src/controls/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const styles = StyleSheet.create({

export type RadioBaseProps<RadioValue extends string> = Omit<
ControlBaseProps<RadioValue>,
'controlColor'
'controlColor' | 'controlSize' | 'dotSize'
> & {
/**
* Sets the checked/active color of the radio.
Expand All @@ -30,14 +30,32 @@ export type RadioBaseProps<RadioValue extends string> = Omit<
* @default 100
*/
borderWidth?: ThemeVars.BorderWidth;
/**
* Sets the outer radio control size in pixels.
* @default theme.controlSize.radioSize
*/
controlSize?: number;
/**
* Sets the inner dot size in pixels.
* @default 2/3 of controlSize
*/
dotSize?: number;
};

export type RadioProps<RadioValue extends string> = RadioBaseProps<RadioValue>;

const DotSvg = ({ color = 'black', width = 20 }: { color?: ColorValue; width?: number }) => {
const DotSvg = ({
color = 'black',
width = 20,
dotSize = (2 * width) / 3,
}: {
color?: ColorValue;
width?: number;
dotSize?: number;
}) => {
return (
<Svg fill="none" height={width} viewBox={`0 0 ${width} ${width}`} width={width}>
<Circle cx="50%" cy="50%" fill={color} r={width / 3} />
<Circle cx="50%" cy="50%" fill={color} r={dotSize / 2} />
</Svg>
);
};
Expand All @@ -53,11 +71,13 @@ const RadioIcon: React.FC<React.PropsWithChildren<ControlIconProps>> = ({
animatedScaleValue,
animatedOpacityValue,
controlColor = 'bgPrimary',
controlSize,
dotSize,
borderColor = checked ? controlColor : 'bgLineHeavy',
testID,
}) => {
const theme = useTheme();
const radioSize = theme.controlSize.radioSize;
const radioSize = controlSize ?? theme.controlSize.radioSize;

return (
<Interactable
Expand All @@ -81,7 +101,7 @@ const RadioIcon: React.FC<React.PropsWithChildren<ControlIconProps>> = ({
style={{ transform: [{ scale: animatedScaleValue }], opacity: animatedOpacityValue }}
>
<Box testID="radio-icon">
<DotSvg color={theme.color[controlColor]} width={radioSize} />
<DotSvg color={theme.color[controlColor]} dotSize={dotSize} width={radioSize} />
</Box>
</Animated.View>
</Interactable>
Expand All @@ -96,7 +116,6 @@ const RadioWithRef = forwardRef(function Radio<RadioValue extends string>(
typeof children === 'string' && accessibilityLabel === undefined
? children
: accessibilityLabel;

return (
<Control<RadioValue>
{...props}
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/controls/RadioCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type RadioCellBaseProps<RadioValue extends string> = {
rowGap?: ThemeVars.Space;
pressedBorderColor?: ThemeVars.Color;
pressedBorderWidth?: ThemeVars.BorderWidth;
} & Omit<ControlBaseProps<RadioValue>, 'style' | 'children' | 'title'> &
} & Omit<ControlBaseProps<RadioValue>, 'style' | 'children' | 'title' | 'controlSize' | 'dotSize'> &
Omit<PressableProps, 'children' | 'noScaleOnPress'>;

export type RadioCellProps<RadioValue extends string> = RadioCellBaseProps<RadioValue> & {
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/controls/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Control, type ControlBaseProps, type ControlIconProps } from './Control

export type SwitchBaseProps<SwitchValue extends string> = Omit<
ControlBaseProps<SwitchValue>,
'style'
'style' | 'controlSize' | 'dotSize'
>;

export type SwitchProps<SwitchValue extends string> = SwitchBaseProps<SwitchValue>;
Expand Down
15 changes: 15 additions & 0 deletions packages/mobile/src/controls/__tests__/Checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ describe('Checkbox', () => {
expect(screen.getByTestId('mock-checkbox')).toBeAccessible();
});

it('applies controlSize to checkbox container', () => {
render(
<DefaultThemeProvider>
<Checkbox checked controlSize={60} testID="test-checkbox">
Checked
</Checkbox>
</DefaultThemeProvider>,
);

expect(screen.getByTestId('test-checkbox')).toHaveStyle({
width: 60,
height: 60,
});
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went ahead and added support for Checkbox as well

Image Image

I thought about the others in ThemeProvider like Tile and Switch but those weren't as simple as a 'controlSize' so better left for another day.


it('renders a minus icon when indeterminate', () => {
render(
<DefaultThemeProvider>
Expand Down
38 changes: 38 additions & 0 deletions packages/mobile/src/controls/__tests__/RadioGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Pressable } from 'react-native';
import { Circle } from 'react-native-svg';
import { fireEvent, render, screen } from '@testing-library/react-native';

import { Text } from '../../typography/Text';
Expand Down Expand Up @@ -158,4 +159,41 @@ describe('Radio', () => {
borderColor: 'rgb(9,133,81)', // This corresponds to bgPositive in defaultTheme
});
});

it('applies controlSize to radio container', () => {
render(
<DefaultThemeProvider>
<Radio checked controlSize={60} testID="test-radio">
Radio
</Radio>
</DefaultThemeProvider>,
);

expect(screen.getByTestId('test-radio')).toHaveStyle({
width: 60,
height: 60,
});
});

it('defaults dotSize to two thirds of controlSize and supports explicit dotSize', () => {
const { rerender } = render(
<DefaultThemeProvider>
<Radio checked controlSize={60} testID="test-radio">
Radio
</Radio>
</DefaultThemeProvider>,
);

expect(screen.UNSAFE_getByType(Circle).props.r).toBe(20);

rerender(
<DefaultThemeProvider>
<Radio checked controlSize={60} dotSize={30} testID="test-radio">
Radio
</Radio>
</DefaultThemeProvider>,
);

expect(screen.UNSAFE_getByType(Circle).props.r).toBe(15);
});
});
12 changes: 8 additions & 4 deletions packages/web/src/controls/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ import { Control, type ControlBaseProps } from './Control';

const checkboxCss = css`
position: relative;
width: var(--controlSize-checkboxSize);
height: var(--controlSize-checkboxSize);

border-style: solid;

transition:
Expand Down Expand Up @@ -50,6 +47,11 @@ export type CheckboxBaseProps<CheckboxValue extends string> = ControlBaseProps<C
* @default 100
*/
borderWidth?: ThemeVars.BorderWidth;
/**
* Sets the outer checkbox control size in pixels.
* @default theme.controlSize.checkboxSize
*/
controlSize?: number;
};

export type CheckboxProps<CheckboxValue extends string> = CheckboxBaseProps<CheckboxValue>;
Expand All @@ -65,13 +67,14 @@ const CheckboxWithRef = forwardRef(function CheckboxWithRef<CheckboxValue extend
borderRadius = 100,
borderWidth = 100,
elevation,
controlSize,
...props
}: CheckboxProps<CheckboxValue>,
ref: React.ForwardedRef<HTMLInputElement>,
) {
const filled = checked || indeterminate;
const theme = useTheme();
const checkboxSize = theme.controlSize.checkboxSize;
const checkboxSize = controlSize ?? theme.controlSize.checkboxSize;
const iconPadding = checkboxSize / 5;
const iconSize = checkboxSize - iconPadding;

Expand Down Expand Up @@ -116,6 +119,7 @@ const CheckboxWithRef = forwardRef(function CheckboxWithRef<CheckboxValue extend
flexShrink={0}
justifyContent="center"
role="presentation"
style={{ width: checkboxSize, height: checkboxSize }}
testID="checkbox-outer"
>
<motion.div {...innerContainerMotionProps} data-testid="checkbox-inner">
Expand Down
7 changes: 7 additions & 0 deletions packages/web/src/controls/CheckboxCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export type CheckboxCellBaseProps<CheckboxValue extends string> = Omit<
'onChange' | 'title' | 'children' | 'iconStyle' | 'labelStyle' | 'checked'
> & {
checked?: boolean;
/**
* Sets the outer checkbox control size in pixels.
* @default theme.controlSize.checkboxSize
*/
controlSize?: number;
title: React.ReactNode;
description?: React.ReactNode;
onChange?: (inputChangeEvent: React.ChangeEvent<HTMLInputElement>) => void;
Expand Down Expand Up @@ -87,6 +92,7 @@ const CheckboxCellWithRef = forwardRef(function CheckboxCell<CheckboxValue exten
noScaleOnPress = true,
readOnly,
indeterminate,
controlSize,
className,
classNames,
styles,
Expand Down Expand Up @@ -142,6 +148,7 @@ const CheckboxCellWithRef = forwardRef(function CheckboxCell<CheckboxValue exten
aria-describedby={ariaDescribedBy}
aria-labelledby={ariaLabelledBy}
checked={!!checked}
controlSize={controlSize}
disabled={disabled}
indeterminate={indeterminate}
onChange={onChange}
Expand Down
Loading
Loading