@cdx-ui/components 0.0.1-alpha.26 → 0.0.1-alpha.28
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/lib/commonjs/components/Button/buttonSharedVariants.js +200 -0
- package/lib/commonjs/components/Button/buttonSharedVariants.js.map +1 -0
- package/lib/commonjs/components/Button/styles.js +21 -222
- package/lib/commonjs/components/Button/styles.js.map +1 -1
- package/lib/commonjs/components/IconButton/index.js +80 -0
- package/lib/commonjs/components/IconButton/index.js.map +1 -0
- package/lib/commonjs/components/IconButton/styles.js +91 -0
- package/lib/commonjs/components/IconButton/styles.js.map +1 -0
- package/lib/commonjs/components/Image/Image.js +69 -0
- package/lib/commonjs/components/Image/Image.js.map +1 -0
- package/lib/commonjs/components/Image/index.js +13 -0
- package/lib/commonjs/components/Image/index.js.map +1 -0
- package/lib/commonjs/components/index.js +24 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/module/components/Button/buttonSharedVariants.js +195 -0
- package/lib/module/components/Button/buttonSharedVariants.js.map +1 -0
- package/lib/module/components/Button/styles.js +21 -223
- package/lib/module/components/Button/styles.js.map +1 -1
- package/lib/module/components/IconButton/index.js +76 -0
- package/lib/module/components/IconButton/index.js.map +1 -0
- package/lib/module/components/IconButton/styles.js +87 -0
- package/lib/module/components/IconButton/styles.js.map +1 -0
- package/lib/module/components/Image/Image.js +67 -0
- package/lib/module/components/Image/Image.js.map +1 -0
- package/lib/module/components/Image/index.js +4 -0
- package/lib/module/components/Image/index.js.map +1 -0
- package/lib/module/components/index.js +2 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/typescript/components/Button/buttonSharedVariants.d.ts +29 -0
- package/lib/typescript/components/Button/buttonSharedVariants.d.ts.map +1 -0
- package/lib/typescript/components/Button/styles.d.ts +6 -6
- package/lib/typescript/components/Button/styles.d.ts.map +1 -1
- package/lib/typescript/components/IconButton/index.d.ts +15 -0
- package/lib/typescript/components/IconButton/index.d.ts.map +1 -0
- package/lib/typescript/components/IconButton/styles.d.ts +16 -0
- package/lib/typescript/components/IconButton/styles.d.ts.map +1 -0
- package/lib/typescript/components/Image/Image.d.ts +47 -0
- package/lib/typescript/components/Image/Image.d.ts.map +1 -0
- package/lib/typescript/components/Image/index.d.ts +2 -0
- package/lib/typescript/components/Image/index.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +2 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components/Button/buttonSharedVariants.ts +281 -0
- package/src/components/Button/styles.ts +32 -275
- package/src/components/IconButton/index.tsx +93 -0
- package/src/components/IconButton/styles.ts +131 -0
- package/src/components/Image/Image.tsx +77 -0
- package/src/components/Image/index.ts +1 -0
- package/src/components/index.ts +2 -0
|
@@ -5,9 +5,36 @@ import {
|
|
|
5
5
|
DISABLED_CURSOR,
|
|
6
6
|
DISABLED_OPACITY,
|
|
7
7
|
RADIUS_SM,
|
|
8
|
-
SEMANTIC_COLORS,
|
|
9
8
|
TRANSITION_COLORS,
|
|
10
9
|
} from '../../styles/primitives';
|
|
10
|
+
import {
|
|
11
|
+
sharedFilledForegroundTextCompounds,
|
|
12
|
+
sharedFilledSurfaceCompounds,
|
|
13
|
+
sharedGhostSurfaceCompounds,
|
|
14
|
+
sharedOutlineGhostForegroundTextCompounds,
|
|
15
|
+
} from './buttonSharedVariants';
|
|
16
|
+
|
|
17
|
+
const buttonStrongSurfaceCompounds = sharedFilledSurfaceCompounds.map((c) => ({
|
|
18
|
+
variant: 'strong' as const,
|
|
19
|
+
...c,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const buttonGhostSurfaceCompounds = sharedGhostSurfaceCompounds.map((c) => ({
|
|
23
|
+
variant: 'ghost' as const,
|
|
24
|
+
...c,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const buttonStrongForegroundTextCompounds = sharedFilledForegroundTextCompounds.map((c) => ({
|
|
28
|
+
variant: 'strong' as const,
|
|
29
|
+
...c,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const buttonOutlineGhostForegroundTextCompounds = sharedOutlineGhostForegroundTextCompounds.map(
|
|
33
|
+
(c) => ({
|
|
34
|
+
variant: ['outline', 'ghost'] as ('outline' | 'ghost')[],
|
|
35
|
+
...c,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
11
38
|
|
|
12
39
|
// TODO: Split into 2 files
|
|
13
40
|
// TODO: Create cva wrapper
|
|
@@ -59,76 +86,7 @@ export const buttonRootVariants = cva(
|
|
|
59
86
|
},
|
|
60
87
|
},
|
|
61
88
|
compoundVariants: [
|
|
62
|
-
|
|
63
|
-
{
|
|
64
|
-
variant: 'strong',
|
|
65
|
-
color: 'action',
|
|
66
|
-
className: [
|
|
67
|
-
SEMANTIC_COLORS.action.bg,
|
|
68
|
-
Platform.select({
|
|
69
|
-
default: 'data-[active=true]:bg-slate-700',
|
|
70
|
-
web: 'data-[hover=true]:bg-slate-800 data-[active=true]:data-[hover=true]:bg-slate-700',
|
|
71
|
-
}),
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
variant: 'strong',
|
|
76
|
-
color: 'danger',
|
|
77
|
-
className: [
|
|
78
|
-
SEMANTIC_COLORS.danger.bg,
|
|
79
|
-
Platform.select({
|
|
80
|
-
default: 'data-[active=true]:bg-red-800',
|
|
81
|
-
web: 'data-[hover=true]:bg-red-700 data-[active=true]:data-[hover=true]:bg-red-800',
|
|
82
|
-
}),
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
variant: 'strong',
|
|
87
|
-
color: 'warning',
|
|
88
|
-
className: [
|
|
89
|
-
SEMANTIC_COLORS.warning.bg,
|
|
90
|
-
Platform.select({
|
|
91
|
-
default: 'data-[active=true]:bg-amber-700',
|
|
92
|
-
web: 'data-[hover=true]:bg-amber-600 data-[active=true]:data-[hover=true]:bg-amber-700',
|
|
93
|
-
}),
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
variant: 'strong',
|
|
98
|
-
color: 'success',
|
|
99
|
-
className: [
|
|
100
|
-
SEMANTIC_COLORS.success.bg,
|
|
101
|
-
Platform.select({
|
|
102
|
-
default: 'data-[active=true]:bg-green-800',
|
|
103
|
-
web: 'data-[hover=true]:bg-green-700 data-[active=true]:data-[hover=true]:bg-green-800',
|
|
104
|
-
}),
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
variant: 'strong',
|
|
109
|
-
color: 'info',
|
|
110
|
-
className: [
|
|
111
|
-
SEMANTIC_COLORS.info.bg,
|
|
112
|
-
Platform.select({
|
|
113
|
-
default: 'data-[active=true]:bg-sky-700',
|
|
114
|
-
web: 'data-[hover=true]:bg-sky-600 data-[active=true]:data-[hover=true]:bg-sky-700',
|
|
115
|
-
}),
|
|
116
|
-
],
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
// --- strong × action × mode: dark ---
|
|
120
|
-
{
|
|
121
|
-
variant: 'strong',
|
|
122
|
-
color: 'action',
|
|
123
|
-
mode: 'dark',
|
|
124
|
-
className: [
|
|
125
|
-
'bg-slate-200',
|
|
126
|
-
Platform.select({
|
|
127
|
-
default: 'data-[active=true]:bg-slate-400',
|
|
128
|
-
web: 'data-[hover=true]:bg-slate-300 data-[active=true]:data-[hover=true]:bg-slate-400',
|
|
129
|
-
}),
|
|
130
|
-
],
|
|
131
|
-
},
|
|
89
|
+
...buttonStrongSurfaceCompounds,
|
|
132
90
|
|
|
133
91
|
// --- outline × color (light mode) ---
|
|
134
92
|
{
|
|
@@ -250,105 +208,7 @@ export const buttonRootVariants = cva(
|
|
|
250
208
|
],
|
|
251
209
|
},
|
|
252
210
|
|
|
253
|
-
|
|
254
|
-
{
|
|
255
|
-
variant: 'ghost',
|
|
256
|
-
color: 'danger',
|
|
257
|
-
className: [
|
|
258
|
-
Platform.select({
|
|
259
|
-
default: 'data-[active=true]:bg-red-100',
|
|
260
|
-
web: 'data-[hover=true]:bg-red-50 data-[active=true]:data-[hover=true]:bg-red-100',
|
|
261
|
-
}),
|
|
262
|
-
],
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
variant: 'ghost',
|
|
266
|
-
color: 'warning',
|
|
267
|
-
className: [
|
|
268
|
-
Platform.select({
|
|
269
|
-
default: 'data-[active=true]:bg-amber-100',
|
|
270
|
-
web: 'data-[hover=true]:bg-amber-50 data-[active=true]:data-[hover=true]:bg-amber-100',
|
|
271
|
-
}),
|
|
272
|
-
],
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
variant: 'ghost',
|
|
276
|
-
color: 'success',
|
|
277
|
-
className: [
|
|
278
|
-
Platform.select({
|
|
279
|
-
default: 'data-[active=true]:bg-green-100',
|
|
280
|
-
web: 'data-[hover=true]:bg-green-50 data-[active=true]:data-[hover=true]:bg-green-100',
|
|
281
|
-
}),
|
|
282
|
-
],
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
variant: 'ghost',
|
|
286
|
-
color: 'info',
|
|
287
|
-
className: [
|
|
288
|
-
Platform.select({
|
|
289
|
-
default: 'data-[active=true]:bg-sky-100',
|
|
290
|
-
web: 'data-[hover=true]:bg-sky-50 data-[active=true]:data-[hover=true]:bg-sky-100',
|
|
291
|
-
}),
|
|
292
|
-
],
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
// --- ghost × action × mode: dark ---
|
|
296
|
-
{
|
|
297
|
-
variant: 'ghost',
|
|
298
|
-
color: 'action',
|
|
299
|
-
mode: 'dark',
|
|
300
|
-
className: [
|
|
301
|
-
Platform.select({
|
|
302
|
-
default: 'data-[active=true]:bg-slate-700',
|
|
303
|
-
web: 'data-[hover=true]:bg-slate-800 data-[active=true]:data-[hover=true]:bg-slate-700',
|
|
304
|
-
}),
|
|
305
|
-
],
|
|
306
|
-
},
|
|
307
|
-
// --- ghost × semantic colors × mode: dark ---
|
|
308
|
-
{
|
|
309
|
-
variant: 'ghost',
|
|
310
|
-
color: 'danger',
|
|
311
|
-
mode: 'dark',
|
|
312
|
-
className: [
|
|
313
|
-
Platform.select({
|
|
314
|
-
default: 'data-[active=true]:bg-red-950',
|
|
315
|
-
web: 'data-[hover=true]:bg-red-950/50 data-[active=true]:data-[hover=true]:bg-red-950',
|
|
316
|
-
}),
|
|
317
|
-
],
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
variant: 'ghost',
|
|
321
|
-
color: 'warning',
|
|
322
|
-
mode: 'dark',
|
|
323
|
-
className: [
|
|
324
|
-
Platform.select({
|
|
325
|
-
default: 'data-[active=true]:bg-amber-950',
|
|
326
|
-
web: 'data-[hover=true]:bg-amber-950/50 data-[active=true]:data-[hover=true]:bg-amber-950',
|
|
327
|
-
}),
|
|
328
|
-
],
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
variant: 'ghost',
|
|
332
|
-
color: 'success',
|
|
333
|
-
mode: 'dark',
|
|
334
|
-
className: [
|
|
335
|
-
Platform.select({
|
|
336
|
-
default: 'data-[active=true]:bg-green-950',
|
|
337
|
-
web: 'data-[hover=true]:bg-green-950/50 data-[active=true]:data-[hover=true]:bg-green-950',
|
|
338
|
-
}),
|
|
339
|
-
],
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
variant: 'ghost',
|
|
343
|
-
color: 'info',
|
|
344
|
-
mode: 'dark',
|
|
345
|
-
className: [
|
|
346
|
-
Platform.select({
|
|
347
|
-
default: 'data-[active=true]:bg-sky-950',
|
|
348
|
-
web: 'data-[hover=true]:bg-sky-950/50 data-[active=true]:data-[hover=true]:bg-sky-950',
|
|
349
|
-
}),
|
|
350
|
-
],
|
|
351
|
-
},
|
|
211
|
+
...buttonGhostSurfaceCompounds,
|
|
352
212
|
],
|
|
353
213
|
defaultVariants: {
|
|
354
214
|
variant: 'strong',
|
|
@@ -384,111 +244,8 @@ export const buttonTextVariants = cva(['font-medium', 'text-center'], {
|
|
|
384
244
|
},
|
|
385
245
|
},
|
|
386
246
|
compoundVariants: [
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
{ variant: 'strong', color: 'danger', className: 'text-white' },
|
|
390
|
-
{ variant: 'strong', color: 'warning', className: 'text-white' },
|
|
391
|
-
{ variant: 'strong', color: 'success', className: 'text-white' },
|
|
392
|
-
{ variant: 'strong', color: 'info', className: 'text-white' },
|
|
393
|
-
|
|
394
|
-
// --- strong × action × mode: dark ---
|
|
395
|
-
{ variant: 'strong', color: 'action', mode: 'dark', className: 'text-slate-900' },
|
|
396
|
-
|
|
397
|
-
// --- outline/ghost × color (light mode text) ---
|
|
398
|
-
{
|
|
399
|
-
variant: ['outline', 'ghost'],
|
|
400
|
-
color: 'action',
|
|
401
|
-
className: [
|
|
402
|
-
'text-slate-900',
|
|
403
|
-
Platform.select({
|
|
404
|
-
default: 'data-[active=true]:text-slate-700',
|
|
405
|
-
web: 'data-[hover=true]:text-slate-800 data-[active=true]:data-[hover=true]:text-slate-700',
|
|
406
|
-
}),
|
|
407
|
-
],
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
variant: ['outline', 'ghost'],
|
|
411
|
-
color: 'danger',
|
|
412
|
-
className: [
|
|
413
|
-
'text-red-600',
|
|
414
|
-
Platform.select({
|
|
415
|
-
default: 'data-[active=true]:text-red-800',
|
|
416
|
-
web: 'data-[hover=true]:text-red-700 data-[active=true]:data-[hover=true]:text-red-800',
|
|
417
|
-
}),
|
|
418
|
-
],
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
variant: ['outline', 'ghost'],
|
|
422
|
-
color: 'warning',
|
|
423
|
-
className: [
|
|
424
|
-
'text-amber-600',
|
|
425
|
-
Platform.select({
|
|
426
|
-
default: 'data-[active=true]:text-amber-800',
|
|
427
|
-
web: 'data-[hover=true]:text-amber-700 data-[active=true]:data-[hover=true]:text-amber-800',
|
|
428
|
-
}),
|
|
429
|
-
],
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
variant: ['outline', 'ghost'],
|
|
433
|
-
color: 'success',
|
|
434
|
-
className: [
|
|
435
|
-
'text-green-600',
|
|
436
|
-
Platform.select({
|
|
437
|
-
default: 'data-[active=true]:text-green-800',
|
|
438
|
-
web: 'data-[hover=true]:text-green-700 data-[active=true]:data-[hover=true]:text-green-800',
|
|
439
|
-
}),
|
|
440
|
-
],
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
variant: ['outline', 'ghost'],
|
|
444
|
-
color: 'info',
|
|
445
|
-
className: [
|
|
446
|
-
'text-sky-600',
|
|
447
|
-
Platform.select({
|
|
448
|
-
default: 'data-[active=true]:text-sky-800',
|
|
449
|
-
web: 'data-[hover=true]:text-sky-700 data-[active=true]:data-[hover=true]:text-sky-800',
|
|
450
|
-
}),
|
|
451
|
-
],
|
|
452
|
-
},
|
|
453
|
-
|
|
454
|
-
// --- outline/ghost × action × mode: dark ---
|
|
455
|
-
{
|
|
456
|
-
variant: ['outline', 'ghost'],
|
|
457
|
-
color: 'action',
|
|
458
|
-
mode: 'dark',
|
|
459
|
-
className: [
|
|
460
|
-
'text-white',
|
|
461
|
-
Platform.select({
|
|
462
|
-
default: 'data-[active=true]:text-white',
|
|
463
|
-
web: 'data-[hover=true]:text-white data-[active=true]:data-[hover=true]:text-white',
|
|
464
|
-
}),
|
|
465
|
-
],
|
|
466
|
-
},
|
|
467
|
-
// --- outline/ghost × semantic colors × mode: dark ---
|
|
468
|
-
{
|
|
469
|
-
variant: ['outline', 'ghost'],
|
|
470
|
-
color: 'danger',
|
|
471
|
-
mode: 'dark',
|
|
472
|
-
className: 'text-red-400',
|
|
473
|
-
},
|
|
474
|
-
{
|
|
475
|
-
variant: ['outline', 'ghost'],
|
|
476
|
-
color: 'warning',
|
|
477
|
-
mode: 'dark',
|
|
478
|
-
className: 'text-amber-400',
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
variant: ['outline', 'ghost'],
|
|
482
|
-
color: 'success',
|
|
483
|
-
mode: 'dark',
|
|
484
|
-
className: 'text-green-400',
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
variant: ['outline', 'ghost'],
|
|
488
|
-
color: 'info',
|
|
489
|
-
mode: 'dark',
|
|
490
|
-
className: 'text-sky-400',
|
|
491
|
-
},
|
|
247
|
+
...buttonStrongForegroundTextCompounds,
|
|
248
|
+
...buttonOutlineGhostForegroundTextCompounds,
|
|
492
249
|
],
|
|
493
250
|
defaultVariants: {
|
|
494
251
|
variant: 'strong',
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { ActivityIndicator, Pressable, Text, View, type PressableProps } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
createButton,
|
|
5
|
+
dataAttributes,
|
|
6
|
+
type IButtonProps,
|
|
7
|
+
useButtonContext,
|
|
8
|
+
} from '@cdx-ui/primitives';
|
|
9
|
+
import type { CdxIcon } from '@cdx-ui/icons';
|
|
10
|
+
import { cn } from '@cdx-ui/utils';
|
|
11
|
+
import { Icon, type IconProps } from '../Icon';
|
|
12
|
+
import {
|
|
13
|
+
type IconButtonVariantProps,
|
|
14
|
+
iconButtonGlyphVariants,
|
|
15
|
+
iconButtonIconColorVariants,
|
|
16
|
+
iconButtonRootVariants,
|
|
17
|
+
} from './styles';
|
|
18
|
+
|
|
19
|
+
const IconButtonPrimitive = createButton({
|
|
20
|
+
Root: Pressable,
|
|
21
|
+
Text,
|
|
22
|
+
Group: View,
|
|
23
|
+
Spinner: ActivityIndicator,
|
|
24
|
+
Icon: View,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function IconButtonGlyph({ as: IconComponent, className }: { as: CdxIcon; className: string }) {
|
|
28
|
+
const { hover, focus, active, disabled, focusVisible } = useButtonContext();
|
|
29
|
+
return (
|
|
30
|
+
<Icon
|
|
31
|
+
as={IconComponent}
|
|
32
|
+
className={className}
|
|
33
|
+
{...dataAttributes({
|
|
34
|
+
hover,
|
|
35
|
+
focus,
|
|
36
|
+
active,
|
|
37
|
+
disabled,
|
|
38
|
+
focusVisible,
|
|
39
|
+
})}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface IconButtonProps extends PressableProps, IButtonProps, IconButtonVariantProps {
|
|
45
|
+
className?: string;
|
|
46
|
+
/** CDX icon component to render (icon-only control). */
|
|
47
|
+
as: CdxIcon;
|
|
48
|
+
/** Passed through to the underlying `Icon`. */
|
|
49
|
+
iconClassName?: IconProps['className'];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const IconButtonRoot = forwardRef<View, IconButtonProps>(
|
|
53
|
+
(
|
|
54
|
+
{
|
|
55
|
+
variant = 'solid',
|
|
56
|
+
size = 'default',
|
|
57
|
+
mode = 'light',
|
|
58
|
+
className,
|
|
59
|
+
style,
|
|
60
|
+
as: IconComponent,
|
|
61
|
+
iconClassName,
|
|
62
|
+
accessibilityRole = 'button',
|
|
63
|
+
...props
|
|
64
|
+
},
|
|
65
|
+
ref,
|
|
66
|
+
) => {
|
|
67
|
+
const rootClassName = cn(iconButtonRootVariants({ variant, size, mode }), className);
|
|
68
|
+
|
|
69
|
+
const glyphClassName = cn(
|
|
70
|
+
iconButtonGlyphVariants({ size }),
|
|
71
|
+
iconButtonIconColorVariants({ variant, mode }),
|
|
72
|
+
iconClassName,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<IconButtonPrimitive
|
|
77
|
+
ref={ref}
|
|
78
|
+
accessibilityRole={accessibilityRole}
|
|
79
|
+
className={rootClassName}
|
|
80
|
+
style={style}
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
<IconButtonGlyph as={IconComponent} className={glyphClassName} />
|
|
84
|
+
</IconButtonPrimitive>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
IconButtonRoot.displayName = 'IconButton';
|
|
90
|
+
|
|
91
|
+
export const IconButton = IconButtonRoot;
|
|
92
|
+
|
|
93
|
+
export type { IconButtonVariantProps };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import {
|
|
4
|
+
COLOR_BORDER_DEFAULT,
|
|
5
|
+
DISABLED_CURSOR,
|
|
6
|
+
DISABLED_OPACITY,
|
|
7
|
+
RADIUS_FULL,
|
|
8
|
+
TRANSITION_COLORS,
|
|
9
|
+
} from '../../styles/primitives';
|
|
10
|
+
import {
|
|
11
|
+
type SharedButtonSemanticColor,
|
|
12
|
+
sharedGhostSurfaceCompounds,
|
|
13
|
+
sharedOutlineGhostForegroundTextCompounds,
|
|
14
|
+
} from '../Button/buttonSharedVariants';
|
|
15
|
+
|
|
16
|
+
function actionOnlyCompounds<T extends { color: SharedButtonSemanticColor }>(
|
|
17
|
+
compounds: T[],
|
|
18
|
+
): Omit<T, 'color'>[] {
|
|
19
|
+
return compounds
|
|
20
|
+
.filter((c) => c.color === 'action')
|
|
21
|
+
.map((c) => {
|
|
22
|
+
const { color, ...rest } = c;
|
|
23
|
+
void color;
|
|
24
|
+
return rest;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Dark-mode hover/active surface for ghost (solid uses a separate compound with border). */
|
|
29
|
+
const iconButtonDarkGhostSurfaceCompounds = actionOnlyCompounds(sharedGhostSurfaceCompounds).map(
|
|
30
|
+
(c) => ({
|
|
31
|
+
variant: 'ghost' as const,
|
|
32
|
+
...c,
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const iconButtonForegroundTextCompounds = actionOnlyCompounds(
|
|
37
|
+
sharedOutlineGhostForegroundTextCompounds,
|
|
38
|
+
).map((c) => ({
|
|
39
|
+
variant: ['solid', 'ghost'] as ('solid' | 'ghost')[],
|
|
40
|
+
...c,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
export const iconButtonRootVariants = cva(
|
|
44
|
+
[
|
|
45
|
+
'flex items-center justify-center',
|
|
46
|
+
RADIUS_FULL,
|
|
47
|
+
'web:outline-none web:focus:outline-none web:focus-visible:outline-none',
|
|
48
|
+
TRANSITION_COLORS,
|
|
49
|
+
DISABLED_OPACITY,
|
|
50
|
+
DISABLED_CURSOR,
|
|
51
|
+
'web:data-[focus-visible=true]:ring-2 web:data-[focus-visible=true]:ring-slate-400/50 web:data-[focus-visible=true]:ring-offset-2',
|
|
52
|
+
],
|
|
53
|
+
{
|
|
54
|
+
variants: {
|
|
55
|
+
variant: {
|
|
56
|
+
/** Same surface as ghost, with a light gray border (aligned with Button `outline`). */
|
|
57
|
+
solid: [
|
|
58
|
+
'bg-transparent',
|
|
59
|
+
`border ${COLOR_BORDER_DEFAULT}`,
|
|
60
|
+
'data-[hover=true]:bg-slate-50',
|
|
61
|
+
'data-[active=true]:bg-slate-100',
|
|
62
|
+
],
|
|
63
|
+
ghost: [
|
|
64
|
+
'bg-transparent',
|
|
65
|
+
'data-[hover=true]:bg-slate-50',
|
|
66
|
+
'data-[active=true]:bg-slate-100',
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
size: {
|
|
70
|
+
default: 'h-10 w-10 min-w-10 shrink-0 p-0',
|
|
71
|
+
small: 'h-8 w-8 min-w-8 shrink-0 p-0',
|
|
72
|
+
},
|
|
73
|
+
mode: {
|
|
74
|
+
light: [],
|
|
75
|
+
dark: [],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
compoundVariants: [
|
|
79
|
+
...iconButtonDarkGhostSurfaceCompounds,
|
|
80
|
+
{
|
|
81
|
+
variant: 'solid',
|
|
82
|
+
mode: 'dark',
|
|
83
|
+
className: [
|
|
84
|
+
'border-slate-700',
|
|
85
|
+
Platform.select({
|
|
86
|
+
default: 'data-[active=true]:bg-slate-700',
|
|
87
|
+
web: 'data-[hover=true]:bg-slate-800 data-[active=true]:data-[hover=true]:bg-slate-700',
|
|
88
|
+
}),
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
defaultVariants: {
|
|
93
|
+
variant: 'solid',
|
|
94
|
+
size: 'default',
|
|
95
|
+
mode: 'light',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
export const iconButtonGlyphVariants = cva([], {
|
|
101
|
+
variants: {
|
|
102
|
+
size: {
|
|
103
|
+
default: 'size-5',
|
|
104
|
+
small: 'size-4',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
defaultVariants: {
|
|
108
|
+
size: 'default',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/** Icon fill aligned with Button outline/ghost action label colors (solid matches ghost). */
|
|
113
|
+
export const iconButtonIconColorVariants = cva([], {
|
|
114
|
+
variants: {
|
|
115
|
+
variant: {
|
|
116
|
+
solid: [],
|
|
117
|
+
ghost: [],
|
|
118
|
+
},
|
|
119
|
+
mode: {
|
|
120
|
+
light: [],
|
|
121
|
+
dark: [],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
compoundVariants: [...iconButtonForegroundTextCompounds],
|
|
125
|
+
defaultVariants: {
|
|
126
|
+
variant: 'solid',
|
|
127
|
+
mode: 'light',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export type IconButtonVariantProps = VariantProps<typeof iconButtonRootVariants>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around React Native's `Image`.
|
|
3
|
+
*
|
|
4
|
+
* Design-system layout (sizes, radii, loading/error UI) is intentionally
|
|
5
|
+
* NOT baked in here — use purpose-built components like `Avatar` instead.
|
|
6
|
+
*
|
|
7
|
+
* What this wrapper adds over a raw RN `Image`:
|
|
8
|
+
* - Accepts a plain URI **string** as `source` (auto-normalized to `{ uri }`).
|
|
9
|
+
* - Extends the prop surface with **web-only** HTML attributes (`sizes`,
|
|
10
|
+
* `loading`, `decoding`, etc.) for better `react-native-web` support.
|
|
11
|
+
* Props already covered by RN 0.81+ (`alt`, `srcSet`, `crossOrigin`,
|
|
12
|
+
* `referrerPolicy`) are inherited from the native typings.
|
|
13
|
+
* - All other props and `ref` are forwarded to the underlying `Image`.
|
|
14
|
+
*
|
|
15
|
+
* Inspired by Expo's html-elements Image primitive:
|
|
16
|
+
* @see https://github.com/expo/expo/blob/main/packages/html-elements/src/primitives/Image.tsx
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { forwardRef, type ComponentPropsWithoutRef, type ComponentRef } from 'react';
|
|
20
|
+
import { Image as RNImage, type ImageSourcePropType } from 'react-native';
|
|
21
|
+
|
|
22
|
+
/** Coerces a plain URI string into the `{ uri }` shape RN expects. */
|
|
23
|
+
function normalizeSource(source: string | ImageSourcePropType): ImageSourcePropType {
|
|
24
|
+
if (typeof source === 'string') {
|
|
25
|
+
return { uri: source };
|
|
26
|
+
}
|
|
27
|
+
return source;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Web-oriented props not yet covered by react-native's Image typings.
|
|
32
|
+
// Props already present in RN 0.81+ (alt, srcSet, crossOrigin,
|
|
33
|
+
// referrerPolicy) are inherited from the native Image props.
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* HTML-ish attributes not in RN’s core typings. Forwarded to `Image` with `...props`;
|
|
38
|
+
* **react-native-web** maps supported ones to the DOM `<img>`. On iOS/Android they are
|
|
39
|
+
* typically ignored (no visual effect).
|
|
40
|
+
*/
|
|
41
|
+
export interface WebImageProps {
|
|
42
|
+
/** DOM `sizes` for responsive selection with `srcSet` — web only. */
|
|
43
|
+
sizes?: string;
|
|
44
|
+
/** DOM lazy-loading hint — web only. */
|
|
45
|
+
loading?: 'lazy' | 'eager';
|
|
46
|
+
/** DOM decode hint — web only. */
|
|
47
|
+
decoding?: 'async' | 'auto' | 'sync';
|
|
48
|
+
/** DOM drag behavior — web only. */
|
|
49
|
+
draggable?: boolean;
|
|
50
|
+
/** DOM focus order — web only. */
|
|
51
|
+
tabIndex?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// ImageProps = native Image props (minus `source`) + WebImageProps + our overrides.
|
|
56
|
+
// `source` is re-declared to also accept a plain string.
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
export type ImageProps = WebImageProps &
|
|
60
|
+
Omit<ComponentPropsWithoutRef<typeof RNImage>, 'source'> & {
|
|
61
|
+
/** URI string (normalized to `{ uri }`) or standard `ImageSourcePropType`. */
|
|
62
|
+
source: string | ImageSourcePropType;
|
|
63
|
+
className?: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Component
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
export const Image = forwardRef<ComponentRef<typeof RNImage>, ImageProps>(function Image(
|
|
71
|
+
{ source, ...props },
|
|
72
|
+
ref,
|
|
73
|
+
) {
|
|
74
|
+
return <RNImage source={normalizeSource(source)} {...props} ref={ref} />;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
Image.displayName = 'Image';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Image, type ImageProps, type WebImageProps } from './Image';
|
package/src/components/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './Card';
|
|
|
7
7
|
export * from './Checkbox';
|
|
8
8
|
export * from './Chip';
|
|
9
9
|
export * from './Dialog';
|
|
10
|
+
export * from './Image';
|
|
10
11
|
export * from './Input';
|
|
11
12
|
export * from './Link';
|
|
12
13
|
export * from './ProgressSegmented';
|
|
@@ -17,3 +18,4 @@ export { HStack, VStack } from './Stack';
|
|
|
17
18
|
export * from './Heading';
|
|
18
19
|
export * from './Text';
|
|
19
20
|
export * from './Icon';
|
|
21
|
+
export * from './IconButton';
|