Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork

Enforce that text is wrapped in <Text>, remaining cases (#3421)

* Toggle.Button -> Toggle.ButtonWithText

* Simplify Prompt.Cancel/Action

* Move lines down for better diff

* Remove ButtonWithText

* Simplify types

* Enforce Button/ButtonText nesting

* Add suggested wrapper in linter error

* Check <Trans> ancestry too

* Also check literals

* Rm ts-ignore

authored by danabra.mov and committed by GitHub 46c112ed 49266c35

Changed files
+587 -73
eslint
src
components
screens
view
com
auth
server-input
screens
+5 -10
.eslintrc.js
··· 23 23 'bsky-internal/avoid-unwrapped-text': [ 24 24 'error', 25 25 { 26 - impliedTextComponents: [ 27 - 'Button', // TODO: Not always safe. 28 - 'H1', 29 - 'H2', 30 - 'H3', 31 - 'H4', 32 - 'H5', 33 - 'H6', 34 - 'P', 35 - ], 26 + impliedTextComponents: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P'], 36 27 impliedTextProps: [], 28 + suggestedTextWrappers: { 29 + Button: 'ButtonText', 30 + 'ToggleButton.Button': 'ToggleButton.ButtonText', 31 + }, 37 32 }, 38 33 ], 39 34 'simple-import-sort/imports': [
+338 -1
eslint/__tests__/avoid-unwrapped-text.test.js
··· 199 199 200 200 { 201 201 code: ` 202 - <View prop={ 202 + <View propText={ 203 203 <Trans><Text>foo</Text></Trans> 204 204 }> 205 205 <Bar /> ··· 281 281 } 282 282 `, 283 283 }, 284 + 285 + { 286 + code: ` 287 + <View> 288 + <Text>{'foo'}</Text> 289 + </View> 290 + `, 291 + }, 292 + 293 + { 294 + code: ` 295 + <View> 296 + <Text>{foo + 'foo'}</Text> 297 + </View> 298 + `, 299 + }, 300 + 301 + { 302 + code: ` 303 + <View> 304 + <Text><Trans>{'foo'}</Trans></Text> 305 + </View> 306 + `, 307 + }, 308 + 309 + { 310 + code: ` 311 + <View> 312 + {foo['bar'] && <Bar />} 313 + </View> 314 + `, 315 + }, 316 + 317 + { 318 + code: ` 319 + <View> 320 + {(foo === 'bar') && <Bar />} 321 + </View> 322 + `, 323 + }, 324 + 325 + { 326 + code: ` 327 + <View> 328 + {(foo !== 'bar') && <Bar />} 329 + </View> 330 + `, 331 + }, 332 + 333 + { 334 + code: ` 335 + <View> 336 + <Text>{\`foo\`}</Text> 337 + </View> 338 + `, 339 + }, 340 + 341 + { 342 + code: ` 343 + <View> 344 + <Text><Trans>{\`foo\`}</Trans></Text> 345 + </View> 346 + `, 347 + }, 348 + 349 + { 350 + code: ` 351 + <View> 352 + <Text>{_(msg\`foo\`)}</Text> 353 + </View> 354 + `, 355 + }, 356 + 357 + { 358 + code: ` 359 + <View> 360 + <Text><Trans>{_(msg\`foo\`)}</Trans></Text> 361 + </View> 362 + `, 363 + }, 364 + 365 + { 366 + code: ` 367 + <Foo> 368 + <View prop={stuff('foo')}> 369 + <Bar /> 370 + </View> 371 + </Foo> 372 + `, 373 + }, 374 + 375 + { 376 + code: ` 377 + <Foo> 378 + <View onClick={() => stuff('foo')}> 379 + <Bar /> 380 + </View> 381 + </Foo> 382 + `, 383 + }, 384 + 385 + { 386 + code: ` 387 + <View> 388 + {renderItem('foo')} 389 + </View> 390 + `, 391 + }, 392 + 393 + { 394 + code: ` 395 + <View> 396 + {foo === 'foo' && <Bar />} 397 + </View> 398 + `, 399 + }, 400 + 401 + { 402 + code: ` 403 + <View> 404 + {foo['foo'] && <Bar />} 405 + </View> 406 + `, 407 + }, 408 + 409 + { 410 + code: ` 411 + <View> 412 + {check('foo') && <Bar />} 413 + </View> 414 + `, 415 + }, 416 + 417 + { 418 + code: ` 419 + <View> 420 + {foo.bar && <Bar />} 421 + </View> 422 + `, 423 + }, 424 + 425 + { 426 + code: ` 427 + <Text> 428 + <Trans>{renderItem('foo')}</Trans> 429 + </Text> 430 + `, 431 + }, 432 + 433 + { 434 + code: ` 435 + <View> 436 + {null} 437 + </View> 438 + `, 439 + }, 440 + 441 + { 442 + code: ` 443 + <Text> 444 + <Trans>{null}</Trans> 445 + </Text> 446 + `, 447 + }, 284 448 ], 285 449 286 450 invalid: [ ··· 452 616 } 453 617 return <Text>foo</Text> 454 618 } 619 + `, 620 + errors: 1, 621 + }, 622 + 623 + { 624 + code: ` 625 + <View> 626 + {'foo'} 627 + </View> 628 + `, 629 + errors: 1, 630 + }, 631 + 632 + { 633 + code: ` 634 + <View> 635 + {foo && 'foo'} 636 + </View> 637 + `, 638 + errors: 1, 639 + }, 640 + 641 + { 642 + code: ` 643 + <View> 644 + <Trans>{'foo'}</Trans> 645 + </View> 646 + `, 647 + errors: 1, 648 + }, 649 + 650 + { 651 + code: ` 652 + <View> 653 + {foo && <Trans>{'foo'}</Trans>} 654 + </View> 655 + `, 656 + errors: 1, 657 + }, 658 + 659 + { 660 + code: ` 661 + <View> 662 + {10} 663 + </View> 664 + `, 665 + errors: 1, 666 + }, 667 + 668 + { 669 + code: ` 670 + <View> 671 + <Trans>{10}</Trans> 672 + </View> 673 + `, 674 + errors: 1, 675 + }, 676 + 677 + { 678 + code: ` 679 + <View> 680 + <Trans>{foo + 10}</Trans> 681 + </View> 682 + `, 683 + errors: 1, 684 + }, 685 + 686 + { 687 + code: ` 688 + <View> 689 + {\`foo\`} 690 + </View> 691 + `, 692 + errors: 1, 693 + }, 694 + 695 + { 696 + code: ` 697 + <View> 698 + <Trans>{\`foo\`}</Trans> 699 + </View> 700 + `, 701 + errors: 1, 702 + }, 703 + 704 + { 705 + code: ` 706 + <View> 707 + <Trans>{foo + \`foo\`}</Trans> 708 + </View> 709 + `, 710 + errors: 1, 711 + }, 712 + 713 + { 714 + code: ` 715 + <View> 716 + {_(msg\`foo\`)} 717 + </View> 718 + `, 719 + errors: 1, 720 + }, 721 + 722 + { 723 + code: ` 724 + <View> 725 + {foo + _(msg\`foo\`)} 726 + </View> 727 + `, 728 + errors: 1, 729 + }, 730 + 731 + { 732 + code: ` 733 + <View> 734 + <Trans>{_(msg\`foo\`)}</Trans> 735 + </View> 736 + `, 737 + errors: 1, 738 + }, 739 + 740 + { 741 + code: ` 742 + <View> 743 + <Trans>{foo + _(msg\`foo\`)}</Trans> 744 + </View> 745 + `, 746 + errors: 1, 747 + }, 748 + 749 + { 750 + code: ` 751 + <View> 752 + <Trans>foo</Trans> 753 + </View> 754 + `, 755 + errors: 1, 756 + }, 757 + 758 + { 759 + code: ` 760 + <View> 761 + <Trans><Trans>foo</Trans></Trans> 762 + </View> 763 + `, 764 + errors: 1, 765 + }, 766 + 767 + { 768 + code: ` 769 + <View> 770 + <Trans>{foo}</Trans> 771 + </View> 772 + `, 773 + errors: 1, 774 + }, 775 + 776 + { 777 + code: ` 778 + <View> 779 + <Trans>{'foo'}</Trans> 780 + </View> 781 + `, 782 + errors: 1, 783 + }, 784 + 785 + { 786 + code: ` 787 + <View prop={ 788 + <Trans><Text>foo</Text></Trans> 789 + }> 790 + <Bar /> 791 + </View> 455 792 `, 456 793 errors: 1, 457 794 },
+189 -5
eslint/avoid-unwrapped-text.js
··· 33 33 const options = context.options[0] || {} 34 34 const impliedTextProps = options.impliedTextProps ?? [] 35 35 const impliedTextComponents = options.impliedTextComponents ?? [] 36 + const suggestedTextWrappers = options.suggestedTextWrappers ?? {} 36 37 const textProps = [...impliedTextProps] 37 38 const textComponents = ['Text', ...impliedTextComponents] 38 39 ··· 54 55 return 55 56 } 56 57 if (tagName === 'Trans') { 57 - // Skip over it and check above. 58 + // Exit and rely on the traversal for <Trans> JSXElement (code below). 58 59 // TODO: Maybe validate that it's present. 59 - parent = parent.parent 60 - continue 60 + return 61 61 } 62 - let message = 'Wrap this string in <Text>.' 63 - if (tagName !== 'View') { 62 + const suggestedWrapper = suggestedTextWrappers[tagName] 63 + let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 64 + if (tagName !== 'View' && !suggestedWrapper) { 64 65 message += 65 66 ' If <' + 66 67 tagName + ··· 94 95 } 95 96 const message = 96 97 'Wrap this string in <Text>.' + 98 + ' If `' + 99 + propName + 100 + '` is guaranteed to be wrapped in <Text>, ' + 101 + 'rename it to `' + 102 + propName + 103 + 'Text' + 104 + '` or add it to impliedTextProps.' 105 + context.report({ 106 + node, 107 + message, 108 + }) 109 + return 110 + } 111 + 112 + parent = parent.parent 113 + continue 114 + } 115 + }, 116 + Literal(node) { 117 + if (typeof node.value !== 'string' && typeof node.value !== 'number') { 118 + return 119 + } 120 + let parent = node.parent 121 + while (parent) { 122 + if (parent.type === 'JSXElement') { 123 + const tagName = getTagName(parent) 124 + if (isTextComponent(tagName)) { 125 + // We're good. 126 + return 127 + } 128 + if (tagName === 'Trans') { 129 + // Exit and rely on the traversal for <Trans> JSXElement (code below). 130 + // TODO: Maybe validate that it's present. 131 + return 132 + } 133 + const suggestedWrapper = suggestedTextWrappers[tagName] 134 + let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 135 + if (tagName !== 'View' && !suggestedWrapper) { 136 + message += 137 + ' If <' + 138 + tagName + 139 + '> is guaranteed to render <Text>, ' + 140 + 'rename it to <' + 141 + tagName + 142 + 'Text> or add it to impliedTextComponents.' 143 + } 144 + context.report({ 145 + node, 146 + message, 147 + }) 148 + return 149 + } 150 + 151 + if (parent.type === 'BinaryExpression' && parent.operator === '+') { 152 + parent = parent.parent 153 + continue 154 + } 155 + 156 + if ( 157 + parent.type === 'JSXExpressionContainer' || 158 + parent.type === 'LogicalExpression' 159 + ) { 160 + parent = parent.parent 161 + continue 162 + } 163 + 164 + // Be conservative for other types. 165 + return 166 + } 167 + }, 168 + TemplateLiteral(node) { 169 + let parent = node.parent 170 + while (parent) { 171 + if (parent.type === 'JSXElement') { 172 + const tagName = getTagName(parent) 173 + if (isTextComponent(tagName)) { 174 + // We're good. 175 + return 176 + } 177 + if (tagName === 'Trans') { 178 + // Exit and rely on the traversal for <Trans> JSXElement (code below). 179 + // TODO: Maybe validate that it's present. 180 + return 181 + } 182 + const suggestedWrapper = suggestedTextWrappers[tagName] 183 + let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.` 184 + if (tagName !== 'View' && !suggestedWrapper) { 185 + message += 186 + ' If <' + 187 + tagName + 188 + '> is guaranteed to render <Text>, ' + 189 + 'rename it to <' + 190 + tagName + 191 + 'Text> or add it to impliedTextComponents.' 192 + } 193 + context.report({ 194 + node, 195 + message, 196 + }) 197 + return 198 + } 199 + 200 + if ( 201 + parent.type === 'CallExpression' && 202 + parent.callee.type === 'Identifier' && 203 + parent.callee.name === '_' 204 + ) { 205 + // This is a user-facing string, keep going up. 206 + parent = parent.parent 207 + continue 208 + } 209 + 210 + if (parent.type === 'BinaryExpression' && parent.operator === '+') { 211 + parent = parent.parent 212 + continue 213 + } 214 + 215 + if ( 216 + parent.type === 'JSXExpressionContainer' || 217 + parent.type === 'LogicalExpression' || 218 + parent.type === 'TaggedTemplateExpression' 219 + ) { 220 + parent = parent.parent 221 + continue 222 + } 223 + 224 + // Be conservative for other types. 225 + return 226 + } 227 + }, 228 + JSXElement(node) { 229 + if (getTagName(node) !== 'Trans') { 230 + return 231 + } 232 + let parent = node.parent 233 + while (parent) { 234 + if (parent.type === 'JSXElement') { 235 + const tagName = getTagName(parent) 236 + if (isTextComponent(tagName)) { 237 + // We're good. 238 + return 239 + } 240 + if (tagName === 'Trans') { 241 + // Exit and rely on the traversal for this JSXElement. 242 + // TODO: Should nested <Trans> even be allowed? 243 + return 244 + } 245 + const suggestedWrapper = suggestedTextWrappers[tagName] 246 + let message = `Wrap this <Trans> in <${suggestedWrapper ?? 'Text'}>.` 247 + if (tagName !== 'View' && !suggestedWrapper) { 248 + message += 249 + ' If <' + 250 + tagName + 251 + '> is guaranteed to render <Text>, ' + 252 + 'rename it to <' + 253 + tagName + 254 + 'Text> or add it to impliedTextComponents.' 255 + } 256 + context.report({ 257 + node, 258 + message, 259 + }) 260 + return 261 + } 262 + 263 + if ( 264 + parent.type === 'JSXAttribute' && 265 + parent.name.type === 'JSXIdentifier' && 266 + parent.parent.type === 'JSXOpeningElement' && 267 + parent.parent.parent.type === 'JSXElement' 268 + ) { 269 + const tagName = getTagName(parent.parent.parent) 270 + const propName = parent.name.name 271 + if ( 272 + textProps.includes(tagName + ' ' + propName) || 273 + propName === 'text' || 274 + propName.endsWith('Text') 275 + ) { 276 + // We're good. 277 + return 278 + } 279 + const message = 280 + 'Wrap this <Trans> in <Text>.' + 97 281 ' If `' + 98 282 propName + 99 283 '` is guaranteed to be wrapped in <Text>, ' +
+7 -14
src/components/Button.tsx
··· 12 12 ViewStyle, 13 13 } from 'react-native' 14 14 import {LinearGradient} from 'expo-linear-gradient' 15 - import {Trans} from '@lingui/macro' 16 15 17 16 import {android, atoms as a, flatten, tokens, useTheme} from '#/alf' 18 17 import {Props as SVGIconProps} from '#/components/icons/common' ··· 59 58 60 59 export type ButtonContext = VariantProps & ButtonState 61 60 61 + type NonTextElements = 62 + | React.ReactElement 63 + | Iterable<React.ReactElement | null | undefined | boolean> 64 + 62 65 export type ButtonProps = Pick< 63 66 PressableProps, 64 67 'disabled' | 'onPress' | 'testID' ··· 68 71 testID?: string 69 72 label: string 70 73 style?: StyleProp<ViewStyle> 71 - children: 72 - | React.ReactNode 73 - | string 74 - | ((context: ButtonContext) => React.ReactNode | string) 74 + children: NonTextElements | ((context: ButtonContext) => NonTextElements) 75 75 } 76 + 76 77 export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean} 77 78 78 79 const Context = React.createContext<VariantProps & ButtonState>({ ··· 404 405 </View> 405 406 )} 406 407 <Context.Provider value={context}> 407 - {/* @ts-ignore */} 408 - {typeof children === 'string' || children?.type === Trans ? ( 409 - /* @ts-ignore */ 410 - <ButtonText>{children}</ButtonText> 411 - ) : typeof children === 'function' ? ( 412 - children(context) 413 - ) : ( 414 - children 415 - )} 408 + {typeof children === 'function' ? children(context) : children} 416 409 </Context.Provider> 417 410 </Pressable> 418 411 )
+4 -2
src/components/Lists.tsx
··· 6 6 import {cleanError} from 'lib/strings/errors' 7 7 import {CenteredView} from 'view/com/util/Views' 8 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 - import {Button} from '#/components/Button' 9 + import {Button, ButtonText} from '#/components/Button' 10 10 import {Error} from '#/components/Error' 11 11 import {Loader} from '#/components/Loader' 12 12 import {Text} from '#/components/Typography' ··· 87 87 a.py_sm, 88 88 ]} 89 89 onPress={onRetry}> 90 - <Trans>Retry</Trans> 90 + <ButtonText> 91 + <Trans>Retry</Trans> 92 + </ButtonText> 91 93 </Button> 92 94 </View> 93 95 </View>
+2 -2
src/components/moderation/LabelsOnMeDialog.tsx
··· 244 244 size="medium" 245 245 onPress={onPressBack} 246 246 label={_(msg`Back`)}> 247 - {_(msg`Back`)} 247 + <ButtonText>{_(msg`Back`)}</ButtonText> 248 248 </Button> 249 249 <Button 250 250 testID="submitBtn" ··· 253 253 size="medium" 254 254 onPress={onSubmit} 255 255 label={_(msg`Submit`)}> 256 - {_(msg`Submit`)} 256 + <ButtonText>{_(msg`Submit`)}</ButtonText> 257 257 </Button> 258 258 </View> 259 259 </>
+2 -2
src/screens/Login/ChooseAccountForm.tsx
··· 10 10 import * as Toast from '#/view/com/util/Toast' 11 11 import {atoms as a} from '#/alf' 12 12 import {AccountList} from '#/components/AccountList' 13 - import {Button} from '#/components/Button' 13 + import {Button, ButtonText} from '#/components/Button' 14 14 import * as TextField from '#/components/forms/TextField' 15 15 import {FormContainer} from './FormContainer' 16 16 ··· 75 75 color="secondary" 76 76 size="medium" 77 77 onPress={onPressBack}> 78 - {_(msg`Back`)} 78 + <ButtonText>{_(msg`Back`)}</ButtonText> 79 79 </Button> 80 80 <View style={[a.flex_1]} /> 81 81 </View>
+3 -1
src/screens/Login/LoginForm.tsx
··· 237 237 color="secondary" 238 238 size="medium" 239 239 onPress={onPressRetryConnect}> 240 - {_(msg`Retry`)} 240 + <ButtonText> 241 + <Trans>Retry</Trans> 242 + </ButtonText> 241 243 </Button> 242 244 ) : !serviceDescription ? ( 243 245 <>
+2 -2
src/screens/Onboarding/Layout.tsx
··· 17 17 useTheme, 18 18 web, 19 19 } from '#/alf' 20 - import {Button, ButtonIcon} from '#/components/Button' 20 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 21 21 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' 22 22 import {createPortalGroup} from '#/components/Portal' 23 23 import {leading, P, Text} from '#/components/Typography' ··· 73 73 onPress={() => onboardDispatch({type: 'skip'})} 74 74 // DEV ONLY 75 75 label="Clear onboarding state"> 76 - Clear 76 + <ButtonText>Clear</ButtonText> 77 77 </Button> 78 78 </View> 79 79 )}
+1 -1
src/view/com/auth/server-input/index.tsx
··· 167 167 size="small" 168 168 onPress={() => control.close()} 169 169 label={_(msg`Done`)}> 170 - {_(msg`Done`)} 170 + <ButtonText>{_(msg`Done`)}</ButtonText> 171 171 </Button> 172 172 </View> 173 173 </View>
+3 -1
src/view/screens/Settings/ExportCarDialog.tsx
··· 92 92 size={gtMobile ? 'small' : 'large'} 93 93 onPress={() => control.close()} 94 94 label={_(msg`Done`)}> 95 - {_(msg`Done`)} 95 + <ButtonText> 96 + <Trans>Done</Trans> 97 + </ButtonText> 96 98 </Button> 97 99 </View> 98 100
+8 -8
src/view/screens/Storybook/Buttons.tsx
··· 4 4 import {atoms as a} from '#/alf' 5 5 import { 6 6 Button, 7 - ButtonVariant, 8 7 ButtonColor, 9 8 ButtonIcon, 10 9 ButtonText, 10 + ButtonVariant, 11 11 } from '#/components/Button' 12 - import {H1} from '#/components/Typography' 13 12 import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight' 14 13 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' 15 14 import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' 15 + import {H1} from '#/components/Typography' 16 16 17 17 export function Buttons() { 18 18 return ( ··· 29 29 color={color as ButtonColor} 30 30 size="large" 31 31 label="Click here"> 32 - Button 32 + <ButtonText>Button</ButtonText> 33 33 </Button> 34 34 <Button 35 35 disabled ··· 37 37 color={color as ButtonColor} 38 38 size="large" 39 39 label="Click here"> 40 - Button 40 + <ButtonText>Button</ButtonText> 41 41 </Button> 42 42 </React.Fragment> 43 43 ))} ··· 54 54 color={name as ButtonColor} 55 55 size="large" 56 56 label="Click here"> 57 - Button 57 + <ButtonText>Button</ButtonText> 58 58 </Button> 59 59 <Button 60 60 disabled ··· 62 62 color={name as ButtonColor} 63 63 size="large" 64 64 label="Click here"> 65 - Button 65 + <ButtonText>Button</ButtonText> 66 66 </Button> 67 67 </React.Fragment> 68 68 ), ··· 77 77 color={name as ButtonColor} 78 78 size="large" 79 79 label="Click here"> 80 - Button 80 + <ButtonText>Button</ButtonText> 81 81 </Button> 82 82 <Button 83 83 disabled ··· 85 85 color={name as ButtonColor} 86 86 size="large" 87 87 label="Click here"> 88 - Button 88 + <ButtonText>Button</ButtonText> 89 89 </Button> 90 90 </React.Fragment> 91 91 ),
+7 -7
src/view/screens/Storybook/Dialogs.tsx
··· 3 3 4 4 import {useDialogStateControlContext} from '#/state/dialogs' 5 5 import {atoms as a} from '#/alf' 6 - import {Button} from '#/components/Button' 6 + import {Button, ButtonText} from '#/components/Button' 7 7 import * as Dialog from '#/components/Dialog' 8 8 import * as Prompt from '#/components/Prompt' 9 9 import {H3, P} from '#/components/Typography' ··· 26 26 basic.open() 27 27 }} 28 28 label="Open basic dialog"> 29 - Open all dialogs 29 + <ButtonText>Open all dialogs</ButtonText> 30 30 </Button> 31 31 32 32 <Button ··· 37 37 scrollable.open() 38 38 }} 39 39 label="Open basic dialog"> 40 - Open scrollable dialog 40 + <ButtonText>Open scrollable dialog</ButtonText> 41 41 </Button> 42 42 43 43 <Button ··· 48 48 basic.open() 49 49 }} 50 50 label="Open basic dialog"> 51 - Open basic dialog 51 + <ButtonText>Open basic dialog</ButtonText> 52 52 </Button> 53 53 54 54 <Button ··· 57 57 size="small" 58 58 onPress={() => prompt.open()} 59 59 label="Open prompt"> 60 - Open prompt 60 + <ButtonText>Open prompt</ButtonText> 61 61 </Button> 62 62 63 63 <Prompt.Outer control={prompt}> ··· 102 102 size="small" 103 103 onPress={closeAllDialogs} 104 104 label="Close all dialogs"> 105 - Close all dialogs 105 + <ButtonText>Close all dialogs</ButtonText> 106 106 </Button> 107 107 <View style={{height: 1000}} /> 108 108 <View style={[a.flex_row, a.justify_end]}> ··· 116 116 }) 117 117 } 118 118 label="Open basic dialog"> 119 - Close dialog 119 + <ButtonText>Close dialog</ButtonText> 120 120 </Button> 121 121 </View> 122 122 </View>
+2 -2
src/view/screens/Storybook/Forms.tsx
··· 2 2 import {View} from 'react-native' 3 3 4 4 import {atoms as a} from '#/alf' 5 - import {Button} from '#/components/Button' 5 + import {Button, ButtonText} from '#/components/Button' 6 6 import {DateField, LabelText} from '#/components/forms/DateField' 7 7 import * as TextField from '#/components/forms/TextField' 8 8 import * as Toggle from '#/components/forms/Toggle' ··· 191 191 setToggleGroupBValues(['a', 'b']) 192 192 setToggleGroupCValues(['a']) 193 193 }}> 194 - Reset all toggles 194 + <ButtonText>Reset all toggles</ButtonText> 195 195 </Button> 196 196 197 197 <View style={[a.gap_md, a.align_start, a.w_full]}>
+14 -15
src/view/screens/Storybook/index.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {CenteredView, ScrollView} from '#/view/com/util/Views' 4 3 5 - import {atoms as a, useTheme, ThemeProvider} from '#/alf' 6 4 import {useSetThemePrefs} from '#/state/shell' 7 - import {Button} from '#/components/Button' 8 - 9 - import {Theming} from './Theming' 10 - import {Typography} from './Typography' 11 - import {Spacing} from './Spacing' 5 + import {CenteredView, ScrollView} from '#/view/com/util/Views' 6 + import {atoms as a, ThemeProvider, useTheme} from '#/alf' 7 + import {Button, ButtonText} from '#/components/Button' 8 + import {Breakpoints} from './Breakpoints' 12 9 import {Buttons} from './Buttons' 13 - import {Links} from './Links' 10 + import {Dialogs} from './Dialogs' 14 11 import {Forms} from './Forms' 15 - import {Dialogs} from './Dialogs' 16 - import {Breakpoints} from './Breakpoints' 17 - import {Shadows} from './Shadows' 18 12 import {Icons} from './Icons' 13 + import {Links} from './Links' 19 14 import {Menus} from './Menus' 15 + import {Shadows} from './Shadows' 16 + import {Spacing} from './Spacing' 17 + import {Theming} from './Theming' 18 + import {Typography} from './Typography' 20 19 21 20 export function Storybook() { 22 21 const t = useTheme() ··· 33 32 size="small" 34 33 label='Set theme to "system"' 35 34 onPress={() => setColorMode('system')}> 36 - System 35 + <ButtonText>System</ButtonText> 37 36 </Button> 38 37 <Button 39 38 variant="solid" ··· 41 40 size="small" 42 41 label='Set theme to "light"' 43 42 onPress={() => setColorMode('light')}> 44 - Light 43 + <ButtonText>Light</ButtonText> 45 44 </Button> 46 45 <Button 47 46 variant="solid" ··· 52 51 setColorMode('dark') 53 52 setDarkTheme('dim') 54 53 }}> 55 - Dim 54 + <ButtonText>Dim</ButtonText> 56 55 </Button> 57 56 <Button 58 57 variant="solid" ··· 63 62 setColorMode('dark') 64 63 setDarkTheme('dark') 65 64 }}> 66 - Dark 65 + <ButtonText>Dark</ButtonText> 67 66 </Button> 68 67 </View> 69 68