@pickle-ui/react-native 1.0.0-beta.1 → 1.0.0-beta.3

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 (255) hide show
  1. package/README.md +62 -24
  2. package/dist/components/Badge.js +1 -1
  3. package/dist/components/Badge.js.map +1 -1
  4. package/dist/components/Button.d.ts.map +1 -1
  5. package/dist/components/Button.js +27 -23
  6. package/dist/components/Button.js.map +1 -1
  7. package/dist/components/Card.d.ts.map +1 -1
  8. package/dist/components/Card.js +7 -4
  9. package/dist/components/Card.js.map +1 -1
  10. package/dist/components/Flex.d.ts +25 -0
  11. package/dist/components/Flex.d.ts.map +1 -0
  12. package/dist/components/Flex.js +24 -0
  13. package/dist/components/Flex.js.map +1 -0
  14. package/dist/components/Grid.d.ts +36 -0
  15. package/dist/components/Grid.d.ts.map +1 -0
  16. package/dist/components/Grid.js +68 -0
  17. package/dist/components/Grid.js.map +1 -0
  18. package/dist/components/Separator.d.ts +11 -0
  19. package/dist/components/Separator.d.ts.map +1 -0
  20. package/dist/components/Separator.js +12 -0
  21. package/dist/components/Separator.js.map +1 -0
  22. package/dist/components/Spinner.d.ts +14 -0
  23. package/dist/components/Spinner.d.ts.map +1 -0
  24. package/dist/components/Spinner.js +41 -0
  25. package/dist/components/Spinner.js.map +1 -0
  26. package/dist/components/Text.d.ts.map +1 -1
  27. package/dist/components/Text.js +5 -4
  28. package/dist/components/Text.js.map +1 -1
  29. package/dist/components/XStack.d.ts +4 -0
  30. package/dist/components/XStack.d.ts.map +1 -0
  31. package/dist/components/XStack.js +6 -0
  32. package/dist/components/XStack.js.map +1 -0
  33. package/dist/components/YStack.d.ts +4 -0
  34. package/dist/components/YStack.d.ts.map +1 -0
  35. package/dist/components/YStack.js +6 -0
  36. package/dist/components/YStack.js.map +1 -0
  37. package/dist/components/_internal/InputFrame.d.ts +13 -0
  38. package/dist/components/_internal/InputFrame.d.ts.map +1 -0
  39. package/dist/components/_internal/InputFrame.js +14 -0
  40. package/dist/components/_internal/InputFrame.js.map +1 -0
  41. package/dist/components/_internal/StoryItem.d.ts +6 -0
  42. package/dist/components/_internal/StoryItem.d.ts.map +1 -0
  43. package/dist/components/_internal/StoryItem.js +9 -0
  44. package/dist/components/_internal/StoryItem.js.map +1 -0
  45. package/dist/components/_internal/layout.d.ts +79 -0
  46. package/dist/components/_internal/layout.d.ts.map +1 -0
  47. package/dist/components/_internal/layout.js +73 -0
  48. package/dist/components/_internal/layout.js.map +1 -0
  49. package/dist/components/_internal/useControllableState.d.ts +7 -0
  50. package/dist/components/_internal/useControllableState.d.ts.map +1 -0
  51. package/dist/components/_internal/useControllableState.js +17 -0
  52. package/dist/components/_internal/useControllableState.js.map +1 -0
  53. package/dist/components/form/Checkbox.d.ts +26 -0
  54. package/dist/components/form/Checkbox.d.ts.map +1 -0
  55. package/dist/components/form/Checkbox.js +43 -0
  56. package/dist/components/form/Checkbox.js.map +1 -0
  57. package/dist/components/form/Form.context.d.ts +34 -0
  58. package/dist/components/form/Form.context.d.ts.map +1 -0
  59. package/dist/components/form/Form.context.js +53 -0
  60. package/dist/components/form/Form.context.js.map +1 -0
  61. package/dist/components/form/Form.d.ts +33 -0
  62. package/dist/components/form/Form.d.ts.map +1 -0
  63. package/dist/components/form/Form.js +36 -0
  64. package/dist/components/form/Form.js.map +1 -0
  65. package/dist/components/form/FormCheckbox.d.ts +7 -0
  66. package/dist/components/form/FormCheckbox.d.ts.map +1 -0
  67. package/dist/components/form/FormCheckbox.js +24 -0
  68. package/dist/components/form/FormCheckbox.js.map +1 -0
  69. package/dist/components/form/FormDescription.d.ts +4 -0
  70. package/dist/components/form/FormDescription.d.ts.map +1 -0
  71. package/dist/components/form/FormDescription.js +7 -0
  72. package/dist/components/form/FormDescription.js.map +1 -0
  73. package/dist/components/form/FormError.d.ts +5 -0
  74. package/dist/components/form/FormError.d.ts.map +1 -0
  75. package/dist/components/form/FormError.js +8 -0
  76. package/dist/components/form/FormError.js.map +1 -0
  77. package/dist/components/form/FormField.d.ts +17 -0
  78. package/dist/components/form/FormField.d.ts.map +1 -0
  79. package/dist/components/form/FormField.js +36 -0
  80. package/dist/components/form/FormField.js.map +1 -0
  81. package/dist/components/form/FormInput.d.ts +10 -0
  82. package/dist/components/form/FormInput.d.ts.map +1 -0
  83. package/dist/components/form/FormInput.js +36 -0
  84. package/dist/components/form/FormInput.js.map +1 -0
  85. package/dist/components/form/FormInputGroup.d.ts +8 -0
  86. package/dist/components/form/FormInputGroup.d.ts.map +1 -0
  87. package/dist/components/form/FormInputGroup.js +18 -0
  88. package/dist/components/form/FormInputGroup.js.map +1 -0
  89. package/dist/components/form/FormLabel.d.ts +4 -0
  90. package/dist/components/form/FormLabel.d.ts.map +1 -0
  91. package/dist/components/form/FormLabel.js +7 -0
  92. package/dist/components/form/FormLabel.js.map +1 -0
  93. package/dist/components/form/FormRadioGroup.d.ts +11 -0
  94. package/dist/components/form/FormRadioGroup.d.ts.map +1 -0
  95. package/dist/components/form/FormRadioGroup.js +24 -0
  96. package/dist/components/form/FormRadioGroup.js.map +1 -0
  97. package/dist/components/form/FormRoot.d.ts +13 -0
  98. package/dist/components/form/FormRoot.d.ts.map +1 -0
  99. package/dist/components/form/FormRoot.js +19 -0
  100. package/dist/components/form/FormRoot.js.map +1 -0
  101. package/dist/components/form/FormTextarea.d.ts +7 -0
  102. package/dist/components/form/FormTextarea.d.ts.map +1 -0
  103. package/dist/components/form/FormTextarea.js +27 -0
  104. package/dist/components/form/FormTextarea.js.map +1 -0
  105. package/dist/components/form/Input.d.ts +17 -0
  106. package/dist/components/form/Input.d.ts.map +1 -0
  107. package/dist/components/form/Input.js +32 -0
  108. package/dist/components/form/Input.js.map +1 -0
  109. package/dist/components/form/InputGroup.context.d.ts +10 -0
  110. package/dist/components/form/InputGroup.context.d.ts.map +1 -0
  111. package/dist/components/form/InputGroup.context.js +8 -0
  112. package/dist/components/form/InputGroup.context.js.map +1 -0
  113. package/dist/components/form/InputGroup.d.ts +31 -0
  114. package/dist/components/form/InputGroup.d.ts.map +1 -0
  115. package/dist/components/form/InputGroup.js +123 -0
  116. package/dist/components/form/InputGroup.js.map +1 -0
  117. package/dist/components/form/RadioGroup.d.ts +29 -0
  118. package/dist/components/form/RadioGroup.d.ts.map +1 -0
  119. package/dist/components/form/RadioGroup.js +42 -0
  120. package/dist/components/form/RadioGroup.js.map +1 -0
  121. package/dist/components/form/Textarea.d.ts +19 -0
  122. package/dist/components/form/Textarea.d.ts.map +1 -0
  123. package/dist/components/form/Textarea.js +26 -0
  124. package/dist/components/form/Textarea.js.map +1 -0
  125. package/dist/entries/checkbox.d.ts +3 -0
  126. package/dist/entries/checkbox.d.ts.map +1 -0
  127. package/dist/entries/checkbox.js +2 -0
  128. package/dist/entries/checkbox.js.map +1 -0
  129. package/dist/entries/flex.d.ts +3 -0
  130. package/dist/entries/flex.d.ts.map +1 -0
  131. package/dist/entries/flex.js +2 -0
  132. package/dist/entries/flex.js.map +1 -0
  133. package/dist/entries/form.d.ts +3 -0
  134. package/dist/entries/form.d.ts.map +1 -0
  135. package/dist/entries/form.js +2 -0
  136. package/dist/entries/form.js.map +1 -0
  137. package/dist/entries/grid.d.ts +3 -0
  138. package/dist/entries/grid.d.ts.map +1 -0
  139. package/dist/entries/grid.js +2 -0
  140. package/dist/entries/grid.js.map +1 -0
  141. package/dist/entries/input-group.d.ts +3 -0
  142. package/dist/entries/input-group.d.ts.map +1 -0
  143. package/dist/entries/input-group.js +2 -0
  144. package/dist/entries/input-group.js.map +1 -0
  145. package/dist/entries/input.d.ts +3 -0
  146. package/dist/entries/input.d.ts.map +1 -0
  147. package/dist/entries/input.js +2 -0
  148. package/dist/entries/input.js.map +1 -0
  149. package/dist/entries/radio-group.d.ts +3 -0
  150. package/dist/entries/radio-group.d.ts.map +1 -0
  151. package/dist/entries/radio-group.js +2 -0
  152. package/dist/entries/radio-group.js.map +1 -0
  153. package/dist/entries/separator.d.ts +3 -0
  154. package/dist/entries/separator.d.ts.map +1 -0
  155. package/dist/entries/separator.js +2 -0
  156. package/dist/entries/separator.js.map +1 -0
  157. package/dist/entries/spinner.d.ts +3 -0
  158. package/dist/entries/spinner.d.ts.map +1 -0
  159. package/dist/entries/spinner.js +2 -0
  160. package/dist/entries/spinner.js.map +1 -0
  161. package/dist/entries/textarea.d.ts +3 -0
  162. package/dist/entries/textarea.d.ts.map +1 -0
  163. package/dist/entries/textarea.js +2 -0
  164. package/dist/entries/textarea.js.map +1 -0
  165. package/dist/entries/x-stack.d.ts +3 -0
  166. package/dist/entries/x-stack.d.ts.map +1 -0
  167. package/dist/entries/x-stack.js +2 -0
  168. package/dist/entries/x-stack.js.map +1 -0
  169. package/dist/entries/y-stack.d.ts +3 -0
  170. package/dist/entries/y-stack.d.ts.map +1 -0
  171. package/dist/entries/y-stack.js +2 -0
  172. package/dist/entries/y-stack.js.map +1 -0
  173. package/dist/index.d.ts +12 -0
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +12 -0
  176. package/dist/index.js.map +1 -1
  177. package/package.json +8 -3
  178. package/src/base.css +23 -5
  179. package/src/components/Badge.tsx +1 -1
  180. package/src/components/Button.tsx +27 -23
  181. package/src/components/Card.tsx +12 -8
  182. package/src/components/Flex.stories.tsx +58 -0
  183. package/src/components/Flex.test.tsx +57 -0
  184. package/src/components/Flex.tsx +52 -0
  185. package/src/components/Grid.stories.tsx +55 -0
  186. package/src/components/Grid.test.tsx +140 -0
  187. package/src/components/Grid.tsx +166 -0
  188. package/src/components/Separator.stories.tsx +39 -0
  189. package/src/components/Separator.test.tsx +25 -0
  190. package/src/components/Separator.tsx +30 -0
  191. package/src/components/Spinner.stories.tsx +35 -0
  192. package/src/components/Spinner.test.tsx +30 -0
  193. package/src/components/Spinner.tsx +75 -0
  194. package/src/components/Text.stories.tsx +49 -0
  195. package/src/components/Text.tsx +5 -4
  196. package/src/components/XStack.stories.tsx +46 -0
  197. package/src/components/XStack.test.tsx +13 -0
  198. package/src/components/XStack.tsx +7 -0
  199. package/src/components/YStack.stories.tsx +46 -0
  200. package/src/components/YStack.test.tsx +13 -0
  201. package/src/components/YStack.tsx +7 -0
  202. package/src/components/_internal/InputFrame.tsx +50 -0
  203. package/src/components/_internal/StoryItem.tsx +24 -0
  204. package/src/components/_internal/layout.ts +103 -0
  205. package/src/components/_internal/useControllableState.ts +25 -0
  206. package/src/components/form/Checkbox.stories.tsx +67 -0
  207. package/src/components/form/Checkbox.test.tsx +88 -0
  208. package/src/components/form/Checkbox.tsx +167 -0
  209. package/src/components/form/Form.context.ts +91 -0
  210. package/src/components/form/Form.stories.tsx +152 -0
  211. package/src/components/form/Form.test.tsx +195 -0
  212. package/src/components/form/Form.tsx +51 -0
  213. package/src/components/form/FormCheckbox.tsx +42 -0
  214. package/src/components/form/FormDescription.tsx +14 -0
  215. package/src/components/form/FormError.tsx +20 -0
  216. package/src/components/form/FormField.tsx +82 -0
  217. package/src/components/form/FormInput.tsx +70 -0
  218. package/src/components/form/FormInputGroup.tsx +30 -0
  219. package/src/components/form/FormLabel.tsx +17 -0
  220. package/src/components/form/FormRadioGroup.tsx +48 -0
  221. package/src/components/form/FormRoot.tsx +41 -0
  222. package/src/components/form/FormTextarea.tsx +56 -0
  223. package/src/components/form/Input.stories.tsx +105 -0
  224. package/src/components/form/Input.test.tsx +76 -0
  225. package/src/components/form/Input.tsx +81 -0
  226. package/src/components/form/InputGroup.context.ts +11 -0
  227. package/src/components/form/InputGroup.stories.tsx +129 -0
  228. package/src/components/form/InputGroup.test.tsx +95 -0
  229. package/src/components/form/InputGroup.tsx +266 -0
  230. package/src/components/form/RadioGroup.stories.tsx +62 -0
  231. package/src/components/form/RadioGroup.test.tsx +98 -0
  232. package/src/components/form/RadioGroup.tsx +151 -0
  233. package/src/components/form/Textarea.stories.tsx +61 -0
  234. package/src/components/form/Textarea.test.tsx +35 -0
  235. package/src/components/form/Textarea.tsx +74 -0
  236. package/src/entries/checkbox.ts +2 -0
  237. package/src/entries/flex.ts +2 -0
  238. package/src/entries/form.ts +13 -0
  239. package/src/entries/grid.ts +2 -0
  240. package/src/entries/input-group.ts +8 -0
  241. package/src/entries/input.ts +2 -0
  242. package/src/entries/radio-group.ts +5 -0
  243. package/src/entries/separator.ts +2 -0
  244. package/src/entries/spinner.ts +2 -0
  245. package/src/entries/textarea.ts +2 -0
  246. package/src/entries/x-stack.ts +2 -0
  247. package/src/entries/y-stack.ts +2 -0
  248. package/src/fonts.test.tsx +244 -0
  249. package/src/index.ts +12 -0
  250. package/src/styles.css +12 -0
  251. package/dist/components/_internal/Spinner.d.ts +0 -9
  252. package/dist/components/_internal/Spinner.d.ts.map +0 -1
  253. package/dist/components/_internal/Spinner.js +0 -31
  254. package/dist/components/_internal/Spinner.js.map +0 -1
  255. package/src/components/_internal/Spinner.tsx +0 -49
@@ -0,0 +1,74 @@
1
+ import { useState } from 'react';
2
+ import { TextInput } from 'react-native';
3
+
4
+ import { cn } from '../../lib/cn';
5
+ import { InputFrame } from '../_internal/InputFrame';
6
+
7
+ const sizeClasses = {
8
+ lg: 'min-h-18 px-[11px]',
9
+ md: 'min-h-17.5 px-[11px]',
10
+ sm: 'min-h-16 px-[9px]',
11
+ } as const;
12
+
13
+ export type TextareaProps = Omit<
14
+ React.ComponentProps<typeof TextInput>,
15
+ 'multiline'
16
+ > & {
17
+ /** Class overrides for the inner `TextInput`; `className` styles the control around it. */
18
+ classNames?: { textarea?: string };
19
+ /** Dims the field and stops editing, like the web's `disabled`. */
20
+ disabled?: boolean;
21
+ /** Marks the value as invalid, like the web's `aria-invalid`. */
22
+ invalid?: boolean;
23
+ size?: 'lg' | 'md' | 'sm';
24
+ };
25
+
26
+ /**
27
+ * A multi-line text field that grows with its content from three lines, like
28
+ * `Textarea` in @pickle-ui/react. Set a `min-h-*` class on
29
+ * `classNames.textarea` to change the minimum.
30
+ */
31
+ export function Textarea({
32
+ className,
33
+ classNames,
34
+ disabled = false,
35
+ editable = true,
36
+ invalid = false,
37
+ onBlur,
38
+ onFocus,
39
+ size = 'md',
40
+ ...props
41
+ }: TextareaProps) {
42
+ const [focused, setFocused] = useState(false);
43
+
44
+ return (
45
+ <InputFrame
46
+ className={className}
47
+ disabled={disabled}
48
+ focused={focused}
49
+ invalid={invalid}
50
+ >
51
+ <TextInput
52
+ accessibilityState={{ disabled }}
53
+ className={cn(
54
+ 'text-foreground web:field-sizing-content web:resize-none flex-1 py-2 font-sans text-sm outline-none',
55
+ sizeClasses[size],
56
+ classNames?.textarea,
57
+ )}
58
+ editable={editable && !disabled}
59
+ multiline
60
+ onBlur={(event) => {
61
+ setFocused(false);
62
+ onBlur?.(event);
63
+ }}
64
+ onFocus={(event) => {
65
+ setFocused(true);
66
+ onFocus?.(event);
67
+ }}
68
+ placeholderTextColorClassName="accent-muted-foreground/72"
69
+ textAlignVertical="top"
70
+ {...props}
71
+ />
72
+ </InputFrame>
73
+ );
74
+ }
@@ -0,0 +1,2 @@
1
+ export { Checkbox } from '../components/form/Checkbox';
2
+ export type { CheckboxProps } from '../components/form/Checkbox';
@@ -0,0 +1,2 @@
1
+ export { Flex } from '../components/Flex';
2
+ export type { FlexProps } from '../components/Flex';
@@ -0,0 +1,13 @@
1
+ export { Form } from '../components/form/Form';
2
+ export type {
3
+ FormCheckboxProps,
4
+ FormDescriptionProps,
5
+ FormErrorProps,
6
+ FormFieldProps,
7
+ FormInputGroupProps,
8
+ FormInputProps,
9
+ FormLabelProps,
10
+ FormProps,
11
+ FormRadioGroupProps,
12
+ FormTextareaProps,
13
+ } from '../components/form/Form';
@@ -0,0 +1,2 @@
1
+ export { Grid } from '../components/Grid';
2
+ export type { GridProps } from '../components/Grid';
@@ -0,0 +1,8 @@
1
+ export { InputGroup } from '../components/form/InputGroup';
2
+ export type {
3
+ InputGroupAddonProps,
4
+ InputGroupButtonProps,
5
+ InputGroupInputProps,
6
+ InputGroupProps,
7
+ InputGroupTextProps,
8
+ } from '../components/form/InputGroup';
@@ -0,0 +1,2 @@
1
+ export { Input } from '../components/form/Input';
2
+ export type { InputProps } from '../components/form/Input';
@@ -0,0 +1,5 @@
1
+ export { RadioGroup } from '../components/form/RadioGroup';
2
+ export type {
3
+ RadioGroupItemProps,
4
+ RadioGroupProps,
5
+ } from '../components/form/RadioGroup';
@@ -0,0 +1,2 @@
1
+ export { Separator } from '../components/Separator';
2
+ export type { SeparatorProps } from '../components/Separator';
@@ -0,0 +1,2 @@
1
+ export { Spinner } from '../components/Spinner';
2
+ export type { SpinnerProps } from '../components/Spinner';
@@ -0,0 +1,2 @@
1
+ export { Textarea } from '../components/form/Textarea';
2
+ export type { TextareaProps } from '../components/form/Textarea';
@@ -0,0 +1,2 @@
1
+ export { XStack } from '../components/XStack';
2
+ export type { XStackProps } from '../components/XStack';
@@ -0,0 +1,2 @@
1
+ export { YStack } from '../components/YStack';
2
+ export type { YStackProps } from '../components/YStack';
@@ -0,0 +1,244 @@
1
+ /// <reference types="node" />
2
+ import {
3
+ render,
4
+ renderHook,
5
+ type RenderHookOptions,
6
+ screen,
7
+ } from '@testing-library/react-native';
8
+ import { execFileSync } from 'node:child_process';
9
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { useForm } from 'react-hook-form';
12
+ import { type StyleProp, StyleSheet, type TextStyle } from 'react-native';
13
+ import { ScopedVariables, useResolveClassNames } from 'uniwind';
14
+
15
+ import { Badge } from './components/Badge';
16
+ import { Button } from './components/Button';
17
+ import { Card } from './components/Card';
18
+ import { Checkbox } from './components/form/Checkbox';
19
+ import { Form } from './components/form/Form';
20
+ import { Input } from './components/form/Input';
21
+ import { InputGroup } from './components/form/InputGroup';
22
+ import { RadioGroup } from './components/form/RadioGroup';
23
+ import { Textarea } from './components/form/Textarea';
24
+ import { Text } from './components/Text';
25
+
26
+ // Resolves the components' classes the way an app does: `styles.css`
27
+ // compiled by Uniwind for iOS, followed by the app's own CSS, then loaded
28
+ // into Uniwind's runtime.
29
+
30
+ type HostElement = { props: { className?: string; style?: TextStyle } };
31
+
32
+ /** The family the element renders with: its classes, then its `style`. */
33
+ async function fontFamily(
34
+ element: HostElement,
35
+ options?: RenderHookOptions<unknown>,
36
+ ) {
37
+ const { result } = await renderHook(
38
+ () => useResolveClassNames(element.props.className ?? ''),
39
+ options,
40
+ );
41
+ // Uniwind's components render `style={[classNameStyle, props.style]}`.
42
+ const style: StyleProp<TextStyle> = [result.current, element.props.style];
43
+ return StyleSheet.flatten(style)?.fontFamily;
44
+ }
45
+
46
+ function FormFields() {
47
+ const form = useForm({ defaultValues: { email: '', name: '' } });
48
+
49
+ return (
50
+ <Form form={form} onSubmit={() => {}}>
51
+ <Form.Field
52
+ description="Field description"
53
+ label="Field label"
54
+ name="name"
55
+ >
56
+ <Form.Input placeholder="Form input" />
57
+ </Form.Field>
58
+ <Form.Field error="Field error" name="email">
59
+ <Form.Input />
60
+ </Form.Field>
61
+ </Form>
62
+ );
63
+ }
64
+
65
+ /** Compiles `styles.css` and `css` as an app's `global.css` and loads them. */
66
+ function loadStyles(css: string) {
67
+ // Inside the package, so the compiled module's `require('uniwind')` resolves.
68
+ const cache = join(__dirname, '../node_modules/.cache');
69
+ mkdirSync(cache, { recursive: true });
70
+ const dir = mkdtempSync(join(cache, 'pickle-fonts-'));
71
+ const file = join(dir, 'global.css');
72
+ writeFileSync(file, `@import '${join(__dirname, 'styles.css')}';\n${css}`);
73
+ try {
74
+ const code = execFileSync(
75
+ process.execPath,
76
+ [join(__dirname, '../test/compile-uniwind.mjs'), file, 'ios'],
77
+ { encoding: 'utf8' },
78
+ );
79
+ writeFileSync(join(dir, 'styles.js'), code);
80
+ jest.requireActual(join(dir, 'styles.js'));
81
+ } finally {
82
+ rmSync(dir, { force: true, recursive: true });
83
+ }
84
+ }
85
+
86
+ async function renderComponents() {
87
+ await render(
88
+ <>
89
+ <Text>Body text</Text>
90
+ <Text variant="h1">Heading 1</Text>
91
+ <Text variant="h2">Heading 2</Text>
92
+ <Text variant="h3">Heading 3</Text>
93
+ <Text variant="code">pnpm test</Text>
94
+ <Card>
95
+ <Card.Header>
96
+ <Card.Title>Card title</Card.Title>
97
+ <Card.Description>Card description</Card.Description>
98
+ </Card.Header>
99
+ </Card>
100
+ <Badge>Badge label</Badge>
101
+ <Button>Button label</Button>
102
+ <Input placeholder="Input" />
103
+ <Textarea placeholder="Textarea" />
104
+ <InputGroup>
105
+ <InputGroup.Addon>@</InputGroup.Addon>
106
+ <InputGroup.Input placeholder="Group input" />
107
+ <InputGroup.Addon align="inline-end">
108
+ <InputGroup.Text>.com</InputGroup.Text>
109
+ </InputGroup.Addon>
110
+ </InputGroup>
111
+ <Checkbox label="Checkbox label" />
112
+ <RadioGroup>
113
+ <RadioGroup.Item label="Radio label" value="a" />
114
+ </RadioGroup>
115
+ <FormFields />
116
+ </>,
117
+ );
118
+ }
119
+
120
+ const SANS_TEXT = [
121
+ 'Body text',
122
+ 'Heading 3',
123
+ 'Card description',
124
+ 'Badge label',
125
+ 'Button label',
126
+ '@',
127
+ '.com',
128
+ 'Checkbox label',
129
+ 'Radio label',
130
+ 'Field label',
131
+ 'Field description',
132
+ 'Field error',
133
+ ];
134
+ const HEADING_TEXT = ['Heading 1', 'Heading 2', 'Card title'];
135
+ const FIELDS = ['Input', 'Textarea', 'Group input', 'Form input'];
136
+
137
+ function elements(names: string[]) {
138
+ return names.map((name) => ({
139
+ element: FIELDS.includes(name)
140
+ ? screen.getByPlaceholderText(name)
141
+ : screen.getByText(name),
142
+ name,
143
+ }));
144
+ }
145
+
146
+ function every(names: string[], value: unknown) {
147
+ return Object.fromEntries(names.map((name) => [name, value]));
148
+ }
149
+
150
+ /** Finds every element first: `renderHook` replaces what `screen` queries. */
151
+ async function fontFamilies(
152
+ names: string[],
153
+ options?: RenderHookOptions<unknown>,
154
+ ) {
155
+ const families: Record<string, unknown> = {};
156
+ for (const { element, name } of elements(names)) {
157
+ families[name] = await fontFamily(element, options);
158
+ }
159
+ return families;
160
+ }
161
+
162
+ const ALL = [...SANS_TEXT, ...HEADING_TEXT, ...FIELDS];
163
+
164
+ describe('fonts', () => {
165
+ describe('when the app sets no font', () => {
166
+ beforeAll(() => loadStyles(''), 60_000);
167
+
168
+ it('sets no family, so text keeps the system font', async () => {
169
+ await renderComponents();
170
+
171
+ expect(await fontFamilies(ALL)).toEqual(every(ALL, undefined));
172
+ });
173
+
174
+ it('keeps the monospace font for code', async () => {
175
+ await renderComponents();
176
+
177
+ expect(await fontFamily(screen.getByText('pnpm test'))).toBe('Menlo');
178
+ });
179
+ });
180
+
181
+ describe('when the app sets --font-sans', () => {
182
+ beforeAll(() => loadStyles("@theme { --font-sans: 'Nunito'; }"), 60_000);
183
+
184
+ it('applies it to every component, headings included', async () => {
185
+ await renderComponents();
186
+
187
+ expect(await fontFamilies(ALL)).toEqual(every(ALL, 'Nunito'));
188
+ });
189
+
190
+ it('keeps the monospace font for code', async () => {
191
+ await renderComponents();
192
+
193
+ expect(await fontFamily(screen.getByText('pnpm test'))).toBe('Menlo');
194
+ });
195
+ });
196
+
197
+ describe('when the app sets --font-sans and --font-heading', () => {
198
+ beforeAll(
199
+ () =>
200
+ loadStyles("@theme { --font-sans: 'Nunito'; --font-heading: 'Lora'; }"),
201
+ 60_000,
202
+ );
203
+
204
+ it('applies --font-heading to h1, h2 and Card.Title', async () => {
205
+ await renderComponents();
206
+
207
+ expect(await fontFamilies(ALL)).toEqual({
208
+ ...every([...SANS_TEXT, ...FIELDS], 'Nunito'),
209
+ ...every(HEADING_TEXT, 'Lora'),
210
+ });
211
+ });
212
+ });
213
+
214
+ describe('when the app sets --font-sans in a :root rule', () => {
215
+ beforeAll(
216
+ () => loadStyles("@layer theme { :root { --font-sans: 'Nunito'; } }"),
217
+ 60_000,
218
+ );
219
+
220
+ it('applies it', async () => {
221
+ await renderComponents();
222
+
223
+ expect(await fontFamilies(ALL)).toEqual(every(ALL, 'Nunito'));
224
+ });
225
+ });
226
+
227
+ describe('when the app sets --font-sans at runtime', () => {
228
+ beforeAll(() => loadStyles(''), 60_000);
229
+
230
+ it('applies it inside ScopedVariables, headings included', async () => {
231
+ await renderComponents();
232
+
233
+ expect(
234
+ await fontFamilies(ALL, {
235
+ wrapper: ({ children }) => (
236
+ <ScopedVariables variables={{ '--font-sans': 'Nunito' }}>
237
+ {children}
238
+ </ScopedVariables>
239
+ ),
240
+ }),
241
+ ).toEqual(every(ALL, 'Nunito'));
242
+ });
243
+ });
244
+ });
package/src/index.ts CHANGED
@@ -3,7 +3,19 @@
3
3
  export * from './entries/badge';
4
4
  export * from './entries/button';
5
5
  export * from './entries/card';
6
+ export * from './entries/checkbox';
6
7
  export * from './entries/cn';
8
+ export * from './entries/flex';
9
+ export * from './entries/form';
10
+ export * from './entries/grid';
11
+ export * from './entries/input';
12
+ export * from './entries/input-group';
13
+ export * from './entries/radio-group';
14
+ export * from './entries/separator';
15
+ export * from './entries/spinner';
7
16
  export * from './entries/text';
17
+ export * from './entries/textarea';
8
18
  export * from './entries/theme';
9
19
  export * from './entries/use-color-token';
20
+ export * from './entries/x-stack';
21
+ export * from './entries/y-stack';
package/src/styles.css CHANGED
@@ -10,3 +10,15 @@
10
10
  @import 'uniwind';
11
11
  @import '@pickle-ui/tokens/native.css';
12
12
  @import './base.css';
13
+
14
+ /*
15
+ * The tokens' `--font-*` variables read the app's own on the web, where an
16
+ * unset one falls back to the inherited font. Uniwind resolves them to
17
+ * themselves and overflows the stack, so take the native defaults from
18
+ * `base.css` again. Declare your own `--font-*` after importing this file.
19
+ */
20
+ @theme {
21
+ --font-sans: var(--pickle-no-font);
22
+ --font-heading: var(--font-sans);
23
+ --font-mono: var(--pickle-no-font);
24
+ }
@@ -1,9 +0,0 @@
1
- /**
2
- * The web `Spinner`'s look on native: a three-quarter ring turning once a
3
- * second. Color it with a `border-*` class. Decorative: the control showing
4
- * it reports the busy state.
5
- */
6
- export declare function Spinner({ className }: {
7
- className?: string;
8
- }): import("react").JSX.Element;
9
- //# sourceMappingURL=Spinner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Spinner.d.ts","sourceRoot":"","sources":["../../../src/components/_internal/Spinner.tsx"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAsC5D"}
@@ -1,31 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
- import { Animated, Easing, Platform, View } from 'react-native';
4
- import { cn } from '../../lib/cn';
5
- /**
6
- * The web `Spinner`'s look on native: a three-quarter ring turning once a
7
- * second. Color it with a `border-*` class. Decorative: the control showing
8
- * it reports the busy state.
9
- */
10
- export function Spinner({ className }) {
11
- const [turn] = useState(() => new Animated.Value(0));
12
- useEffect(() => {
13
- const loop = Animated.loop(Animated.timing(turn, {
14
- duration: 1000,
15
- easing: Easing.linear,
16
- toValue: 1,
17
- // react-native-web has no native driver and warns when asked for it.
18
- useNativeDriver: Platform.OS !== 'web',
19
- }));
20
- loop.start();
21
- return () => loop.stop();
22
- }, [turn]);
23
- const rotate = turn.interpolate({
24
- inputRange: [0, 1],
25
- outputRange: ['0deg', '360deg'],
26
- });
27
- return (_jsx(Animated.View, { accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: { transform: [{ rotate }] }, children: _jsx(View, { className: cn('border-foreground size-3 rounded-full border-[1.5px] opacity-80', className,
28
- // After the color, which would otherwise override the open side.
29
- 'border-t-transparent') }) }));
30
- }
31
- //# sourceMappingURL=Spinner.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Spinner.js","sourceRoot":"","sources":["../../../src/components/_internal/Spinner.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAElC;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,EAAE,SAAS,EAA0B;IAC3D,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CACxB,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE;YACpB,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,CAAC;YACV,qEAAqE;YACrE,eAAe,EAAE,QAAQ,CAAC,EAAE,KAAK,KAAK;SACvC,CAAC,CACH,CAAC;QACF,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC;QAC9B,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAClB,WAAW,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;KAChC,CAAC,CAAC;IAEH,OAAO,CACL,KAAC,QAAQ,CAAC,IAAI,IACZ,2BAA2B,QAC3B,yBAAyB,EAAC,qBAAqB,EAC/C,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,YAElC,KAAC,IAAI,IACH,SAAS,EAAE,EAAE,CACX,iEAAiE,EACjE,SAAS;YACT,iEAAiE;YACjE,sBAAsB,CACvB,GACD,GACY,CACjB,CAAC;AACJ,CAAC"}
@@ -1,49 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { Animated, Easing, Platform, View } from 'react-native';
3
-
4
- import { cn } from '../../lib/cn';
5
-
6
- /**
7
- * The web `Spinner`'s look on native: a three-quarter ring turning once a
8
- * second. Color it with a `border-*` class. Decorative: the control showing
9
- * it reports the busy state.
10
- */
11
- export function Spinner({ className }: { className?: string }) {
12
- const [turn] = useState(() => new Animated.Value(0));
13
-
14
- useEffect(() => {
15
- const loop = Animated.loop(
16
- Animated.timing(turn, {
17
- duration: 1000,
18
- easing: Easing.linear,
19
- toValue: 1,
20
- // react-native-web has no native driver and warns when asked for it.
21
- useNativeDriver: Platform.OS !== 'web',
22
- }),
23
- );
24
- loop.start();
25
- return () => loop.stop();
26
- }, [turn]);
27
-
28
- const rotate = turn.interpolate({
29
- inputRange: [0, 1],
30
- outputRange: ['0deg', '360deg'],
31
- });
32
-
33
- return (
34
- <Animated.View
35
- accessibilityElementsHidden
36
- importantForAccessibility="no-hide-descendants"
37
- style={{ transform: [{ rotate }] }}
38
- >
39
- <View
40
- className={cn(
41
- 'border-foreground size-3 rounded-full border-[1.5px] opacity-80',
42
- className,
43
- // After the color, which would otherwise override the open side.
44
- 'border-t-transparent',
45
- )}
46
- />
47
- </Animated.View>
48
- );
49
- }