@kanso-labs/react-native-kanso-ui 0.7.0 → 0.9.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 (150) hide show
  1. package/README.md +37 -0
  2. package/dist/badge/index.js +9 -68
  3. package/dist/badge/index.js.map +1 -1
  4. package/dist/button/index.d.ts +2 -1
  5. package/dist/button/index.js +1 -1
  6. package/dist/button/index.js.map +1 -1
  7. package/dist/button/palettes.d.ts +2 -0
  8. package/dist/button/pending.js +17 -21
  9. package/dist/button/pending.js.map +1 -1
  10. package/dist/calendar/index.js +1 -1
  11. package/dist/components/autocomplete/index.js +28 -26
  12. package/dist/components/autocomplete/index.js.map +1 -1
  13. package/dist/components/avatar/index.d.ts +2 -2
  14. package/dist/components/avatar/index.js +2 -2
  15. package/dist/components/avatar/index.js.map +1 -1
  16. package/dist/components/badge/index.d.ts +48 -0
  17. package/dist/components/badge/index.js +122 -0
  18. package/dist/components/badge/index.js.map +1 -0
  19. package/dist/{badge → components/badge}/styles.js +10 -6
  20. package/dist/components/badge/styles.js.map +1 -0
  21. package/dist/components/breadcrumbs/index.d.ts +66 -0
  22. package/dist/components/breadcrumbs/index.js +285 -0
  23. package/dist/components/breadcrumbs/index.js.map +1 -0
  24. package/dist/components/calendar/index.d.ts +1 -1
  25. package/dist/components/calendar/index.js +77 -38
  26. package/dist/components/calendar/index.js.map +1 -1
  27. package/dist/components/checkbox/index.d.ts +7 -1
  28. package/dist/components/checkbox/index.js +183 -138
  29. package/dist/components/checkbox/index.js.map +1 -1
  30. package/dist/components/checkbox-group/index.d.ts +93 -0
  31. package/dist/components/checkbox-group/index.js +306 -0
  32. package/dist/components/checkbox-group/index.js.map +1 -0
  33. package/dist/components/chip-group/index.d.ts +4 -3
  34. package/dist/components/chip-group/index.js +47 -45
  35. package/dist/components/chip-group/index.js.map +1 -1
  36. package/dist/components/code/index.d.ts +25 -0
  37. package/dist/components/code/index.js +87 -0
  38. package/dist/components/code/index.js.map +1 -0
  39. package/dist/components/container/index.d.ts +47 -0
  40. package/dist/components/container/index.js +85 -0
  41. package/dist/components/container/index.js.map +1 -0
  42. package/dist/components/copy-field/index.d.ts +5 -5
  43. package/dist/components/copy-field/index.js +30 -28
  44. package/dist/components/copy-field/index.js.map +1 -1
  45. package/dist/components/currency/index.d.ts +2 -2
  46. package/dist/components/currency/index.js +3 -1
  47. package/dist/components/currency/index.js.map +1 -1
  48. package/dist/components/fab/index.d.ts +80 -0
  49. package/dist/components/fab/index.js +231 -0
  50. package/dist/components/fab/index.js.map +1 -0
  51. package/dist/components/fab-menu/index.d.ts +120 -0
  52. package/dist/components/fab-menu/index.js +640 -0
  53. package/dist/components/fab-menu/index.js.map +1 -0
  54. package/dist/components/feed/index.d.ts +57 -0
  55. package/dist/components/feed/index.js +167 -0
  56. package/dist/components/feed/index.js.map +1 -0
  57. package/dist/components/keycap/index.d.ts +27 -0
  58. package/dist/components/keycap/index.js +136 -0
  59. package/dist/components/keycap/index.js.map +1 -0
  60. package/dist/components/list/index.d.ts +6 -5
  61. package/dist/components/list/index.js +97 -106
  62. package/dist/components/list/index.js.map +1 -1
  63. package/dist/components/menu/index.d.ts +4 -3
  64. package/dist/components/menu/index.js +33 -40
  65. package/dist/components/menu/index.js.map +1 -1
  66. package/dist/components/meter/index.d.ts +2 -1
  67. package/dist/components/meter/index.js +9 -7
  68. package/dist/components/meter/index.js.map +1 -1
  69. package/dist/components/number-field/index.d.ts +7 -3
  70. package/dist/components/number-field/index.js +214 -192
  71. package/dist/components/number-field/index.js.map +1 -1
  72. package/dist/components/product-icon/index.d.ts +2 -2
  73. package/dist/components/product-icon/index.js +2 -2
  74. package/dist/components/product-icon/index.js.map +1 -1
  75. package/dist/components/progress-indicator/index.d.ts +2 -1
  76. package/dist/components/progress-indicator/index.js +15 -13
  77. package/dist/components/progress-indicator/index.js.map +1 -1
  78. package/dist/components/search-field/index.d.ts +3 -3
  79. package/dist/components/search-field/index.js +105 -104
  80. package/dist/components/search-field/index.js.map +1 -1
  81. package/dist/components/separator/index.d.ts +3 -2
  82. package/dist/components/separator/index.js +29 -26
  83. package/dist/components/separator/index.js.map +1 -1
  84. package/dist/components/sheet/index.d.ts +2 -2
  85. package/dist/components/sheet/index.js +22 -26
  86. package/dist/components/sheet/index.js.map +1 -1
  87. package/dist/components/snackbar/index.d.ts +3 -3
  88. package/dist/components/snackbar/index.js +36 -34
  89. package/dist/components/snackbar/index.js.map +1 -1
  90. package/dist/components/supporting-pane/index.d.ts +44 -0
  91. package/dist/components/supporting-pane/index.js +109 -0
  92. package/dist/components/supporting-pane/index.js.map +1 -0
  93. package/dist/components/tag/index.d.ts +58 -0
  94. package/dist/components/tag/index.js +205 -0
  95. package/dist/components/tag/index.js.map +1 -0
  96. package/dist/components/text/index.d.ts +1 -1
  97. package/dist/components/text/index.js +49 -21
  98. package/dist/components/text/index.js.map +1 -1
  99. package/dist/components/text-area/index.d.ts +145 -0
  100. package/dist/components/text-area/index.js +231 -0
  101. package/dist/components/text-area/index.js.map +1 -0
  102. package/dist/components/text-field/index.d.ts +6 -3
  103. package/dist/components/text-field/index.js +3 -0
  104. package/dist/components/text-field/index.js.map +1 -1
  105. package/dist/components/toolbar/index.d.ts +53 -0
  106. package/dist/components/toolbar/index.js +185 -0
  107. package/dist/components/toolbar/index.js.map +1 -0
  108. package/dist/control/group.js +23 -0
  109. package/dist/control/group.js.map +1 -0
  110. package/dist/fab/index.d.ts +19 -0
  111. package/dist/fab/index.js +147 -0
  112. package/dist/fab/index.js.map +1 -0
  113. package/dist/field/index.js +123 -79
  114. package/dist/field/index.js.map +1 -1
  115. package/dist/field/styles.js +18 -0
  116. package/dist/field/styles.js.map +1 -1
  117. package/dist/form/index.d.ts +1 -2
  118. package/dist/form/index.js +1 -1
  119. package/dist/form/index.js.map +1 -1
  120. package/dist/i18n/index.js +12 -1
  121. package/dist/i18n/index.js.map +1 -1
  122. package/dist/i18n/messages.js +367 -0
  123. package/dist/i18n/messages.js.map +1 -1
  124. package/dist/i18n/provider.d.ts +53 -0
  125. package/dist/i18n/provider.js +96 -0
  126. package/dist/i18n/provider.js.map +1 -0
  127. package/dist/index.d.ts +19 -4
  128. package/dist/index.js +17 -3
  129. package/dist/indicator/index.js +1 -2
  130. package/dist/indicator/index.js.map +1 -1
  131. package/dist/mark/index.js +77 -117
  132. package/dist/mark/index.js.map +1 -1
  133. package/dist/mark/initials.js.map +1 -1
  134. package/dist/modal/index.js +59 -46
  135. package/dist/modal/index.js.map +1 -1
  136. package/dist/navigation/index.js +8 -5
  137. package/dist/navigation/index.js.map +1 -1
  138. package/dist/number/count.js +15 -0
  139. package/dist/number/count.js.map +1 -0
  140. package/dist/number/locale.js.map +1 -1
  141. package/dist/overlay/host.js +30 -21
  142. package/dist/overlay/host.js.map +1 -1
  143. package/dist/overlay/placement.js +3 -1
  144. package/dist/overlay/placement.js.map +1 -1
  145. package/dist/separator/index.js +7 -0
  146. package/dist/separator/index.js.map +1 -0
  147. package/dist/typography/size.js +14 -0
  148. package/dist/typography/size.js.map +1 -0
  149. package/package.json +1 -1
  150. package/dist/badge/styles.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["ComponentRef","ReactNode","Ref","RefObject","GestureResponderEvent","HostInstance","PressableProps","StyleProp","TextStyle","ViewStyle","AnimatedStyle","Key","Node","TreeProps","TreeState","useCallback","useEffect","useMemo","useRef","useState","I18nManager","Text","NativeText","Pressable","StyleSheet","View","Animated","Easing","useAnimatedStyle","useReducedMotion","useSharedValue","withTiming","useMenuTriggerState","useTreeState","FabSize","FabTone","IconStyle","AnimatedPressable","buttonStyles","FAB_ICON_SIZES","fabColorsOf","fabCornersOf","fabPalettesOf","fabShadowOf","fabSizesOf","CloseGlyph","IconStyleContext","Overlay","useAnchoredPlacement","DismissLayer","useBackToClose","useFocusOnPlaced","useReturnFocus","usePressFeedback","refsOf","useTheme","composeStyles","themedStyles","useTypeRole","CLOSE_SIZE","RISE_FROM","HAS_MENU","CLOSE","FabMenuItemProps","Omit","children","icon","id","isDisabled","onAction","ref","style","textValue","FabMenuProps","defaultOpen","isOpen","key","onOpenChange","size","testID","tone","styles","create","base","alignItems","flexShrink","justifyContent","label","stylesOf","radii","sizing","spacing","action","columnGap","sm","flexDirection","minHeight","controlLg","minWidth","paddingHorizontal","xl","corner","borderRadius","pill","menu","rowGap","xs","FabMenu","t0","$","_c","t1","t2","undefined","theme","t3","state","open","fab","anchor","useCloseButtonAnchor","content","feedback","color","morph","useCloseMorph","t4","toggle","t5","close","T0","t7","t8","handlers","t9","t10","container","ring","t11","clip","t12","layer","T1","T2","t14","iconStyleOf","t15","t16","t17","t13","t18","t6","t19","t20","FabMenuItem","_props","getCollectionNode","props","hasChildNodes","rendered","type","FabMenuAction","activate","node","setFocusTarget","own","role","onBlur","onFocus","onHoverIn","onHoverOut","onPress","onPressIn","onPressOut","_id","_isDisabled","_onAction","_textValue","palettes","colors","disabled","tonal","corners","pressThen","md","FabMenuPanel","align","side","sideOffset","placement","rise","useRiseIn","placed","focusTarget","firstActionKey","focusKey","onLayout","collection","selectionManager","map","node_0","getKeys","event","edge","current","measureInWindow","callback","x","y","width","height","isRTL","reduceMotion","motion","fill","timing","duration","durationMedium1","easing","bezier","easingEmphasized","backgroundColor","shown","set","durationShort3","easingEmphasizedDecelerate","progress","get","opacity","transform","translateY","Item","_c","NativeText"],"sources":["../../../src/components/fab-menu/index.tsx"],"sourcesContent":["import type { ComponentRef, ReactNode, Ref, RefObject } from 'react'\nimport type {\n GestureResponderEvent,\n HostInstance,\n PressableProps,\n StyleProp,\n TextStyle,\n ViewStyle,\n} from 'react-native'\nimport type { AnimatedStyle } from 'react-native-reanimated'\nimport type { Key, Node } from 'react-stately'\nimport type { TreeProps, TreeState } from 'react-stately/useTreeState'\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n I18nManager,\n Text as NativeText,\n Pressable,\n StyleSheet,\n View,\n} from 'react-native'\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useReducedMotion,\n useSharedValue,\n withTiming,\n} from 'react-native-reanimated'\nimport { useMenuTriggerState } from 'react-stately/useMenuTriggerState'\nimport { useTreeState } from 'react-stately/useTreeState'\n\nimport type { FabSize, FabTone } from '../../fab'\nimport type { IconStyle } from '../../glyphs/icon-style'\n\nimport { AnimatedPressable, buttonStyles } from '../../button'\nimport {\n FAB_ICON_SIZES,\n fabColorsOf,\n fabCornersOf,\n fabPalettesOf,\n fabShadowOf,\n fabSizesOf,\n} from '../../fab'\nimport { CloseGlyph } from '../../glyphs'\nimport { IconStyleContext } from '../../glyphs/icon-style'\nimport { Overlay, useAnchoredPlacement } from '../../overlay'\nimport {\n DismissLayer,\n useBackToClose,\n useFocusOnPlaced,\n useReturnFocus,\n} from '../../overlay/panel'\nimport { usePressFeedback } from '../../press'\nimport { refsOf } from '../../ref'\nimport { useTheme } from '../../theme/scope'\nimport { composeStyles, themedStyles } from '../../theme/styles'\nimport { useTypeRole } from '../../typography/roles'\n\n// kanso-ui's FabMenu: the FAB menu page's menu, a FAB that opens two to six\n// related actions floating above it, in place of a speed dial. Open, the FAB\n// becomes the menu's close button, the page's 56dp circle in the tone itself\n// with a 20dp close mark, and the actions stand in a column 8dp above it,\n// lined up on its trailing edge: each a 56dp pill on the tone's container\n// pair with 24dp at either end, a 24dp icon 8dp before a title-medium label,\n// 4dp apart and raised on the level 3 shadow, lifted to level 4 under a\n// hovering pointer as Fab is. The three tones are the page's three colour\n// sets: the close button takes the tone and the actions its container, so\n// the two contrast as the page draws them.\n//\n// The FAB is the menu's own rather than a child the call site places, as on\n// web, because what it draws depends on whether the menu is open. Closed, it\n// is Fab at any of its sizes, from src/fab's metrics and colours. As the menu\n// opens, its width, height, corner and container ease to the close button's\n// over the medium1 duration on the emphasised curve, and back as it closes.\n// The icon is swapped for the close mark at once, in the colour drawn on the\n// container it is easing to. Under reduced motion the change is made at once.\n//\n// The actions are a collection, built by React Stately's tree state as\n// Menu's items are, so `FabMenu.Item` renders nothing on its own. They are\n// drawn in the overlay host above the screen with src/overlay's anchored\n// placement, on the FAB's top side at its end, and rise 8dp into place over\n// the short3 duration on the emphasised decelerating curve, at once under\n// reduced motion. As on web, they leave at once.\n//\n// They are placed against the close button rather than against the FAB that\n// opens them. A FAB opening a menu sits in its screen's bottom trailing\n// corner, so the 80 and 96dp sizes shrink into that corner, and the actions\n// stand 8dp above the 56dp circle they shrink to rather than above where the\n// larger FAB was.\n//\n// A press outside the actions lands on the dismiss layer under them and\n// closes the menu, as does a press on the close button, which that layer\n// covers. Android's back gesture and VoiceOver's escape gesture close it\n// too. A press on an action runs it and closes the menu. A screen reader is\n// sent to the first action that takes a press once they are placed, and back\n// to the FAB once they have gone, and the FAB reports whether its menu is\n// expanded. Web's arrow keys between the actions need a hardware keyboard,\n// which this pass leaves out, as Menu does. Web's forced-colours rules have\n// no counterpart on iOS or Android and are not drawn.\n\n// The close mark at the page's 20dp.\nconst CLOSE_SIZE = 20\n\n// How far below its place the column starts as it arrives, web's keyframes'.\nconst RISE_FROM = 8\n\n// React Native Web writes `aria-haspopup` onto the element, which React\n// Native's types do not name.\nconst HAS_MENU: { readonly 'aria-haspopup': 'menu' } = {\n 'aria-haspopup': 'menu',\n}\n\n// Hoisted so it is one stable element per render, which is what react-perf's\n// no-jsx-as-prop is after.\nconst CLOSE = <CloseGlyph />\n\ntype FabMenuItemProps = Omit<\n PressableProps,\n 'children' | 'disabled' | 'id' | 'role' | 'style'\n> & {\n /** The action's name, drawn as its label. */\n readonly children: ReactNode\n /**\n * An icon before the label, which takes the label's colour at the page's\n * 24dp through `useIconStyle()`.\n */\n readonly icon?: ReactNode\n /**\n * The key the action is reported by. Give one to every action the call\n * site needs to tell apart.\n */\n readonly id?: Key\n /**\n * Draws the action disabled, without its shadow, and takes no presses on\n * it.\n * @default false\n */\n readonly isDisabled?: boolean\n /**\n * Called when a press runs the action, in place of the menu's `onAction`\n * for this one.\n */\n readonly onAction?: () => void\n readonly ref?: Ref<HostInstance>\n readonly style?: StyleProp<ViewStyle>\n /**\n * The action's text, read off `children` when they are a plain string, so\n * an action whose label is an element passes it.\n */\n readonly textValue?: string\n}\n\ntype FabMenuProps = {\n /**\n * What the FAB does, in words: an icon alone has no name, and the menu\n * takes its name from the FAB that opens it.\n */\n readonly 'aria-label': string\n /** The actions, as `FabMenu.Item`s. The page asks for two to six. */\n readonly children: ReactNode\n /**\n * Whether the menu starts open, when it keeps its own state.\n * @default false\n */\n readonly defaultOpen?: boolean\n /**\n * The FAB's icon while the menu is closed. The FAB hands it its colour and\n * the size's icon size through `useIconStyle()`.\n */\n readonly icon: ReactNode\n /** Whether the menu is open, when the call site holds the state. */\n readonly isOpen?: boolean\n /** Called with an action's `id` when a press runs it. */\n readonly onAction?: (key: Key) => void\n /** Called with the new state as the menu opens or closes. */\n readonly onOpenChange?: (isOpen: boolean) => void\n readonly ref?: Ref<ComponentRef<typeof View>>\n /**\n * The FAB's size while the menu is closed, as Fab's: `md` 56dp, `lg` 80dp\n * or `xl` 96dp. Open, it is the close button's 56dp whatever it was.\n * @default 'md'\n */\n readonly size?: FabSize\n readonly style?: StyleProp<ViewStyle>\n readonly testID?: string\n /**\n * The page's three colour sets: the close button takes the tone and the\n * actions its container pair.\n * @default 'primary'\n */\n readonly tone?: FabTone\n}\n\nconst styles = StyleSheet.create({\n // The icon sits in the middle of the FAB, and a row never squeezes it.\n base: {\n alignItems: 'center',\n flexShrink: 0,\n justifyContent: 'center',\n },\n icon: {\n alignItems: 'center',\n flexShrink: 0,\n justifyContent: 'center',\n },\n label: {\n flexShrink: 1,\n },\n})\n\nconst stylesOf = themedStyles(({ radii, sizing, spacing }) =>\n StyleSheet.create({\n // An action: a pill as tall as the close button, its icon 8dp before the\n // label, 24dp in from either end. Its height is a floor, so a label the\n // device's text size enlarges grows the pill rather than spilling out.\n action: {\n alignItems: 'center',\n columnGap: spacing.sm,\n flexDirection: 'row',\n minHeight: sizing.controlLg,\n minWidth: sizing.controlLg,\n paddingHorizontal: spacing.xl,\n },\n corner: {\n borderRadius: radii.pill,\n },\n // The actions in a column on the FAB's trailing edge, 4dp apart.\n menu: {\n alignItems: 'flex-end',\n rowGap: spacing.xs,\n },\n }),\n)\n\n/**\n * A FAB that opens a column of related actions above it, and closes them\n * again. The actions are `FabMenu.Item`s.\n *\n * ```tsx\n * <FabMenu aria-label=\"Label\" icon={<PlusIcon />}>\n * <FabMenu.Item icon={<FirstIcon />} id=\"first\">\n * First item\n * </FabMenu.Item>\n * <FabMenu.Item icon={<SecondIcon />} id=\"second\">\n * Second item\n * </FabMenu.Item>\n * </FabMenu>\n * ```\n *\n * `style`, `ref` and `testID` land on the FAB, which is what a call site\n * positions over its screen.\n */\nfunction FabMenu({\n 'aria-label': label,\n children,\n defaultOpen,\n icon,\n isOpen,\n onAction,\n onOpenChange,\n ref,\n size = 'md',\n style,\n testID,\n tone = 'primary',\n}: FabMenuProps) {\n const theme = useTheme()\n const state = useMenuTriggerState({ defaultOpen, isOpen, onOpenChange })\n const open = state.isOpen\n const fab = useRef<HostInstance>(null)\n const anchor = useCloseButtonAnchor(fab, theme.sizing.controlLg)\n const { content } = fabColorsOf(theme)[open ? 'filled' : 'tonal'][tone]\n const feedback = usePressFeedback({ color: content })\n const morph = useCloseMorph(open, size, tone)\n const toggle = useCallback(() => {\n state.toggle()\n }, [state])\n const close = useCallback(() => {\n state.close()\n }, [state])\n\n useReturnFocus(open, false, fab)\n\n return (\n <>\n <AnimatedPressable\n {...HAS_MENU}\n aria-expanded={open}\n aria-label={label}\n role=\"button\"\n testID={testID}\n {...feedback.handlers}\n onPress={toggle}\n ref={refsOf<HostInstance>(fab, ref)}\n style={composeStyles<AnimatedStyle<ViewStyle>>(\n styles.base,\n morph.container,\n fabShadowOf(feedback, false, theme),\n feedback.ring,\n style,\n )}\n >\n <Animated.View\n style={composeStyles<AnimatedStyle<ViewStyle>>(\n buttonStyles.clip,\n morph.clip,\n )}\n >\n {feedback.layer}\n </Animated.View>\n <View style={styles.icon}>\n <IconStyleContext\n value={iconStyleOf(\n content,\n open ? CLOSE_SIZE : FAB_ICON_SIZES[size],\n )}\n >\n {open ? CLOSE : icon}\n </IconStyleContext>\n </View>\n </AnimatedPressable>\n {open ? (\n <Overlay>\n <FabMenuPanel\n anchor={anchor}\n close={close}\n label={label}\n onAction={onAction}\n tone={tone}\n >\n {children}\n </FabMenuPanel>\n </Overlay>\n ) : null}\n </>\n )\n}\n\n/**\n * One action: a pill holding an icon and a label, on the menu's tone. A press\n * on it runs it and closes the menu.\n *\n * The menu draws it from its entry in the collection, so it renders nothing\n * on its own. `style`, `ref` and `testID` land on the pill.\n */\nfunction FabMenuItem(_props: FabMenuItemProps): null {\n return null\n}\n\n// The entry the collection holds for an action, as React Stately's own\n// `Item` gives one: keyed by the action's `id`, and carrying the props the\n// pill is drawn from.\nFabMenuItem.getCollectionNode = function* getCollectionNode(\n props: FabMenuItemProps,\n) {\n yield {\n hasChildNodes: false,\n key: props.id,\n props,\n rendered: props.children,\n textValue:\n props.textValue ??\n (typeof props.children === 'string' ? props.children : ''),\n type: 'item',\n }\n}\n\n/** An action's pill, drawn from its entry in the collection. */\nfunction FabMenuAction({\n activate,\n isDisabled,\n node,\n setFocusTarget,\n tone,\n}: {\n readonly activate: (node: Node<object>) => void\n readonly isDisabled: boolean\n readonly node: Node<object>\n /** Keeps the pill a screen reader is sent to as the menu opens. */\n readonly setFocusTarget: ((target: HostInstance | null) => void) | undefined\n readonly tone: FabTone\n}) {\n const theme = useTheme()\n const own = stylesOf(theme)\n const role = useTypeRole('titleMedium')\n // oxlint-disable-next-line typescript/no-unsafe-assignment -- a node carries the props its action was written with, which React Stately types as `any`\n const {\n children,\n icon,\n id: _id,\n isDisabled: _isDisabled,\n onAction: _onAction,\n onBlur,\n onFocus,\n onHoverIn,\n onHoverOut,\n onPress,\n onPressIn,\n onPressOut,\n ref,\n style,\n textValue: _textValue,\n ...props\n }: FabMenuItemProps = node.props\n const palettes = fabPalettesOf(theme)\n const colors = isDisabled ? palettes.disabled : palettes.tonal[tone]\n const feedback = usePressFeedback({\n color: colors.content,\n corners: own.corner,\n disabled: isDisabled,\n onBlur,\n onFocus,\n onHoverIn,\n onHoverOut,\n onPressIn,\n onPressOut,\n })\n\n return (\n <Pressable\n role=\"menuitem\"\n {...props}\n {...feedback.handlers}\n disabled={isDisabled}\n onPress={pressThen(onPress, activate, node)}\n ref={refsOf(setFocusTarget, ref)}\n style={composeStyles<ViewStyle>(\n own.action,\n own.corner,\n colors.container,\n fabShadowOf(feedback, isDisabled, theme),\n feedback.ring,\n style,\n )}\n >\n {feedback.layer}\n {icon === undefined ? null : (\n <View style={styles.icon}>\n <IconStyleContext\n value={iconStyleOf(colors.content, FAB_ICON_SIZES.md)}\n >\n {icon}\n </IconStyleContext>\n </View>\n )}\n <NativeText\n numberOfLines={1}\n style={composeStyles<TextStyle>(role, styles.label, colors.label)}\n >\n {children}\n </NativeText>\n </Pressable>\n )\n}\n\n/**\n * The open menu: the dismiss layer, then the column of actions, placed above\n * the close button's end and risen into place once it has been.\n */\nfunction FabMenuPanel({\n anchor,\n children,\n close,\n label,\n onAction,\n tone,\n}: {\n readonly anchor: RefObject<Pick<HostInstance, 'measureInWindow'>>\n readonly children: ReactNode\n readonly close: () => void\n readonly label: string\n readonly onAction: ((key: Key) => void) | undefined\n readonly tone: FabTone\n}) {\n const theme = useTheme()\n const own = stylesOf(theme)\n const state = useTreeState<object>({\n // Each action builds its own entry in the collection; see\n // `FabMenuItem.getCollectionNode`. The collection skips what is not an\n // element, as a ReactNode may be, and asks any element for its entry.\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- see above\n children: children as TreeProps<object>['children'],\n })\n const placement = useAnchoredPlacement({\n align: 'end',\n anchor,\n side: 'top',\n sideOffset: theme.spacing.sm,\n })\n const rise = useRiseIn(placement.placed)\n const [focusTarget, setFocusTarget] = useState<HostInstance | null>(null)\n const focusKey = firstActionKey(state)\n const activate = useCallback(\n (node: Node<object>) => {\n // oxlint-disable-next-line typescript/no-unsafe-assignment -- a node carries the props its action was written with, which React Stately types as `any`\n const action: FabMenuItemProps = node.props\n\n if (action.onAction === undefined) {\n onAction?.(node.key)\n } else {\n action.onAction()\n }\n close()\n },\n [close, onAction],\n )\n\n useBackToClose(close, true)\n useFocusOnPlaced(placement.placed, focusTarget)\n\n return (\n <>\n <DismissLayer onPress={close} />\n <Animated.View\n aria-label={label}\n onAccessibilityEscape={close}\n onLayout={placement.onLayout}\n role=\"menu\"\n style={composeStyles<AnimatedStyle<ViewStyle>>(\n own.menu,\n placement.style,\n rise,\n )}\n >\n {[...state.collection].map((node) => (\n <FabMenuAction\n activate={activate}\n isDisabled={state.selectionManager.isDisabled(node.key)}\n key={node.key}\n node={node}\n setFocusTarget={node.key === focusKey ? setFocusTarget : undefined}\n tone={tone}\n />\n ))}\n </Animated.View>\n </>\n )\n}\n\n/** The first action a press reaches, which a screen reader is sent to. */\nfunction firstActionKey(state: TreeState<object>) {\n for (const key of state.collection.getKeys()) {\n if (!state.selectionManager.isDisabled(key)) {\n return key\n }\n }\n\n return null\n}\n\nfunction iconStyleOf(color: string, size: number): IconStyle {\n return { color, size }\n}\n\n/**\n * The press handler of an action: the call site's own `onPress`, then the\n * action's activation. Built by a call rather than written inline at the\n * prop, which is what react-perf's no-new-function-as-prop is after.\n */\nfunction pressThen(\n onPress: PressableProps['onPress'],\n activate: (node: Node<object>) => void,\n node: Node<object>,\n) {\n return (event: GestureResponderEvent) => {\n onPress?.(event)\n activate(node)\n }\n}\n\n/**\n * The close button the FAB becomes, for the actions to be placed against: a\n * square of the close button's edge in the FAB's bottom trailing corner, read\n * off the FAB wherever in its change it is when it is measured.\n */\nfunction useCloseButtonAnchor(\n fab: RefObject<HostInstance | null>,\n edge: number,\n) {\n return useMemo<RefObject<Pick<HostInstance, 'measureInWindow'>>>(\n () => ({\n current: {\n measureInWindow: (callback) => {\n fab.current?.measureInWindow((x, y, width, height) => {\n callback(\n I18nManager.isRTL ? x : x + width - edge,\n y + height - edge,\n edge,\n edge,\n )\n })\n },\n },\n }),\n [edge, fab],\n )\n}\n\n/**\n * The FAB's change into the close button and back: its width, height,\n * corner and container eased to the state's over medium1 on the emphasised\n * curve, at once under reduced motion. The corner is returned twice, for the\n * container and for the clip its press feedback is drawn in.\n */\nfunction useCloseMorph(isOpen: boolean, size: FabSize, tone: FabTone) {\n const theme = useTheme()\n const reduceMotion = useReducedMotion()\n const { motion, sizing } = theme\n const edge = isOpen ? sizing.controlLg : fabSizesOf(theme)[size].width\n const corner = isOpen\n ? sizing.controlLg / 2\n : fabCornersOf(theme)[size].borderRadius\n const fill = fabColorsOf(theme)[isOpen ? 'filled' : 'tonal'][tone].container\n const timing = {\n duration: motion.durationMedium1,\n easing: Easing.bezier(...motion.easingEmphasized),\n }\n\n const container = useAnimatedStyle(() => {\n 'worklet'\n return {\n backgroundColor: reduceMotion ? fill : withTiming(fill, timing),\n borderRadius: reduceMotion ? corner : withTiming(corner, timing),\n height: reduceMotion ? edge : withTiming(edge, timing),\n width: reduceMotion ? edge : withTiming(edge, timing),\n }\n })\n const clip = useAnimatedStyle(() => {\n 'worklet'\n return {\n borderRadius: reduceMotion ? corner : withTiming(corner, timing),\n }\n })\n\n return { clip, container }\n}\n\n/**\n * The actions' arrival: they fade in and rise 8dp into place over short3 on\n * the emphasised decelerating curve once they have been placed, and stay\n * transparent until then. Under reduced motion they arrive at once.\n */\nfunction useRiseIn(placed: boolean) {\n const { motion } = useTheme()\n const reduceMotion = useReducedMotion()\n const shown = useSharedValue(0)\n\n useEffect(() => {\n if (!placed) {\n return\n }\n\n shown.set(\n reduceMotion\n ? 1\n : withTiming(1, {\n duration: motion.durationShort3,\n easing: Easing.bezier(...motion.easingEmphasizedDecelerate),\n }),\n )\n }, [motion, placed, reduceMotion, shown])\n\n return useAnimatedStyle(() => {\n 'worklet'\n const progress = shown.get()\n\n return {\n opacity: progress,\n transform: [{ translateY: RISE_FROM * (1 - progress) }],\n }\n })\n}\n\nFabMenu.Item = FabMenuItem\n\nexport { FabMenu, FabMenuItem }\nexport type { FabMenuItemProps, FabMenuProps }\n"],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { View, ViewProps } from "react-native";
2
+ import { ComponentRef, ReactNode, Ref } from "react";
3
+ //#region src/components/feed/index.d.ts
4
+ type FeedProps = Omit<ViewProps, 'children'> & {
5
+ /**
6
+ * The items, each laid into a cell of its own. An element is one item,
7
+ * whatever it renders, so hand the feed the items themselves rather than a
8
+ * component listing them; a fragment counts as the items it holds. A cell
9
+ * is as tall as the tallest item in its line, and an item that should fill
10
+ * that height, as a card in a grid does, sets `flexGrow: 1`.
11
+ */
12
+ readonly children?: ReactNode;
13
+ /**
14
+ * How narrow a cell may get before the grid drops a column, in dp or as a
15
+ * share of the room the feed is given. Every column is at least this wide
16
+ * and the grid fits as many as the room allows, which is how Material
17
+ * Design describes an adaptive grid: by its cell rather than by a column
18
+ * count per window size. Given less room than this, the one column takes
19
+ * the room it is given rather than overflowing it.
20
+ *
21
+ * Worth setting. The default is the figure Material Design's own example
22
+ * uses, and it is a floor rather than advice: only the contents know how
23
+ * narrow they can go.
24
+ * @default 180
25
+ */
26
+ readonly minItemWidth?: `${number}%` | number;
27
+ readonly ref?: Ref<ComponentRef<typeof View>>;
28
+ };
29
+ /**
30
+ * Material Design's feed layout: a grid of comparable items that fits as many
31
+ * columns as the room allows, down to a single column when it allows only
32
+ * one.
33
+ *
34
+ * ```tsx
35
+ * <Feed minItemWidth={260}>
36
+ * {items.map((item) => (
37
+ * <Card key={item.id} style={FILL}>…</Card>
38
+ * ))}
39
+ * </Feed>
40
+ * ```
41
+ *
42
+ * Layout only: it paints no surface. Each element it is handed sits in a
43
+ * cell of the feed's, which is what sizes it: a fragment counts as the items
44
+ * it holds and `null` as none, while a component counts as one item whatever
45
+ * it renders, since a grid on native sees elements rather than what they
46
+ * draw.
47
+ *
48
+ * It reads the room it is in rather than the width of the window, so a feed
49
+ * inside a pane reflows with the pane.
50
+ *
51
+ * `style`, `ref` and `testID` land on the outer `View`, and a `style` there
52
+ * applies over the feed's own.
53
+ */
54
+ declare function Feed({ children, minItemWidth, style, ...props }: FeedProps): import("react").JSX.Element;
55
+ //#endregion
56
+ export { Feed, type FeedProps };
57
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,167 @@
1
+ import { useTheme } from "../../theme/scope.js";
2
+ import { composeStyles, themedStyles } from "../../theme/styles.js";
3
+ import { c } from "react/compiler-runtime";
4
+ import { StyleSheet, View, useWindowDimensions } from "react-native";
5
+ import { Children, Fragment, isValidElement, useState } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/components/feed/index.tsx
8
+ const DEFAULT_MIN_ITEM_WIDTH = 180;
9
+ const ROUNDING = .5;
10
+ const EPSILON = 1e-6;
11
+ const styles = StyleSheet.create({
12
+ cell: {
13
+ flexGrow: 1,
14
+ flexShrink: 1,
15
+ minWidth: 0
16
+ },
17
+ grid: {
18
+ alignSelf: "stretch",
19
+ flexDirection: "row",
20
+ flexWrap: "wrap"
21
+ }
22
+ });
23
+ const stylesOf = themedStyles(({ spacing }) => StyleSheet.create({ grid: {
24
+ columnGap: spacing.xl,
25
+ rowGap: spacing.xl
26
+ } }));
27
+ function cellsOf(children, columns) {
28
+ const cells = itemsOf(children, "");
29
+ for (let at = cells.length; at % columns !== 0; at += 1) cells.push({ key: `empty-${at}` });
30
+ return cells;
31
+ }
32
+ /**
33
+ * How many columns of at least `minItemWidth` fit in `room` with `gap`
34
+ * between them, as `repeat(auto-fill, minmax(min(…, 100%), 1fr))` counts
35
+ * them, and never fewer than one.
36
+ */
37
+ function columnsOf(room, minItemWidth, gap) {
38
+ const least = typeof minItemWidth === "number" ? minItemWidth : room * Number.parseFloat(minItemWidth) / 100;
39
+ const cell = Math.min(least, room);
40
+ return Math.max(1, Math.floor((room + gap) / (cell + gap) + EPSILON));
41
+ }
42
+ /**
43
+ * Material Design's feed layout: a grid of comparable items that fits as many
44
+ * columns as the room allows, down to a single column when it allows only
45
+ * one.
46
+ *
47
+ * ```tsx
48
+ * <Feed minItemWidth={260}>
49
+ * {items.map((item) => (
50
+ * <Card key={item.id} style={FILL}>…</Card>
51
+ * ))}
52
+ * </Feed>
53
+ * ```
54
+ *
55
+ * Layout only: it paints no surface. Each element it is handed sits in a
56
+ * cell of the feed's, which is what sizes it: a fragment counts as the items
57
+ * it holds and `null` as none, while a component counts as one item whatever
58
+ * it renders, since a grid on native sees elements rather than what they
59
+ * draw.
60
+ *
61
+ * It reads the room it is in rather than the width of the window, so a feed
62
+ * inside a pane reflows with the pane.
63
+ *
64
+ * `style`, `ref` and `testID` land on the outer `View`, and a `style` there
65
+ * applies over the feed's own.
66
+ */
67
+ function Feed(t0) {
68
+ const $ = c(16);
69
+ let children;
70
+ let props;
71
+ let style;
72
+ let t1;
73
+ if ($[0] !== t0) {
74
+ ({children, minItemWidth: t1, style, ...props} = t0);
75
+ $[0] = t0;
76
+ $[1] = children;
77
+ $[2] = props;
78
+ $[3] = style;
79
+ $[4] = t1;
80
+ } else {
81
+ children = $[1];
82
+ props = $[2];
83
+ style = $[3];
84
+ t1 = $[4];
85
+ }
86
+ const minItemWidth = t1 === void 0 ? DEFAULT_MIN_ITEM_WIDTH : t1;
87
+ const theme = useTheme();
88
+ const own = stylesOf(theme);
89
+ const dimensions = useWindowDimensions();
90
+ const [measured, setMeasured] = useState();
91
+ const room = measured ?? dimensions.width;
92
+ const gap = theme.spacing.xl;
93
+ const columns = columnsOf(room, minItemWidth, gap);
94
+ const share = shareOf(room, columns, gap);
95
+ const T0 = View;
96
+ const T1 = View;
97
+ let t2;
98
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
99
+ t2 = widthTo(setMeasured);
100
+ $[5] = t2;
101
+ } else t2 = $[5];
102
+ const t3 = composeStyles(styles.grid, own.grid);
103
+ const t5 = cellsOf(children, columns).map((t4) => {
104
+ const { item, key } = t4;
105
+ return /* @__PURE__ */ jsx(View, {
106
+ style: composeStyles(styles.cell, share),
107
+ children: item
108
+ }, key);
109
+ });
110
+ let t6;
111
+ if ($[6] !== T1 || $[7] !== t2 || $[8] !== t3 || $[9] !== t5) {
112
+ t6 = /* @__PURE__ */ jsx(T1, {
113
+ onLayout: t2,
114
+ style: t3,
115
+ children: t5
116
+ });
117
+ $[6] = T1;
118
+ $[7] = t2;
119
+ $[8] = t3;
120
+ $[9] = t5;
121
+ $[10] = t6;
122
+ } else t6 = $[10];
123
+ let t7;
124
+ if ($[11] !== T0 || $[12] !== props || $[13] !== style || $[14] !== t6) {
125
+ t7 = /* @__PURE__ */ jsx(T0, {
126
+ ...props,
127
+ style,
128
+ children: t6
129
+ });
130
+ $[11] = T0;
131
+ $[12] = props;
132
+ $[13] = style;
133
+ $[14] = t6;
134
+ $[15] = t7;
135
+ } else t7 = $[15];
136
+ return t7;
137
+ }
138
+ function itemsOf(children, within) {
139
+ return Children.toArray(children).flatMap((item, at) => {
140
+ if (!isValidElement(item)) return [{
141
+ item,
142
+ key: `${within}item-${at}`
143
+ }];
144
+ const key = `${within}${item.key ?? `item-${at}`}`;
145
+ return item.type === Fragment ? itemsOf(item.props.children, key) : [{
146
+ item,
147
+ key
148
+ }];
149
+ });
150
+ }
151
+ /**
152
+ * A cell's share of a line as a style: an equal part of the room left after
153
+ * the gaps, less the rounding the cells grow back from.
154
+ */
155
+ function shareOf(room, columns, gap) {
156
+ const share = (room - gap * (columns - 1)) / columns;
157
+ return { flexBasis: Math.max(0, share - ROUNDING) };
158
+ }
159
+ function widthTo(onMeasure) {
160
+ return (event) => {
161
+ onMeasure(event.nativeEvent.layout.width);
162
+ };
163
+ }
164
+ //#endregion
165
+ export { Feed };
166
+
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["ComponentRef","ReactNode","Ref","LayoutChangeEvent","ViewProps","ViewStyle","Children","Fragment","isValidElement","useState","StyleSheet","useWindowDimensions","View","useTheme","composeStyles","themedStyles","DEFAULT_MIN_ITEM_WIDTH","ROUNDING","EPSILON","styles","create","cell","flexGrow","flexShrink","minWidth","grid","alignSelf","flexDirection","flexWrap","stylesOf","spacing","columnGap","xl","rowGap","Cell","item","ReturnType","toArray","key","FeedProps","Omit","children","minItemWidth","ref","cellsOf","columns","cells","itemsOf","at","length","push","columnsOf","room","NonNullable","gap","least","Number","parseFloat","Math","min","max","floor","Feed","t0","$","_c","props","style","t1","undefined","theme","own","dimensions","measured","setMeasured","width","share","shareOf","T0","T1","t2","Symbol","for","widthTo","t3","t5","map","t4","t6","t7","within","flatMap","type","flexBasis","onMeasure","event","nativeEvent","layout","_c"],"sources":["../../../src/components/feed/index.tsx"],"sourcesContent":["import type { ComponentRef, ReactNode, Ref } from 'react'\nimport type { LayoutChangeEvent, ViewProps, ViewStyle } from 'react-native'\n\nimport { Children, Fragment, isValidElement, useState } from 'react'\nimport { StyleSheet, useWindowDimensions, View } from 'react-native'\n\nimport { useTheme } from '../../theme/scope'\nimport { composeStyles, themedStyles } from '../../theme/styles'\n\n// Material Design's example minimum for an adaptive grid cell, and the only\n// number the spec puts on one. It is a floor rather than a recommendation: how\n// narrow a cell can get before its contents stop making sense is a property of\n// the contents, which is why Material Design leaves it a parameter. Most feeds\n// should say what theirs is instead of taking this.\nconst DEFAULT_MIN_ITEM_WIDTH = 180\n\n// How far under its share of the line a cell starts, before it grows back to\n// it. The layout rounds every box to the device's pixels, so cells summing to\n// exactly the room could come out a fraction wider than it and wrap the last\n// one onto a line of its own.\nconst ROUNDING = 0.5\n\n// Room for rounding in the width the layout reports, so a room exactly wide\n// enough for one more column is not short of it by a fraction of a dp.\nconst EPSILON = 1e-6\n\n// The third of Material Design's canonical layouts, after list-detail and\n// supporting pane, as kanso-ui's Feed draws it: one pane arranged into a grid\n// of comparable things, described by its cell rather than by its columns.\n// Compose spells it `GridCells.Adaptive(minSize)`: every column is at least\n// that wide, the grid fits as many as it can, and always at least one, which\n// takes the whole width even where that is narrower than the minimum.\n//\n// React Native has no grid, so the feed works out the column count itself\n// from the room it is given, and wraps the items into lines of that many\n// equal cells. The last line is filled out with empty cells, which is web's\n// `auto-fill` rather than `auto-fit`: two items in a line with room for four\n// stay a cell wide rather than stretching to share it. Every cell of a line is\n// as tall as its tallest item, as grid cells are.\n//\n// The room is the width of the grid inside the feed's own padding, which the\n// layout reports once the feed has drawn, so a feed inside a pane reflows\n// with the pane rather than with the window. Before that report arrives the\n// first render guesses the window's width, the room a feed most often has.\n//\n// Every cell is a child of the one grid, so an item keeps its place in the\n// tree, and whatever state it holds, as the column count changes.\n//\n// It virtualises nothing, as on web: every item is drawn whether or not it is\n// on screen. A feed of hundreds of items is a `FlatList` with `numColumns`,\n// which draws only what can be seen.\nconst styles = StyleSheet.create({\n // A cell grows back to its share of the line from under it, and shrinks\n // past its content rather than overflowing the line.\n cell: {\n flexGrow: 1,\n flexShrink: 1,\n minWidth: 0,\n },\n // The grid spans the feed's content box, whatever the feed aligns its\n // content to, since its width is the room the columns are counted in.\n grid: {\n alignSelf: 'stretch',\n flexDirection: 'row',\n flexWrap: 'wrap',\n },\n})\n\nconst stylesOf = themedStyles(({ spacing }) =>\n StyleSheet.create({\n // The gap between lines and the one between cells, which web's grid\n // spells `gap: spacing.xl` for both.\n grid: {\n columnGap: spacing.xl,\n rowGap: spacing.xl,\n },\n }),\n)\n\ntype Cell = {\n readonly item?: ReturnType<typeof Children.toArray>[number]\n readonly key: string\n}\n\ntype FeedProps = Omit<ViewProps, 'children'> & {\n /**\n * The items, each laid into a cell of its own. An element is one item,\n * whatever it renders, so hand the feed the items themselves rather than a\n * component listing them; a fragment counts as the items it holds. A cell\n * is as tall as the tallest item in its line, and an item that should fill\n * that height, as a card in a grid does, sets `flexGrow: 1`.\n */\n readonly children?: ReactNode\n /**\n * How narrow a cell may get before the grid drops a column, in dp or as a\n * share of the room the feed is given. Every column is at least this wide\n * and the grid fits as many as the room allows, which is how Material\n * Design describes an adaptive grid: by its cell rather than by a column\n * count per window size. Given less room than this, the one column takes\n * the room it is given rather than overflowing it.\n *\n * Worth setting. The default is the figure Material Design's own example\n * uses, and it is a floor rather than advice: only the contents know how\n * narrow they can go.\n * @default 180\n */\n readonly minItemWidth?: `${number}%` | number\n readonly ref?: Ref<ComponentRef<typeof View>>\n}\n\n// The items as cells, the last line filled out with empty ones.\nfunction cellsOf(children: ReactNode, columns: number) {\n const cells = itemsOf(children, '')\n for (let at = cells.length; at % columns !== 0; at += 1) {\n cells.push({ key: `empty-${at}` })\n }\n return cells\n}\n\n/**\n * How many columns of at least `minItemWidth` fit in `room` with `gap`\n * between them, as `repeat(auto-fill, minmax(min(…, 100%), 1fr))` counts\n * them, and never fewer than one.\n */\nfunction columnsOf(\n room: number,\n minItemWidth: NonNullable<FeedProps['minItemWidth']>,\n gap: number,\n) {\n const least =\n typeof minItemWidth === 'number'\n ? minItemWidth\n : (room * Number.parseFloat(minItemWidth)) / 100\n const cell = Math.min(least, room)\n return Math.max(1, Math.floor((room + gap) / (cell + gap) + EPSILON))\n}\n\n/**\n * Material Design's feed layout: a grid of comparable items that fits as many\n * columns as the room allows, down to a single column when it allows only\n * one.\n *\n * ```tsx\n * <Feed minItemWidth={260}>\n * {items.map((item) => (\n * <Card key={item.id} style={FILL}>…</Card>\n * ))}\n * </Feed>\n * ```\n *\n * Layout only: it paints no surface. Each element it is handed sits in a\n * cell of the feed's, which is what sizes it: a fragment counts as the items\n * it holds and `null` as none, while a component counts as one item whatever\n * it renders, since a grid on native sees elements rather than what they\n * draw.\n *\n * It reads the room it is in rather than the width of the window, so a feed\n * inside a pane reflows with the pane.\n *\n * `style`, `ref` and `testID` land on the outer `View`, and a `style` there\n * applies over the feed's own.\n */\nfunction Feed({\n children,\n minItemWidth = DEFAULT_MIN_ITEM_WIDTH,\n style,\n ...props\n}: FeedProps) {\n const theme = useTheme()\n const own = stylesOf(theme)\n const dimensions = useWindowDimensions()\n const [measured, setMeasured] = useState<number>()\n const room = measured ?? dimensions.width\n const gap = theme.spacing.xl\n const columns = columnsOf(room, minItemWidth, gap)\n const share = shareOf(room, columns, gap)\n\n return (\n <View {...props} style={style}>\n <View\n onLayout={widthTo(setMeasured)}\n style={composeStyles<ViewStyle>(styles.grid, own.grid)}\n >\n {cellsOf(children, columns).map(({ item, key }) => (\n <View key={key} style={composeStyles<ViewStyle>(styles.cell, share)}>\n {item}\n </View>\n ))}\n </View>\n </View>\n )\n}\n\n// Each item as a cell, with a fragment's items in its place, as a grid on web\n// lays out what a fragment holds rather than the fragment. A cell takes the\n// key `Children.toArray` gives its element under the keys of the fragments\n// around it, or its place for a bare string or number, which takes none.\nfunction itemsOf(children: ReactNode, within: string): Cell[] {\n return Children.toArray(children).flatMap((item, at) => {\n if (!isValidElement<{ children?: ReactNode }>(item)) {\n return [{ item, key: `${within}item-${at}` }]\n }\n const key = `${within}${item.key ?? `item-${at}`}`\n return item.type === Fragment\n ? itemsOf(item.props.children, key)\n : [{ item, key }]\n })\n}\n\n/**\n * A cell's share of a line as a style: an equal part of the room left after\n * the gaps, less the rounding the cells grow back from.\n */\nfunction shareOf(room: number, columns: number, gap: number): ViewStyle {\n const share = (room - gap * (columns - 1)) / columns\n return { flexBasis: Math.max(0, share - ROUNDING) }\n}\n\n// A layout handler handing the width it reports to `onMeasure`. Built by a\n// call rather than written inline, which is what react-perf's\n// jsx-no-new-function-as-prop is after; the React Compiler memoises it.\nfunction widthTo(onMeasure: (width: number) => void) {\n return (event: LayoutChangeEvent) => {\n onMeasure(event.nativeEvent.layout.width)\n }\n}\n\nexport type { FeedProps }\n\nexport { Feed }\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { View, ViewProps } from "react-native";
2
+ import { ComponentRef, ReactNode, Ref } from "react";
3
+ //#region src/components/keycap/index.d.ts
4
+ type KeycapProps = Omit<ViewProps, 'children'> & {
5
+ /** The key's name, as the instruction writes it, such as `Enter` or `Esc`. */
6
+ readonly children?: ReactNode;
7
+ readonly ref?: Ref<ComponentRef<typeof View>>;
8
+ };
9
+ /**
10
+ * A single key on a keyboard, as an instruction names it: `Enter`, `Esc`,
11
+ * `Ctrl`. A chord is several of these, combined at the call site.
12
+ *
13
+ * ```tsx
14
+ * <Text variant="bodyLarge">
15
+ * Press <Keycap>Esc</Keycap> to dismiss
16
+ * </Text>
17
+ * ```
18
+ *
19
+ * It takes its size from the Text around it, so a key named in a heading and
20
+ * one named in a footnote are the same component, and stays part of that
21
+ * Text's sentence to a screen reader. `style`, `ref` and `testID` land on the
22
+ * cap's `View`, and a `style` there applies over its own.
23
+ */
24
+ declare function Keycap({ children, style, ...props }: KeycapProps): import("react").JSX.Element;
25
+ //#endregion
26
+ export { Keycap, type KeycapProps };
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,136 @@
1
+ import { useTheme } from "../../theme/scope.js";
2
+ import { composeStyles, themedStyles } from "../../theme/styles.js";
3
+ import { useFontFamily } from "../../typography/fonts.js";
4
+ import { familyStyle } from "../../typography/roles.js";
5
+ import { useTextSize } from "../../typography/size.js";
6
+ import { c } from "react/compiler-runtime";
7
+ import { Platform, StyleSheet, Text, View } from "react-native";
8
+ import { isValidElement } from "react";
9
+ import { jsx } from "react/jsx-runtime";
10
+ //#region src/components/keycap/index.tsx
11
+ const SHARE = .8125;
12
+ const LINE = 1.4;
13
+ const RULE = 1;
14
+ function baselineOf(size) {
15
+ return RULE + .31149999999999994 * size;
16
+ }
17
+ const styles = StyleSheet.create({ alone: { alignSelf: "flex-start" } });
18
+ const HIDDEN = {
19
+ accessibilityElementsHidden: true,
20
+ importantForAccessibility: "no-hide-descendants"
21
+ };
22
+ const stylesOf = themedStyles(({ colors, radii, spacing }) => StyleSheet.create({
23
+ cap: {
24
+ borderColor: colors.outline,
25
+ borderRadius: radii.xs,
26
+ borderWidth: RULE,
27
+ paddingHorizontal: spacing.xxs
28
+ },
29
+ text: { color: colors.onSurface }
30
+ }));
31
+ function holdsKeycap(node) {
32
+ if (Array.isArray(node)) return node.some((part) => holdsKeycap(part));
33
+ if (!isValidElement(node)) return false;
34
+ return node.type === Keycap || holdsKeycap(node.props.children);
35
+ }
36
+ /**
37
+ * A single key on a keyboard, as an instruction names it: `Enter`, `Esc`,
38
+ * `Ctrl`. A chord is several of these, combined at the call site.
39
+ *
40
+ * ```tsx
41
+ * <Text variant="bodyLarge">
42
+ * Press <Keycap>Esc</Keycap> to dismiss
43
+ * </Text>
44
+ * ```
45
+ *
46
+ * It takes its size from the Text around it, so a key named in a heading and
47
+ * one named in a footnote are the same component, and stays part of that
48
+ * Text's sentence to a screen reader. `style`, `ref` and `testID` land on the
49
+ * cap's `View`, and a `style` there applies over its own.
50
+ */
51
+ function Keycap(t0) {
52
+ const $ = c(14);
53
+ let children;
54
+ let props;
55
+ let style;
56
+ if ($[0] !== t0) {
57
+ ({children, style, ...props} = t0);
58
+ $[0] = t0;
59
+ $[1] = children;
60
+ $[2] = props;
61
+ $[3] = style;
62
+ } else {
63
+ children = $[1];
64
+ props = $[2];
65
+ style = $[3];
66
+ }
67
+ const theme = useTheme();
68
+ const own = stylesOf(theme);
69
+ const around = useTextSize();
70
+ const mono = useFontFamily(theme.typography.fontFamilyMono);
71
+ const size = (around ?? theme.typography.bodyMediumSize) * SHARE;
72
+ const inline = around !== void 0;
73
+ const native = Platform.OS !== "web";
74
+ const lowered = inline && native ? { transform: [{ translateY: baselineOf(size) }] } : null;
75
+ const T0 = View;
76
+ const t1 = inline && native ? HIDDEN : null;
77
+ const t2 = composeStyles(own.cap, inline ? null : styles.alone, lowered, style);
78
+ const T1 = Text;
79
+ const t3 = 1;
80
+ const t4 = composeStyles(own.text, familyStyle(mono), {
81
+ fontSize: size,
82
+ lineHeight: size * LINE
83
+ });
84
+ let t5;
85
+ if ($[4] !== T1 || $[5] !== children || $[6] !== t4) {
86
+ t5 = /* @__PURE__ */ jsx(T1, {
87
+ numberOfLines: t3,
88
+ style: t4,
89
+ children
90
+ });
91
+ $[4] = T1;
92
+ $[5] = children;
93
+ $[6] = t4;
94
+ $[7] = t5;
95
+ } else t5 = $[7];
96
+ let t6;
97
+ if ($[8] !== T0 || $[9] !== props || $[10] !== t1 || $[11] !== t2 || $[12] !== t5) {
98
+ t6 = /* @__PURE__ */ jsx(T0, {
99
+ ...t1,
100
+ ...props,
101
+ style: t2,
102
+ children: t5
103
+ });
104
+ $[8] = T0;
105
+ $[9] = props;
106
+ $[10] = t1;
107
+ $[11] = t2;
108
+ $[12] = t5;
109
+ $[13] = t6;
110
+ } else t6 = $[13];
111
+ return t6;
112
+ }
113
+ /**
114
+ * The words a Text holding a Keycap says to a screen reader on iOS and
115
+ * Android, with each key's name in its place, or `undefined` for one holding
116
+ * no Keycap, or anything whose words cannot be read off its children.
117
+ */
118
+ function sentenceLabel(children) {
119
+ if (Platform.OS === "web" || !holdsKeycap(children)) return;
120
+ return spoken(children) ?? void 0;
121
+ }
122
+ function spoken(node) {
123
+ if (node === null || node === void 0 || typeof node === "boolean") return "";
124
+ if (typeof node === "string" || typeof node === "number") return String(node);
125
+ if (Array.isArray(node)) {
126
+ const parts = node.map((part) => spoken(part));
127
+ return parts.includes(null) ? null : parts.join("");
128
+ }
129
+ if (!isValidElement(node)) return null;
130
+ if (node.props.onPress !== void 0 || node.props.children === void 0) return null;
131
+ return spoken(node.props.children);
132
+ }
133
+ //#endregion
134
+ export { Keycap, sentenceLabel };
135
+
136
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["ComponentRef","ReactNode","Ref","TextStyle","ViewProps","ViewStyle","isValidElement","Text","NativeText","Platform","StyleSheet","View","useTheme","composeStyles","themedStyles","useFontFamily","familyStyle","useTextSize","SHARE","LINE","ASCENT","DESCENT","RULE","baselineOf","size","styles","create","alone","alignSelf","HIDDEN","accessibilityElementsHidden","importantForAccessibility","stylesOf","colors","radii","spacing","cap","borderColor","outline","borderRadius","xs","borderWidth","paddingHorizontal","xxs","text","color","onSurface","KeycapProps","Omit","children","ref","holdsKeycap","node","Array","isArray","some","part","type","Keycap","props","t0","$","_c","style","theme","own","around","mono","typography","fontFamilyMono","bodyMediumSize","inline","undefined","native","OS","lowered","transform","translateY","T0","t1","t2","T1","t3","t4","fontSize","lineHeight","t5","t6","sentenceLabel","spoken","String","parts","map","includes","join","onPress","_c","NativeText"],"sources":["../../../src/components/keycap/index.tsx"],"sourcesContent":["import type { ComponentRef, ReactNode, Ref } from 'react'\nimport type { TextStyle, ViewProps, ViewStyle } from 'react-native'\n\nimport { isValidElement } from 'react'\nimport { Text as NativeText, Platform, StyleSheet, View } from 'react-native'\n\nimport { useTheme } from '../../theme/scope'\nimport { composeStyles, themedStyles } from '../../theme/styles'\nimport { useFontFamily } from '../../typography/fonts'\nimport { familyStyle } from '../../typography/roles'\nimport { useTextSize } from '../../typography/size'\n\n// kanso-ui's Keycap: one key on a keyboard, as an instruction names it, drawn\n// as a small outlined cap in the line of text around it. A chord or a path\n// through a menu is several of them, with the sentence's own words between.\n//\n// No Material Design page draws a key, so it keeps web's reading of the chips\n// spec page: the assist chip's 1dp rule in the outline role, and the\n// extra-small corner rather than the chip's small one, since a box the height\n// of a line has no room for 8dp. No fill, so a keycap inside a Card or a Sheet\n// does not read as a nested surface. The text is on surface in the mono face,\n// at 0.8125 of the size around it on a 1.4 line height, a share smaller than\n// Code's because the rule adds height the glyphs do not, and on one line,\n// since 'Enter' broken across two stops reading as one key.\n//\n// A Text nested in another draws no border on iOS or Android, so the cap is a\n// View laid inline in the Text around it, which both platforms place on the\n// line, holding a Text of its own. Its size is the share of the one the\n// library's Text provides, as Code's is, and body medium's outside a Text.\n//\n// **iOS and Android stand an inline View on the baseline by its bottom edge**,\n// where React Native Web lays one out as an `inline-flex` box, which shares\n// its text's baseline with the line. So on native the cap is moved down by\n// how far its own baseline sits above its bottom edge: the 1dp rule, half the\n// leading the 1.4 line height adds, and the face's descent. The figures are\n// Roboto Mono's, the mono token's first family, measured in Chromium from the\n// face kanso-ui ships: 1.048em of ascent and 0.271em of descent, so 1.319em of\n// glyph box in a 1.4em line, and the baseline 0.3115em above the line box's\n// bottom. Neither platform's own placement has been measured on a device yet.\n//\n// A screen reader on iOS or Android reads the Text around the cap as one\n// element whose words leave the cap out, since an inline View is not text. So\n// the cap is hidden from it there, and the library's Text, when a Keycap is\n// among its children, speaks the sentence with the key's name in its place,\n// through `sentenceLabel` below. React Native Web reads the cap in its place\n// in the sentence as it is.\n\n// The size against the text around it, web's `0.8125em`.\nconst SHARE = 0.8125\n\n// The line height against the cap's own size, web's `1.4`.\nconst LINE = 1.4\n\n// Roboto Mono's ascent and descent against its size.\nconst ASCENT = 1.048\nconst DESCENT = 0.271\n\n// The rule around the cap, web's `1px`.\nconst RULE = 1\n\n// How far the cap's baseline sits above its bottom edge at a size: the rule,\n// half the leading and the descent.\nfunction baselineOf(size: number) {\n return RULE + ((LINE - ASCENT - DESCENT) / 2 + DESCENT) * size\n}\n\nconst styles = StyleSheet.create({\n // A cap standing on its own shrinks to its key rather than stretching\n // across the column it sits in.\n alone: {\n alignSelf: 'flex-start',\n },\n})\n\n// Kept from a screen reader on iOS and Android, where the Text around the cap\n// speaks its key instead.\nconst HIDDEN: ViewProps = {\n accessibilityElementsHidden: true,\n importantForAccessibility: 'no-hide-descendants',\n}\n\nconst stylesOf = themedStyles(({ colors, radii, spacing }) =>\n StyleSheet.create({\n cap: {\n borderColor: colors.outline,\n borderRadius: radii.xs,\n borderWidth: RULE,\n paddingHorizontal: spacing.xxs,\n },\n text: {\n color: colors.onSurface,\n },\n }),\n)\n\ntype KeycapProps = Omit<ViewProps, 'children'> & {\n /** The key's name, as the instruction writes it, such as `Enter` or `Esc`. */\n readonly children?: ReactNode\n readonly ref?: Ref<ComponentRef<typeof View>>\n}\n\n// Whether a Keycap is among the nodes, at any depth.\nfunction holdsKeycap(node: ReactNode): boolean {\n if (Array.isArray(node)) {\n return node.some((part: ReactNode) => holdsKeycap(part))\n }\n if (!isValidElement<{ children?: ReactNode }>(node)) {\n return false\n }\n return node.type === Keycap || holdsKeycap(node.props.children)\n}\n\n/**\n * A single key on a keyboard, as an instruction names it: `Enter`, `Esc`,\n * `Ctrl`. A chord is several of these, combined at the call site.\n *\n * ```tsx\n * <Text variant=\"bodyLarge\">\n * Press <Keycap>Esc</Keycap> to dismiss\n * </Text>\n * ```\n *\n * It takes its size from the Text around it, so a key named in a heading and\n * one named in a footnote are the same component, and stays part of that\n * Text's sentence to a screen reader. `style`, `ref` and `testID` land on the\n * cap's `View`, and a `style` there applies over its own.\n */\nfunction Keycap({ children, style, ...props }: KeycapProps) {\n const theme = useTheme()\n const own = stylesOf(theme)\n const around = useTextSize()\n const mono = useFontFamily(theme.typography.fontFamilyMono)\n const size = (around ?? theme.typography.bodyMediumSize) * SHARE\n const inline = around !== undefined\n const native = Platform.OS !== 'web'\n const lowered: null | ViewStyle =\n inline && native ? { transform: [{ translateY: baselineOf(size) }] } : null\n\n return (\n <View\n {...(inline && native ? HIDDEN : null)}\n {...props}\n style={composeStyles<ViewStyle>(\n own.cap,\n inline ? null : styles.alone,\n lowered,\n style,\n )}\n >\n <NativeText\n numberOfLines={1}\n style={composeStyles<TextStyle>(own.text, familyStyle(mono), {\n fontSize: size,\n lineHeight: size * LINE,\n })}\n >\n {children}\n </NativeText>\n </View>\n )\n}\n\n/**\n * The words a Text holding a Keycap says to a screen reader on iOS and\n * Android, with each key's name in its place, or `undefined` for one holding\n * no Keycap, or anything whose words cannot be read off its children.\n */\nfunction sentenceLabel(children: ReactNode): string | undefined {\n if (Platform.OS === 'web' || !holdsKeycap(children)) {\n return undefined\n }\n return spoken(children) ?? undefined\n}\n\n// What a node says in a sentence, with each Keycap's name in its place, or\n// `null` where it holds something whose words cannot be read off its\n// children, such as a component that draws its own text or one that presses,\n// which the Text around it then leaves to the platform.\nfunction spoken(node: ReactNode): null | string {\n if (node === null || node === undefined || typeof node === 'boolean') {\n return ''\n }\n if (typeof node === 'string' || typeof node === 'number') {\n return String(node)\n }\n if (Array.isArray(node)) {\n const parts = node.map((part: ReactNode) => spoken(part))\n return parts.includes(null) ? null : parts.join('')\n }\n if (!isValidElement<{ children?: ReactNode; onPress?: unknown }>(node)) {\n return null\n }\n if (node.props.onPress !== undefined || node.props.children === undefined) {\n return null\n }\n return spoken(node.props.children)\n}\n\nexport type { KeycapProps }\n\nexport { Keycap, sentenceLabel }\n"],"mappings":"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"}
@@ -10,8 +10,8 @@ type ListLoadMoreProps = {
10
10
  readonly isLoading?: boolean;
11
11
  /**
12
12
  * What a screen reader calls the ring, and hears as it appears. Left out,
13
- * it is the word for it in the device's language, "Loading more" in
14
- * English.
13
+ * it is the word for it in the `I18nProvider`'s language, or the device's
14
+ * outside one, "Loading more" in English.
15
15
  */
16
16
  readonly label?: string;
17
17
  /**
@@ -57,8 +57,8 @@ type ListProps<T extends object = object> = Omit<ScrollViewProps, 'children' | '
57
57
  readonly ref?: Ref<FlatList>;
58
58
  /**
59
59
  * What the list shows when it has no rows and nothing is loading. Left
60
- * out, it is a muted row saying "No results" in the device's language, so
61
- * the list keeps a row's height.
60
+ * out, it is a muted row saying "No results" in the `I18nProvider`'s
61
+ * language, or the device's outside one, so the list keeps a row's height.
62
62
  */
63
63
  readonly renderEmptyState?: () => ReactNode;
64
64
  /**
@@ -76,7 +76,8 @@ type ListProps<T extends object = object> = Omit<ScrollViewProps, 'children' | '
76
76
  * What selecting a row is called, for a screen reader: the accessibility
77
77
  * action each selectable row offers, which is what its checkbox does. The
78
78
  * row's own text is what names the row. Left out, it is the word for it in
79
- * the device's language, "Select" in English.
79
+ * the `I18nProvider`'s language, or the device's outside one, "Select" in
80
+ * English.
80
81
  */
81
82
  readonly selectLabel?: string;
82
83
  readonly style?: StyleProp<ViewStyle>;