@bitrise/bitkit 10.24.2-alpha-chakra.1 → 10.25.1
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/package.json +1 -1
- package/src/Components/Select/Select.stories.tsx +17 -0
- package/src/Components/Select/Select.test.tsx +96 -0
- package/src/Components/Select/Select.tsx +7 -5
- package/src/Components/Text/Text.stories.tsx +1 -0
- package/src/Components/Text/Text.tsx +5 -6
- package/src/Foundations/Breakpoints/Breakpoints.ts +5 -3
- package/src/Foundations/Responsive/Responsive.examples.tsx +95 -0
- package/src/Foundations/Responsive/Responsive.stories.mdx +6 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useResponsive.ts +23 -0
- package/src/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
3
|
+
import { useForm } from 'react-hook-form';
|
|
3
4
|
import { sortObjectByKey } from '../../utils/storyUtils';
|
|
4
5
|
import Select from './Select';
|
|
5
6
|
|
|
@@ -54,6 +55,22 @@ export const Controlled = () => {
|
|
|
54
55
|
);
|
|
55
56
|
};
|
|
56
57
|
|
|
58
|
+
export const ControlledWithReactHookForm = () => {
|
|
59
|
+
const form = useForm({
|
|
60
|
+
defaultValues: {
|
|
61
|
+
field: undefined,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const props = form.register('field');
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Select w="300px" placeholder="Test holder" {...props} isRequired>
|
|
69
|
+
{children}
|
|
70
|
+
</Select>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
57
74
|
export const WithLoading = () => (
|
|
58
75
|
<>
|
|
59
76
|
<Select w="300px" isDisabled placeholder="Test holder">
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { useForm } from 'react-hook-form';
|
|
4
|
+
import Select from './Select';
|
|
5
|
+
|
|
6
|
+
const WithReactHookForm = ({ placeholder }: { placeholder?: string }) => {
|
|
7
|
+
const form = useForm({ defaultValues: { rating: undefined } });
|
|
8
|
+
const props = form.register('rating');
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Select placeholder={placeholder} {...props}>
|
|
12
|
+
<option value="bad">Bad</option>
|
|
13
|
+
<option value="good">Good</option>
|
|
14
|
+
</Select>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const placeholder = 'What was your impression?';
|
|
19
|
+
|
|
20
|
+
describe('Select', () => {
|
|
21
|
+
it('renders placeholder option', async () => {
|
|
22
|
+
render(
|
|
23
|
+
<Select placeholder={placeholder}>
|
|
24
|
+
<option value="bad">Bad</option>
|
|
25
|
+
<option value="good">Good</option>
|
|
26
|
+
</Select>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const placeholderOption = screen.queryByRole('option', { name: placeholder });
|
|
30
|
+
expect(placeholderOption).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders placeholder option as disabled', async () => {
|
|
34
|
+
render(
|
|
35
|
+
<Select placeholder={placeholder}>
|
|
36
|
+
<option value="bad">Bad</option>
|
|
37
|
+
<option value="good">Good</option>
|
|
38
|
+
</Select>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const placeholderOption = screen.queryByRole('option', { name: placeholder });
|
|
42
|
+
expect(placeholderOption).toBeDisabled();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('placeholder option is selected if no value is set', async () => {
|
|
46
|
+
render(
|
|
47
|
+
<Select placeholder={placeholder}>
|
|
48
|
+
<option value="bad">Bad</option>
|
|
49
|
+
<option value="good">Good</option>
|
|
50
|
+
</Select>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const select = await screen.findByRole<HTMLInputElement>('combobox');
|
|
54
|
+
expect(select).toHaveValue('');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('updates selected option without placeholder', async () => {
|
|
58
|
+
render(<WithReactHookForm />);
|
|
59
|
+
|
|
60
|
+
const select = await screen.findByRole<HTMLInputElement>('combobox');
|
|
61
|
+
await userEvent.selectOptions(select, 'good');
|
|
62
|
+
expect(select).toHaveValue('good');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('updates selected option with placeholder', async () => {
|
|
66
|
+
render(<WithReactHookForm placeholder="What was your impression?" />);
|
|
67
|
+
|
|
68
|
+
const select = await screen.findByRole<HTMLInputElement>('combobox');
|
|
69
|
+
await userEvent.selectOptions(select, 'good');
|
|
70
|
+
expect(select).toHaveValue('good');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('handles defaultValue correctly without placeholder', async () => {
|
|
74
|
+
render(
|
|
75
|
+
<Select defaultValue="good">
|
|
76
|
+
<option value="bad">Bad</option>
|
|
77
|
+
<option value="good">Good</option>
|
|
78
|
+
</Select>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const select = await screen.findByRole<HTMLInputElement>('combobox');
|
|
82
|
+
expect(select).toHaveValue('good');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('handles defaultValue correctly with placeholder', async () => {
|
|
86
|
+
render(
|
|
87
|
+
<Select placeholder="What was your impression?" defaultValue="good">
|
|
88
|
+
<option value="bad">Bad</option>
|
|
89
|
+
<option value="good">Good</option>
|
|
90
|
+
</Select>,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const select = await screen.findByRole<HTMLInputElement>('combobox');
|
|
94
|
+
expect(select).toHaveValue('good');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -41,6 +41,7 @@ const Select = forwardRef<SelectProps, 'div'>((props, ref) => {
|
|
|
41
41
|
placeholder,
|
|
42
42
|
size,
|
|
43
43
|
value,
|
|
44
|
+
defaultValue,
|
|
44
45
|
...rest
|
|
45
46
|
} = props;
|
|
46
47
|
const iconSize = size === 'medium' ? '24' : '16';
|
|
@@ -48,7 +49,6 @@ const Select = forwardRef<SelectProps, 'div'>((props, ref) => {
|
|
|
48
49
|
isDisabled: isDisabled || isLoading,
|
|
49
50
|
isInvalid: isInvalid || !!errorText,
|
|
50
51
|
...rest,
|
|
51
|
-
ref,
|
|
52
52
|
};
|
|
53
53
|
const selectProperties: ChakraSelectProps = {
|
|
54
54
|
icon: isLoading ? (
|
|
@@ -61,21 +61,23 @@ const Select = forwardRef<SelectProps, 'div'>((props, ref) => {
|
|
|
61
61
|
onChange,
|
|
62
62
|
size,
|
|
63
63
|
value,
|
|
64
|
+
defaultValue,
|
|
64
65
|
variant: 'select',
|
|
65
66
|
};
|
|
66
67
|
if (placeholder) {
|
|
67
|
-
if ('value' in
|
|
68
|
-
selectProperties.value = selectProperties.value
|
|
68
|
+
if ('value' in props) {
|
|
69
|
+
selectProperties.value = selectProperties.value ?? '';
|
|
69
70
|
} else {
|
|
70
|
-
selectProperties.defaultValue = '';
|
|
71
|
+
selectProperties.defaultValue = props.defaultValue ?? '';
|
|
71
72
|
}
|
|
72
73
|
}
|
|
74
|
+
|
|
73
75
|
return (
|
|
74
76
|
<FormControl {...formControlProps}>
|
|
75
77
|
{label && <FormLabel>{label}</FormLabel>}
|
|
76
78
|
<ChakraSelect data-testid={dataTestid} ref={ref} {...selectProperties}>
|
|
77
79
|
{placeholder && (
|
|
78
|
-
<option
|
|
80
|
+
<option disabled value="">
|
|
79
81
|
{placeholder}
|
|
80
82
|
</option>
|
|
81
83
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Text as ChakraText, TextProps as ChakraTextProps, forwardRef } from '@chakra-ui/react';
|
|
1
|
+
import { Text as ChakraText, TextProps as ChakraTextProps, forwardRef, ResponsiveValue } from '@chakra-ui/react';
|
|
2
2
|
import { TextSizes } from '../../Foundations/Typography/Typography';
|
|
3
3
|
|
|
4
4
|
type TextTags =
|
|
@@ -37,21 +37,20 @@ type TextTags =
|
|
|
37
37
|
| 'var';
|
|
38
38
|
|
|
39
39
|
export interface TextProps extends ChakraTextProps {
|
|
40
|
-
align?: 'center' | 'justify' | 'left' | 'right'
|
|
40
|
+
align?: ResponsiveValue<'center' | 'justify' | 'left' | 'right'>;
|
|
41
41
|
/**
|
|
42
42
|
* Any valid HTML text tag
|
|
43
43
|
*/
|
|
44
44
|
as?: TextTags;
|
|
45
|
-
className?: string;
|
|
46
45
|
/**
|
|
47
46
|
* Font weight
|
|
48
47
|
*/
|
|
49
|
-
fontWeight?: 'bold' | 'normal'
|
|
48
|
+
fontWeight?: ResponsiveValue<'bold' | 'normal'>;
|
|
50
49
|
/**
|
|
51
50
|
* Size config (https://www.figma.com/file/grik9mTaJ5DfhydhWhXdP5/Bitkit-Foundations?node-id=211%3A12)
|
|
52
51
|
*/
|
|
53
|
-
size?: TextSizes
|
|
54
|
-
textTransform?: 'capitalize' | 'lowercase' | 'none' | 'uppercase'
|
|
52
|
+
size?: ResponsiveValue<TextSizes>;
|
|
53
|
+
textTransform?: ResponsiveValue<'capitalize' | 'lowercase' | 'none' | 'uppercase'>;
|
|
55
54
|
hasEllipsis?: boolean;
|
|
56
55
|
}
|
|
57
56
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export const BREAKPOINTS = {
|
|
2
2
|
MOBILE: 'base',
|
|
3
|
+
TABLET: 'tablet',
|
|
3
4
|
DESKTOP: 'desktop',
|
|
4
|
-
|
|
5
|
+
WIDEDESKTOP: 'wideDesktop',
|
|
5
6
|
};
|
|
6
7
|
|
|
7
8
|
const breakpoints = {
|
|
8
9
|
[BREAKPOINTS.MOBILE]: '0rem',
|
|
9
|
-
[BREAKPOINTS.
|
|
10
|
-
[BREAKPOINTS.
|
|
10
|
+
[BREAKPOINTS.TABLET]: '53rem', // 848px
|
|
11
|
+
[BREAKPOINTS.DESKTOP]: '80rem', // 1280px
|
|
12
|
+
[BREAKPOINTS.WIDEDESKTOP]: '101rem', // 1616px
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export default breakpoints;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Code } from '@chakra-ui/react';
|
|
2
|
+
import Box from '../../Components/Box/Box';
|
|
3
|
+
import Card from '../../Components/Card/Card';
|
|
4
|
+
import CardContent from '../../Components/Card/CardContent';
|
|
5
|
+
import Link from '../../Components/Link/Link';
|
|
6
|
+
import List from '../../Components/List/List';
|
|
7
|
+
import ListItem from '../../Components/List/ListItem';
|
|
8
|
+
import Provider from '../../Components/Provider/Provider';
|
|
9
|
+
import Text from '../../Components/Text/Text';
|
|
10
|
+
|
|
11
|
+
import useResponsive from '../../hooks/useResponsive';
|
|
12
|
+
|
|
13
|
+
export const Responsive = () => {
|
|
14
|
+
const { isMobile, isTablet, isDesktop, isWideDesktop } = useResponsive();
|
|
15
|
+
return (
|
|
16
|
+
<Provider>
|
|
17
|
+
<Text as="h2" size="8" marginBottom="24">
|
|
18
|
+
Responsive solutions
|
|
19
|
+
</Text>
|
|
20
|
+
|
|
21
|
+
<Card boxShadow="medium" marginBottom="48" padding="24">
|
|
22
|
+
<CardContent>
|
|
23
|
+
<Text as="h3" size="6" marginBottom="12">
|
|
24
|
+
With responsive style props
|
|
25
|
+
</Text>
|
|
26
|
+
<Text marginBottom="32">
|
|
27
|
+
Use the array syntax on style props, when the element should be always visible, but you want to change
|
|
28
|
+
margin, padding, etc on it.
|
|
29
|
+
<br />
|
|
30
|
+
<Link
|
|
31
|
+
colorScheme="purple"
|
|
32
|
+
href="https://chakra-ui.com/docs/styled-system/responsive-styles#the-array-syntax"
|
|
33
|
+
isExternal
|
|
34
|
+
isUnderlined
|
|
35
|
+
>
|
|
36
|
+
https://chakra-ui.com/docs/styled-system/responsive-styles#the-array-syntax
|
|
37
|
+
</Link>
|
|
38
|
+
</Text>
|
|
39
|
+
<Text marginBottom="8">We have 4 breakpoints:</Text>
|
|
40
|
+
<Box
|
|
41
|
+
backgroundColor={['green.40', 'purple.30', 'red.40', 'neutral.40']}
|
|
42
|
+
color="neutral.93"
|
|
43
|
+
paddingX="24"
|
|
44
|
+
paddingY="16"
|
|
45
|
+
transition="200ms"
|
|
46
|
+
>
|
|
47
|
+
This Box is
|
|
48
|
+
<List>
|
|
49
|
+
<ListItem>green below 848px (53rem)</ListItem>
|
|
50
|
+
<ListItem>purple from 848px (53rem) but below 1280px (80rem)</ListItem>
|
|
51
|
+
<ListItem>red from 1280px (80rem) but below 1616px (101rem)</ListItem>
|
|
52
|
+
<ListItem>gray from 1616px (101rem)</ListItem>
|
|
53
|
+
</List>
|
|
54
|
+
</Box>
|
|
55
|
+
</CardContent>
|
|
56
|
+
</Card>
|
|
57
|
+
|
|
58
|
+
<Card boxShadow="medium" marginBottom="24" padding="24">
|
|
59
|
+
<CardContent>
|
|
60
|
+
<Text as="h3" size="6" marginBottom="12">
|
|
61
|
+
With useResponsive hook
|
|
62
|
+
</Text>
|
|
63
|
+
<Text marginBottom="24">
|
|
64
|
+
Use this hook when you want to render something conditionally. It will return an object with 1-4 booleans,
|
|
65
|
+
like:
|
|
66
|
+
<br />
|
|
67
|
+
<Code backgroundColor="neutral.20" color="neutral.93" paddingY="4" paddingX="8">
|
|
68
|
+
const { isMobile, isTablet, isDesktop, isWideDesktop } = useResponsive();
|
|
69
|
+
</Code>
|
|
70
|
+
</Text>
|
|
71
|
+
{isMobile && (
|
|
72
|
+
<Box backgroundColor="green.40" color="neutral.93" paddingX="24" paddingY="16">
|
|
73
|
+
This Box is rendered below 848px (53rem), because isMobile is true.
|
|
74
|
+
</Box>
|
|
75
|
+
)}
|
|
76
|
+
{isTablet && (
|
|
77
|
+
<Box backgroundColor="purple.30" color="neutral.93" paddingX="24" paddingY="16">
|
|
78
|
+
This Box is rendered from 848px (53rem) but below 1280px (80rem), because isTablet is true.
|
|
79
|
+
</Box>
|
|
80
|
+
)}
|
|
81
|
+
{isDesktop && (
|
|
82
|
+
<Box backgroundColor="red.40" color="neutral.93" paddingX="24" paddingY="16">
|
|
83
|
+
This Box is rendered from 1280px (80rem) but below 1616px, because isDesktop is true.
|
|
84
|
+
</Box>
|
|
85
|
+
)}
|
|
86
|
+
{isWideDesktop && (
|
|
87
|
+
<Box backgroundColor="neutral.40" color="neutral.93" paddingX="24" paddingY="16">
|
|
88
|
+
This Box is rendered from 1616px (101rem), because isWideDesktop is true.
|
|
89
|
+
</Box>
|
|
90
|
+
)}
|
|
91
|
+
</CardContent>
|
|
92
|
+
</Card>
|
|
93
|
+
</Provider>
|
|
94
|
+
);
|
|
95
|
+
};
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useMediaQuery } from '@chakra-ui/react';
|
|
2
|
+
import breakpoints, { BREAKPOINTS } from '../Foundations/Breakpoints/Breakpoints';
|
|
3
|
+
|
|
4
|
+
const minusOnePixel = (value: string) => {
|
|
5
|
+
const valueInPixels = parseInt(value, 10) * 16;
|
|
6
|
+
return `${valueInPixels - 1}px`;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const mediaQueries = [
|
|
10
|
+
`(min-width: ${breakpoints[BREAKPOINTS.MOBILE]}) and (max-width: ${minusOnePixel(breakpoints[BREAKPOINTS.TABLET])})`,
|
|
11
|
+
`(min-width: ${breakpoints[BREAKPOINTS.TABLET]}) and (max-width: ${minusOnePixel(breakpoints[BREAKPOINTS.DESKTOP])})`,
|
|
12
|
+
`(min-width: ${breakpoints[BREAKPOINTS.DESKTOP]}) and (max-width: ${minusOnePixel(
|
|
13
|
+
breakpoints[BREAKPOINTS.WIDEDESKTOP],
|
|
14
|
+
)})`,
|
|
15
|
+
`(min-width: ${breakpoints[BREAKPOINTS.WIDEDESKTOP]})`,
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const useResponsive = () => {
|
|
19
|
+
const [isMobile, isTablet, isDesktop, isWideDesktop] = useMediaQuery(mediaQueries);
|
|
20
|
+
return { isMobile, isTablet, isDesktop, isWideDesktop };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default useResponsive;
|