panelui-native 0.14.0 → 0.16.0

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.
Files changed (31) hide show
  1. package/README.md +2 -0
  2. package/lib/module/components/direction/index.js +86 -0
  3. package/lib/module/components/direction/index.js.map +1 -0
  4. package/lib/module/components/message/index.js +16 -1
  5. package/lib/module/components/message/index.js.map +1 -1
  6. package/lib/module/components/soundwave/index.js +813 -0
  7. package/lib/module/components/soundwave/index.js.map +1 -0
  8. package/lib/module/components/typography/index.js +196 -5
  9. package/lib/module/components/typography/index.js.map +1 -1
  10. package/lib/module/icons/index.js +69 -0
  11. package/lib/module/icons/index.js.map +1 -1
  12. package/lib/module/index.js +3 -1
  13. package/lib/module/index.js.map +1 -1
  14. package/lib/typescript/src/components/direction/index.d.ts +64 -0
  15. package/lib/typescript/src/components/direction/index.d.ts.map +1 -0
  16. package/lib/typescript/src/components/message/index.d.ts.map +1 -1
  17. package/lib/typescript/src/components/soundwave/index.d.ts +91 -0
  18. package/lib/typescript/src/components/soundwave/index.d.ts.map +1 -0
  19. package/lib/typescript/src/components/typography/index.d.ts +154 -1
  20. package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
  21. package/lib/typescript/src/icons/index.d.ts +6 -0
  22. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  23. package/lib/typescript/src/index.d.ts +3 -1
  24. package/lib/typescript/src/index.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/components/direction/index.tsx +90 -0
  27. package/src/components/message/index.tsx +17 -1
  28. package/src/components/soundwave/index.tsx +1029 -0
  29. package/src/components/typography/index.tsx +231 -10
  30. package/src/icons/index.tsx +36 -0
  31. package/src/index.ts +15 -0
@@ -1,14 +1,32 @@
1
1
  /**
2
- * Typography — semantic text presets.
2
+ * Typography — semantic text presets, and the marks that go on them.
3
3
  *
4
4
  * The type scale in one place, so headings stay consistent instead of being
5
5
  * rebuilt out of size and weight classes at each call site. Built on the Text
6
6
  * primitive, so every preset keeps `className` passthrough and theme colours.
7
+ *
8
+ * ```tsx
9
+ * <Typography type="h2">Billing</Typography>
10
+ * <Typography>Your plan renews on the 1st.</Typography>
11
+ * <Typography underline>Terms of service</Typography>
12
+ * ```
13
+ *
14
+ * A preset sets size, weight and tracking together; the marks — `underline`,
15
+ * `italic`, `strike` — and `weight`, `align` and `transform` layer on top of
16
+ * whichever preset is in force. They are props rather than class names because
17
+ * the point of this component is that a screen never has to know which
18
+ * utilities add up to "a bolded lead paragraph".
19
+ *
20
+ * React Native draws no list markers and has no blockquote, so
21
+ * `Typography.List` and `Typography.Blockquote` build both out of a row and a
22
+ * rule — which is exactly the kind of thing this component exists to stop
23
+ * people rebuilding per screen.
7
24
  */
8
- import { forwardRef } from 'react';
25
+ import { Children, forwardRef, type ReactNode } from 'react';
9
26
  import { View, type Text as RNText, type ViewProps } from 'react-native';
10
27
  import { tv, type VariantProps } from 'tailwind-variants';
11
28
  import { Text, type TextProps } from '../../primitives/text';
29
+ import { cn } from '../../utils/cn';
12
30
 
13
31
  const typographyVariants = tv({
14
32
  base: 'text-foreground',
@@ -20,11 +38,43 @@ const typographyVariants = tv({
20
38
  h4: 'text-xl font-semibold',
21
39
  h5: 'text-lg font-semibold',
22
40
  h6: 'text-base font-semibold',
41
+ /** The sentence under a heading, set larger and quieter than body. */
42
+ lead: 'text-xl font-normal text-muted-foreground',
23
43
  body: 'text-base font-normal',
24
44
  'body-sm': 'text-sm font-normal',
25
45
  'body-xs': 'text-xs font-normal',
46
+ /** Body, one step up — for a number or a name that carries the row. */
47
+ large: 'text-lg font-semibold',
48
+ /** Body, one step down and tighter — captions, footnotes, meta. */
49
+ small: 'text-sm font-medium leading-none',
50
+ blockquote: 'text-base font-normal italic',
26
51
  code: 'font-mono text-sm text-foreground',
27
52
  },
53
+ weight: {
54
+ normal: 'font-normal',
55
+ medium: 'font-medium',
56
+ semibold: 'font-semibold',
57
+ bold: 'font-bold',
58
+ },
59
+ align: {
60
+ left: 'text-left',
61
+ center: 'text-center',
62
+ right: 'text-right',
63
+ },
64
+ transform: {
65
+ uppercase: 'uppercase',
66
+ lowercase: 'lowercase',
67
+ capitalize: 'capitalize',
68
+ },
69
+ underline: {
70
+ true: 'underline',
71
+ },
72
+ italic: {
73
+ true: 'italic',
74
+ },
75
+ strike: {
76
+ true: 'line-through',
77
+ },
28
78
  muted: {
29
79
  true: 'text-muted-foreground',
30
80
  },
@@ -38,16 +88,38 @@ export type TypographyType = NonNullable<
38
88
  VariantProps<typeof typographyVariants>['type']
39
89
  >;
40
90
 
91
+ export type TypographyWeight = NonNullable<
92
+ VariantProps<typeof typographyVariants>['weight']
93
+ >;
94
+
41
95
  export interface TypographyProps
42
96
  extends Omit<TextProps, 'size' | 'weight'>,
43
97
  VariantProps<typeof typographyVariants> {
44
98
  className?: string;
99
+ /**
100
+ * Overrides the weight the preset sets. This is the one to reach for when a
101
+ * paragraph needs a bolded run and a heading would be wrong.
102
+ */
103
+ weight?: TypographyWeight;
104
+ /** Underlines the text — a link, a defined term, a signature line. */
105
+ underline?: boolean;
106
+ /** Slants the text. */
107
+ italic?: boolean;
108
+ /** Strikes the text through: an old price, a completed task. */
109
+ strike?: boolean;
110
+ /** Horizontal alignment within whatever the text is laid out in. */
111
+ align?: 'left' | 'center' | 'right';
112
+ /** Case, applied for display without changing the string underneath. */
113
+ transform?: 'uppercase' | 'lowercase' | 'capitalize';
45
114
  }
46
115
 
47
116
  /** Heading levels, for `Typography.Heading`. */
48
117
  type HeadingType = Extract<TypographyType, 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>;
49
118
  /** Body sizes, for `Typography.Paragraph`. */
50
- type ParagraphType = Extract<TypographyType, 'body' | 'body-sm' | 'body-xs'>;
119
+ type ParagraphType = Extract<
120
+ TypographyType,
121
+ 'body' | 'body-sm' | 'body-xs' | 'lead' | 'large' | 'small'
122
+ >;
51
123
 
52
124
  /** Maps a heading preset to its accessibility heading level. */
53
125
  const HEADING_LEVEL: Record<HeadingType, number> = {
@@ -60,8 +132,25 @@ const HEADING_LEVEL: Record<HeadingType, number> = {
60
132
  };
61
133
 
62
134
  const TypographyRoot = forwardRef<RNText, TypographyProps>(
63
- ({ className, type, muted, ...props }, ref) => (
64
- <Text ref={ref} className={typographyVariants({ type, muted, className })} {...props} />
135
+ (
136
+ { className, type, muted, weight, align, transform, underline, italic, strike, ...props },
137
+ ref
138
+ ) => (
139
+ <Text
140
+ ref={ref}
141
+ className={typographyVariants({
142
+ type,
143
+ muted,
144
+ weight,
145
+ align,
146
+ transform,
147
+ underline,
148
+ italic,
149
+ strike,
150
+ className,
151
+ })}
152
+ {...props}
153
+ />
65
154
  )
66
155
  );
67
156
  TypographyRoot.displayName = 'Typography';
@@ -72,12 +161,25 @@ export interface TypographyHeadingProps extends Omit<TypographyProps, 'type'> {
72
161
 
73
162
  /** Heading text, wired up with the matching accessibility heading level. */
74
163
  const TypographyHeading = forwardRef<RNText, TypographyHeadingProps>(
75
- ({ className, type = 'h2', muted, ...props }, ref) => (
164
+ (
165
+ { className, type = 'h2', muted, weight, align, transform, underline, italic, strike, ...props },
166
+ ref
167
+ ) => (
76
168
  <Text
77
169
  ref={ref}
78
170
  accessibilityRole="header"
79
171
  aria-level={HEADING_LEVEL[type]}
80
- className={typographyVariants({ type, muted, className })}
172
+ className={typographyVariants({
173
+ type,
174
+ muted,
175
+ weight,
176
+ align,
177
+ transform,
178
+ underline,
179
+ italic,
180
+ strike,
181
+ className,
182
+ })}
81
183
  {...props}
82
184
  />
83
185
  )
@@ -89,8 +191,25 @@ export interface TypographyParagraphProps extends Omit<TypographyProps, 'type'>
89
191
  }
90
192
 
91
193
  const TypographyParagraph = forwardRef<RNText, TypographyParagraphProps>(
92
- ({ className, type = 'body', muted, ...props }, ref) => (
93
- <Text ref={ref} className={typographyVariants({ type, muted, className })} {...props} />
194
+ (
195
+ { className, type = 'body', muted, weight, align, transform, underline, italic, strike, ...props },
196
+ ref
197
+ ) => (
198
+ <Text
199
+ ref={ref}
200
+ className={typographyVariants({
201
+ type,
202
+ muted,
203
+ weight,
204
+ align,
205
+ transform,
206
+ underline,
207
+ italic,
208
+ strike,
209
+ className,
210
+ })}
211
+ {...props}
212
+ />
94
213
  )
95
214
  );
96
215
  TypographyParagraph.displayName = 'Typography.Paragraph';
@@ -106,7 +225,7 @@ const TypographyCode = forwardRef<View, TypographyCodeProps & Pick<ViewProps, 't
106
225
  <View
107
226
  ref={ref}
108
227
  testID={testID}
109
- className={`self-start rounded-md bg-muted px-1.5 py-1 ${containerClassName ?? ''}`}
228
+ className={cn('self-start rounded-md bg-muted px-1.5 py-1', containerClassName)}
110
229
  >
111
230
  <Text className={typographyVariants({ type: 'code', muted, className })} {...props} />
112
231
  </View>
@@ -114,8 +233,110 @@ const TypographyCode = forwardRef<View, TypographyCodeProps & Pick<ViewProps, 't
114
233
  );
115
234
  TypographyCode.displayName = 'Typography.Code';
116
235
 
236
+ export interface TypographyBlockquoteProps extends Omit<TypographyProps, 'type'> {
237
+ /** Classes for the row that carries the rule. */
238
+ containerClassName?: string;
239
+ }
240
+
241
+ /**
242
+ * A quotation, marked by a rule down its leading edge.
243
+ *
244
+ * The rule uses `border-s`, so it moves to the right-hand side under a
245
+ * right-to-left `Direction` without the quote having to know.
246
+ */
247
+ const TypographyBlockquote = forwardRef<View, TypographyBlockquoteProps>(
248
+ ({ className, containerClassName, muted, weight, align, transform, underline, italic = true, strike, children, ...props }, ref) => (
249
+ <View ref={ref} className={cn('border-s-2 border-border ps-4', containerClassName)}>
250
+ <Text
251
+ className={typographyVariants({
252
+ type: 'blockquote',
253
+ muted,
254
+ weight,
255
+ align,
256
+ transform,
257
+ underline,
258
+ italic,
259
+ strike,
260
+ className,
261
+ })}
262
+ {...props}
263
+ >
264
+ {children}
265
+ </Text>
266
+ </View>
267
+ )
268
+ );
269
+ TypographyBlockquote.displayName = 'Typography.Blockquote';
270
+
271
+ export interface TypographyListProps extends ViewProps {
272
+ className?: string;
273
+ /** Numbered rather than bulleted. The numbers are drawn, not counted by CSS. */
274
+ ordered?: boolean;
275
+ children?: ReactNode;
276
+ }
277
+
278
+ /**
279
+ * A bulleted or numbered list.
280
+ *
281
+ * React Native has no list markers at all, so each row is a marker and a text
282
+ * block side by side. The marker is drawn here rather than in the item, because
283
+ * only the list knows whether it is a bullet or a number — and only the list
284
+ * knows which number.
285
+ */
286
+ const TypographyList = forwardRef<View, TypographyListProps>(
287
+ ({ className, ordered = false, children, ...props }, ref) => (
288
+ <View ref={ref} accessibilityRole="list" className={cn('gap-2', className)} {...props}>
289
+ {Children.map(children, (child, index) => (
290
+ <View className="w-full flex-row gap-2">
291
+ {ordered ? (
292
+ <Text className="text-base text-muted-foreground">{index + 1}.</Text>
293
+ ) : (
294
+ // A dot rather than "•": the character's size and baseline vary by
295
+ // platform font, and a view does not.
296
+ <View className="mt-2.5 size-1.5 shrink-0 rounded-full bg-muted-foreground" />
297
+ )}
298
+ <View className="flex-1">{child}</View>
299
+ </View>
300
+ ))}
301
+ </View>
302
+ )
303
+ );
304
+ TypographyList.displayName = 'Typography.List';
305
+
306
+ export interface TypographyListItemProps extends Omit<TypographyProps, 'type'> {
307
+ type?: ParagraphType;
308
+ }
309
+
310
+ /** One line of a list. The marker beside it belongs to the list. */
311
+ const TypographyListItem = forwardRef<RNText, TypographyListItemProps>(
312
+ (
313
+ { className, type = 'body', muted, weight, align, transform, underline, italic, strike, ...props },
314
+ ref
315
+ ) => (
316
+ <Text
317
+ ref={ref}
318
+ className={typographyVariants({
319
+ type,
320
+ muted,
321
+ weight,
322
+ align,
323
+ transform,
324
+ underline,
325
+ italic,
326
+ strike,
327
+ className,
328
+ })}
329
+ {...props}
330
+ />
331
+ )
332
+ );
333
+ TypographyListItem.displayName = 'Typography.ListItem';
334
+
117
335
  export const Typography = Object.assign(TypographyRoot, {
118
336
  Heading: TypographyHeading,
119
337
  Paragraph: TypographyParagraph,
120
338
  Code: TypographyCode,
339
+ Blockquote: TypographyBlockquote,
340
+ List: TypographyList,
341
+ ListItem: TypographyListItem,
121
342
  });
@@ -510,3 +510,39 @@ export function PaperclipIcon({ size = 16, color, ...props }: IconProps) {
510
510
  </Svg>
511
511
  );
512
512
  }
513
+
514
+ /** A capsule on a stand — a microphone, for anything voice. */
515
+ export function MicIcon({ size = 16, color, ...props }: IconProps) {
516
+ const resolved = useResolvedColor(color, '#737373');
517
+ return (
518
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
519
+ <Path
520
+ d="M12 3a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V6a3 3 0 0 0-3-3zM5 11v1a7 7 0 0 0 14 0v-1M12 19v3"
521
+ stroke={resolved}
522
+ strokeWidth={2}
523
+ strokeLinecap="round"
524
+ strokeLinejoin="round"
525
+ />
526
+ </Svg>
527
+ );
528
+ }
529
+
530
+ /** A filled triangle — start playback. */
531
+ export function PlayIcon({ size = 16, color, ...props }: IconProps) {
532
+ const resolved = useResolvedColor(color, '#737373');
533
+ return (
534
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
535
+ <Path d="M7 4.5v15l13-7.5z" fill={resolved} stroke={resolved} strokeWidth={2} strokeLinejoin="round" />
536
+ </Svg>
537
+ );
538
+ }
539
+
540
+ /** Two filled bars — hold playback where it is. */
541
+ export function PauseIcon({ size = 16, color, ...props }: IconProps) {
542
+ const resolved = useResolvedColor(color, '#737373');
543
+ return (
544
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
545
+ <Path d="M8 5h2v14H8zM14 5h2v14h-2z" fill={resolved} stroke={resolved} strokeWidth={1.5} strokeLinejoin="round" />
546
+ </Svg>
547
+ );
548
+ }
package/src/index.ts CHANGED
@@ -92,6 +92,18 @@ export {
92
92
  type DialogProps,
93
93
  type DialogContentProps,
94
94
  } from './components/dialog';
95
+ export {
96
+ Soundwave,
97
+ type SoundwaveProps,
98
+ type SoundwaveState,
99
+ type SoundwaveVariant,
100
+ } from './components/soundwave';
101
+ export {
102
+ Direction,
103
+ useDirection,
104
+ type DirectionProps,
105
+ type DirectionValue,
106
+ } from './components/direction';
95
107
  export {
96
108
  Frame,
97
109
  type FrameProps,
@@ -325,9 +337,12 @@ export {
325
337
  IconColorProvider,
326
338
  InfoIcon,
327
339
  ImageIcon,
340
+ MicIcon,
328
341
  MoonIcon,
329
342
  PackageIcon,
330
343
  PaperclipIcon,
344
+ PauseIcon,
345
+ PlayIcon,
331
346
  PlusSquareIcon,
332
347
  ReceiptIcon,
333
348
  SearchIcon,