-
Notifications
You must be signed in to change notification settings - Fork 102
feat: Add size to various components, deprecating compact #809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cb-ekuersch
wants to merge
22
commits into
master
Choose a base branch
from
tshirts2
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
a18dc24
feat(CDS-2168): adopt t-shirt size prop for Button
cb-ekuersch 15c4a2c
feat(CDS-2445): adopt t-shirt size prop for Icon Button
cb-ekuersch 4109b23
feat(CDS-2448): adopt t-shirt size prop for Slide Button
cb-ekuersch eb43f87
feat(CDS-2223, CDS-2224): adopt t-shirt size prop for Chip family
cb-ekuersch 8cf73a7
feat(CDS-2224): adopt t-shirt size prop for Select Chip (alpha)
cb-ekuersch 31eb5c3
feat(CDS-2173): adopt t-shirt size prop for TabbedChips (alpha)
cb-ekuersch d0e6268
feat: add t-shirt size prop to TextInput foundation
cb-ekuersch 298ba1b
feat(CDS-2450): adopt t-shirt size prop for Search Input
cb-ekuersch 553d4ef
feat(CDS-2449): adopt t-shirt size prop for Select (alpha)
cb-ekuersch 1fc6361
feat(CDS-2451): adopt t-shirt size prop for Date Picker
cb-ekuersch ecfe173
feat: add cross-component size composition stories
cb-ekuersch 6a797e4
fix: align input field heights and inside-label placement across sizes
cb-ekuersch c856430
refactor: address PR review feedback for t-shirt sizing
cb-ekuersch c2da773
feat: ensure Combobox (alpha) supports t-shirt size and deprecated co…
cb-ekuersch 2d0f995
add/update siz composition stories
cb-ekuersch 570f833
adjust paddles size for TabbedChips sizes
cb-ekuersch c597753
Improve gap spacing between inside label and text input element
cb-ekuersch cce3fe3
improve organization/convention of web stories
cb-ekuersch 0405317
WIP - fix some issues the agent found while building the migrator and…
cb-ekuersch 3e7c19c
fix: remove references to deprecated compact props internally, fix ba…
cb-ekuersch 09dcd52
address PR feedbkac
cb-ekuersch 8b57baf
remove size examples from deprecated selectchip
cb-ekuersch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
packages/common/src/chips/__tests__/getMediaChipSpacingProps.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { getMediaChipSpacingProps } from '../getMediaChipSpacingProps'; | ||
|
|
||
| describe('getMediaChipSpacingProps', () => { | ||
| const sizeDependentCompositions = [ | ||
| { name: 'label only', start: false, children: true, end: false }, | ||
| { name: 'start + label', start: true, children: true, end: false }, | ||
| { name: 'label + end', start: false, children: true, end: true }, | ||
| { name: 'start + label + end', start: true, children: true, end: true }, | ||
| ] as const; | ||
|
|
||
| describe('backward compatibility with compact', () => { | ||
| it.each(sizeDependentCompositions)( | ||
| 'compact:true deep-equals size:"xs" for $name', | ||
| ({ start, children, end }) => { | ||
| expect(getMediaChipSpacingProps({ compact: true, start, children, end })).toEqual( | ||
| getMediaChipSpacingProps({ size: 'xs', start, children, end }), | ||
| ); | ||
| }, | ||
| ); | ||
|
|
||
| it.each(sizeDependentCompositions)( | ||
| 'compact:false deep-equals size:"s" for $name', | ||
| ({ start, children, end }) => { | ||
| expect(getMediaChipSpacingProps({ compact: false, start, children, end })).toEqual( | ||
| getMediaChipSpacingProps({ size: 's', start, children, end }), | ||
| ); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| describe('default resolution', () => { | ||
| it.each(sizeDependentCompositions)( | ||
| 'omitting both size and compact resolves to the s output for $name', | ||
| ({ start, children, end }) => { | ||
| expect(getMediaChipSpacingProps({ start, children, end })).toEqual( | ||
| getMediaChipSpacingProps({ size: 's', start, children, end }), | ||
| ); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| describe('size wins over compact', () => { | ||
| it.each(sizeDependentCompositions)( | ||
| 'size:"s" + compact:true resolves to the s output for $name', | ||
| ({ start, children, end }) => { | ||
| expect( | ||
| getMediaChipSpacingProps({ size: 's', compact: true, start, children, end }), | ||
| ).toEqual(getMediaChipSpacingProps({ size: 's', start, children, end })); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| describe('media-only compositions are size-agnostic', () => { | ||
| it('start only is unaffected by size', () => { | ||
| const expected = { paddingY: 1, paddingX: 1 }; | ||
| expect(getMediaChipSpacingProps({ start: true, size: 'xs' })).toEqual(expected); | ||
| expect(getMediaChipSpacingProps({ start: true, size: 's' })).toEqual(expected); | ||
| }); | ||
|
|
||
| it('start + end is unaffected by size', () => { | ||
| const expected = { paddingStart: 1, paddingY: 1, paddingEnd: 1.5, gap: 0.75 }; | ||
| expect(getMediaChipSpacingProps({ start: true, end: true, size: 'xs' })).toEqual(expected); | ||
| expect(getMediaChipSpacingProps({ start: true, end: true, size: 's' })).toEqual(expected); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
222 changes: 222 additions & 0 deletions
222
packages/mobile/src/__stories__/SizeComposition.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| import { useMemo, useState } from 'react'; | ||
| import { useWindowDimensions } from 'react-native'; | ||
| import type { DateInputValidationError } from '@coinbase/cds-common/dates/DateInputValidationError'; | ||
| import { sampleTabs } from '@coinbase/cds-common/internal/data/tabs'; | ||
| import type { TabValue } from '@coinbase/cds-common/tabs/useTabs'; | ||
| import { gutter } from '@coinbase/cds-common/tokens/sizing'; | ||
|
|
||
| import { Combobox } from '../alpha/combobox/Combobox'; | ||
| import { Select } from '../alpha/select/Select'; | ||
| import { SelectChip } from '../alpha/select-chip/SelectChip'; | ||
| import { TabbedChips } from '../alpha/tabbed-chips/TabbedChips'; | ||
| import { Button } from '../buttons/Button'; | ||
| import { IconButton } from '../buttons/IconButton'; | ||
| import type { ChipSize } from '../chips/ChipProps'; | ||
| import { InputChip } from '../chips/InputChip'; | ||
| import { SearchInput } from '../controls/SearchInput'; | ||
| import { TextInput, type TextInputSize } from '../controls/TextInput'; | ||
| import { DatePicker } from '../dates/DatePicker'; | ||
| import { Example, ExampleScreen } from '../examples/ExampleScreen'; | ||
| import { useTheme } from '../hooks/useTheme'; | ||
| import { HStack } from '../layout/HStack'; | ||
| import { VStack } from '../layout/VStack'; | ||
|
|
||
| const dateSharedProps = { | ||
| invalidDateError: 'Please enter a valid date', | ||
| disabledDateError: 'Date unavailable', | ||
| requiredError: 'This field is required', | ||
| }; | ||
|
|
||
| const selectOptions = [ | ||
| { value: '1', label: 'Apple' }, | ||
| { value: '2', label: 'Banana' }, | ||
| { value: '3', label: 'Cherry' }, | ||
| ]; | ||
|
|
||
| const chipSelectOptions = [ | ||
| { value: 'usd', label: 'USD' }, | ||
| { value: 'eur', label: 'EUR' }, | ||
| { value: 'btc', label: 'BTC' }, | ||
| ]; | ||
|
|
||
| const tabbedChipTabs = sampleTabs.slice(0, 4); | ||
|
|
||
| /** Spacing token used for the gap between every composed control. */ | ||
| const compositionGap = 1; | ||
|
|
||
| /** | ||
| * Minimum controls per wrapped row, so each row always pairs at least two | ||
| * controls and their natural heights can be compared against each other. | ||
| */ | ||
| const controlsPerRow = 2; | ||
|
|
||
| /** | ||
| * Derives control widths from the live screen width minus the horizontal | ||
| * padding `ExampleScreen` applies, so fixed-width controls always wrap into | ||
| * rows of `controlsPerRow` instead of overflowing narrow devices. | ||
| */ | ||
| const useCompositionWidths = () => { | ||
| const { width: screenWidth } = useWindowDimensions(); | ||
| const theme = useTheme(); | ||
|
|
||
| return useMemo(() => { | ||
| const contentWidth = screenWidth - theme.space[gutter] * 2; | ||
| const totalGap = theme.space[compositionGap] * (controlsPerRow - 1); | ||
|
|
||
| return { | ||
| contentWidth, | ||
| controlWidth: Math.floor((contentWidth - totalGap) / controlsPerRow), | ||
| }; | ||
| }, [screenWidth, theme]); | ||
| }; | ||
|
|
||
| const InputRow = ({ size }: { size?: TextInputSize }) => { | ||
| const { controlWidth } = useCompositionWidths(); | ||
| const [selectValue, setSelectValue] = useState<string | null>('1'); | ||
| const [comboboxValue, setComboboxValue] = useState<string | null>('1'); | ||
| const [text, setText] = useState(''); | ||
| const [search, setSearch] = useState(''); | ||
| const [pickerDate, setPickerDate] = useState<Date | null>(null); | ||
| const [pickerError, setPickerError] = useState<DateInputValidationError | null>(null); | ||
|
|
||
| const selectStyle = useMemo(() => ({ width: controlWidth }), [controlWidth]); | ||
|
|
||
| return ( | ||
| <HStack alignItems="flex-end" flexWrap="wrap" gap={compositionGap}> | ||
| <Select | ||
| label="Select" | ||
| onChange={setSelectValue} | ||
| options={selectOptions} | ||
| placeholder="Choose" | ||
| size={size} | ||
| style={selectStyle} | ||
| value={selectValue} | ||
| /> | ||
| <Combobox | ||
| label="Combobox" | ||
| onChange={setComboboxValue} | ||
| options={selectOptions} | ||
| placeholder="Search" | ||
| size={size} | ||
| style={selectStyle} | ||
| type="single" | ||
| value={comboboxValue} | ||
| /> | ||
| <TextInput | ||
| label="Text" | ||
| onChangeText={setText} | ||
| placeholder="Name" | ||
| size={size} | ||
| value={text} | ||
| width={controlWidth} | ||
| /> | ||
| <DatePicker | ||
| {...dateSharedProps} | ||
| date={pickerDate} | ||
| error={pickerError} | ||
| helperText="" | ||
| label="Date" | ||
| onChangeDate={setPickerDate} | ||
| onErrorDate={setPickerError} | ||
| openCalendarAccessibilityLabel="Open date picker calendar" | ||
| size={size} | ||
| width={controlWidth} | ||
| /> | ||
| <SearchInput | ||
| accessibilityLabel="Search" | ||
| onChangeText={setSearch} | ||
| onClear={() => setSearch('')} | ||
| placeholder="Search" | ||
| size={size} | ||
| value={search} | ||
| width={controlWidth} | ||
| /> | ||
| </HStack> | ||
| ); | ||
| }; | ||
|
|
||
| const ChipMixRow = ({ chipSize, controlSize }: { chipSize: ChipSize; controlSize: 'xs' | 's' }) => { | ||
| const { contentWidth, controlWidth } = useCompositionWidths(); | ||
| const [activeTab, setActiveTab] = useState<TabValue | null>(tabbedChipTabs[0]); | ||
| const [chipSelectValue, setChipSelectValue] = useState<string | null>('usd'); | ||
| const [text, setText] = useState(''); | ||
| const [search, setSearch] = useState(''); | ||
| const includeTextInputs = chipSize === 's'; | ||
|
|
||
| return ( | ||
| <VStack gap={compositionGap}> | ||
| {/* | ||
| * TabbedChips wraps a horizontal ScrollView, which has no intrinsic height | ||
| * as a flex-row child, so it gets its own full-width row rather than | ||
| * sitting inside the wrapping HStack below. | ||
| */} | ||
| <TabbedChips | ||
| activeTab={activeTab} | ||
| onChange={setActiveTab} | ||
| size={chipSize} | ||
| tabs={tabbedChipTabs} | ||
| width={contentWidth} | ||
| /> | ||
| <HStack alignItems="center" flexWrap="wrap" gap={compositionGap}> | ||
| <InputChip onPress={() => {}} size={chipSize}> | ||
| USD | ||
| </InputChip> | ||
| <SelectChip | ||
| onChange={setChipSelectValue} | ||
| options={chipSelectOptions} | ||
| placeholder="Asset" | ||
| size={chipSize} | ||
| value={chipSelectValue} | ||
| /> | ||
| <Button size={controlSize}>Action</Button> | ||
| <IconButton accessibilityLabel="Settings" name="gear" size={controlSize} /> | ||
| {includeTextInputs && ( | ||
| <> | ||
| <TextInput | ||
| accessibilityLabel="Filter" | ||
| onChangeText={setText} | ||
| placeholder="Filter" | ||
| size="s" | ||
| value={text} | ||
| width={controlWidth} | ||
| /> | ||
| <SearchInput | ||
| accessibilityLabel="Search" | ||
| onChangeText={setSearch} | ||
| onClear={() => setSearch('')} | ||
| placeholder="Search" | ||
| size="s" | ||
| value={search} | ||
| width={controlWidth} | ||
| /> | ||
| </> | ||
| )} | ||
| </HStack> | ||
| </VStack> | ||
| ); | ||
| }; | ||
|
|
||
| /** | ||
| * Side-by-side composition of sized controls to verify natural height alignment in a row. | ||
| */ | ||
| const SizeCompositionScreen = () => ( | ||
| <ExampleScreen> | ||
| <Example title='Inputs — size="s"'> | ||
| <InputRow size="s" /> | ||
| </Example> | ||
| <Example title='Inputs — size="m"'> | ||
| <InputRow size="m" /> | ||
| </Example> | ||
| <Example title="Inputs — default (size l)"> | ||
| <InputRow /> | ||
| </Example> | ||
| <Example title='Chips + small controls — size="s"'> | ||
| <ChipMixRow chipSize="s" controlSize="s" /> | ||
| </Example> | ||
| <Example title='Chips + small controls — size="xs"'> | ||
| <ChipMixRow chipSize="xs" controlSize="xs" /> | ||
| </Example> | ||
| </ExampleScreen> | ||
| ); | ||
|
|
||
| export default SizeCompositionScreen; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.