+5
-10
.eslintrc.js
+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
+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
+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
+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
+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
+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
+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
+3
-1
src/screens/Login/LoginForm.tsx
+2
-2
src/screens/Onboarding/Layout.tsx
+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
+1
-1
src/view/com/auth/server-input/index.tsx
+3
-1
src/view/screens/Settings/ExportCarDialog.tsx
+3
-1
src/view/screens/Settings/ExportCarDialog.tsx
+8
-8
src/view/screens/Storybook/Buttons.tsx
+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
+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
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
+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