@ankhorage/surface 1.1.0 → 1.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @ankhorage/surface
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 73fd969: Add a render-only Image primitive.
8
+
9
+ This introduces a Surface `Image` component with string and React Native image source support, token-aware sizing and radius props, `fit`/`resizeMode` behavior, accessibility label mapping, and provider-neutral `fallbackSource` handling for load errors.
10
+
3
11
  ## 1.1.0
4
12
 
5
13
  ### Minor Changes
package/README.md CHANGED
@@ -23,9 +23,18 @@ bun add @ankhorage/surface
23
23
  ## 📦 Usage
24
24
 
25
25
  ```tsx
26
- import { Box, Text } from '@ankhorage/surface';
26
+ import { Box, Image, Text } from '@ankhorage/surface';
27
27
 
28
28
  <Box padding="lg" gap="md">
29
+ <Image
30
+ alt="Hero image"
31
+ fallbackSource="https://example.com/hero-fallback.jpg"
32
+ fit="cover"
33
+ height={240}
34
+ radius="l"
35
+ source="https://example.com/hero.jpg"
36
+ width="100%"
37
+ />
29
38
  <Text variant="title">Title</Text>
30
39
  <Text>Content</Text>
31
40
  </Box>;
package/dist/index.d.ts CHANGED
@@ -50,6 +50,8 @@ export type { HeadingLevel, HeadingProps } from './primitives/heading';
50
50
  export { Heading } from './primitives/heading';
51
51
  export type { IconProps, IconProvider } from './primitives/icon';
52
52
  export { Icon } from './primitives/icon';
53
+ export type { ImageFit, ImageProps, SurfaceImageSource } from './primitives/image';
54
+ export { Image } from './primitives/image';
53
55
  export type { TextProps } from './primitives/text';
54
56
  export { Text } from './primitives/text';
55
57
  export * from './theme';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,WAAW,GACZ,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,MAAM,EACN,UAAU,GACX,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AACjG,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,WAAW,GACZ,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,MAAM,EACN,UAAU,GACX,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AACjG,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ export * from './layout';
25
25
  export { ButtonBase } from './primitives/button-base';
26
26
  export { Heading } from './primitives/heading';
27
27
  export { Icon } from './primitives/icon';
28
+ export { Image } from './primitives/image';
28
29
  export { Text } from './primitives/text';
29
30
  export * from './theme';
30
31
  export { isDeepEqual } from './utils/deepEqual';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAW3C,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,MAAM,EACN,UAAU,GACX,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export type { BadgeProps } from './components/badge';\nexport { Badge } from './components/badge';\nexport type { ButtonIconSpec, ButtonProps } from './components/button';\nexport { Button } from './components/button';\nexport type { CardProps } from './components/card';\nexport { Card } from './components/card';\nexport type { CheckboxProps } from './components/checkbox';\nexport { Checkbox } from './components/checkbox';\nexport type { DrawerProps } from './components/drawer';\nexport { Drawer } from './components/drawer';\nexport type { FieldProps } from './components/field';\nexport { Field } from './components/field';\nexport type { HelperTextProps } from './components/helper-text';\nexport { HelperText } from './components/helper-text';\nexport type { IconButtonProps } from './components/icon-button';\nexport { IconButton } from './components/icon-button';\nexport type { LabelProps } from './components/label';\nexport { Label } from './components/label';\nexport type { ListItemProps } from './components/list-item';\nexport { ListItem } from './components/list-item';\nexport type { MenuItem, MenuProps } from './components/menu';\nexport { Menu } from './components/menu';\nexport type { ModalProps } from './components/modal';\nexport { Modal } from './components/modal';\nexport type {\n DrawerNavigationItemProps,\n DrawerNavigationProps,\n NavigationItemIcon,\n NavigationItemProps,\n NavigationItemSpec,\n NavigationListProps,\n TabBarItemProps,\n TabBarProps,\n} from './components/navigation';\nexport {\n DrawerNavigation,\n DrawerNavigationItem,\n NavigationItem,\n NavigationList,\n TabBar,\n TabBarItem,\n} from './components/navigation';\nexport type { RadioProps } from './components/radio';\nexport { Radio } from './components/radio';\nexport type { SwitchProps } from './components/switch';\nexport { Switch } from './components/switch';\nexport type { TabListProps, TabPanelProps, TabProps, TabsProps } from './components/tabs';\nexport { Tab, TabList, TabPanel, Tabs } from './components/tabs';\nexport type { TextInputProps } from './components/text-input';\nexport { TextInput } from './components/text-input';\nexport type { TextareaProps } from './components/textarea';\nexport { Textarea } from './components/textarea';\nexport type { ToastOptions, ToastProps, ToastTone } from './components/toast';\nexport { Toast, ToastProvider, useToast } from './components/toast';\nexport type { TooltipProps } from './components/tooltip';\nexport { Tooltip } from './components/tooltip';\nexport type { FontRuntime } from './context/FontContext';\nexport { FontProvider, useFontContext } from './context/FontContext';\nexport type { I18nInstance, TranslationRuntime, Translator } from './context/TranslationContext';\nexport { TranslationProvider, useTranslationContext } from './context/TranslationContext';\nexport * from './core/responsive';\nexport * from './layout';\nexport type { ButtonBaseProps } from './primitives/button-base';\nexport { ButtonBase } from './primitives/button-base';\nexport type { HeadingLevel, HeadingProps } from './primitives/heading';\nexport { Heading } from './primitives/heading';\nexport type { IconProps, IconProvider } from './primitives/icon';\nexport { Icon } from './primitives/icon';\nexport type { TextProps } from './primitives/text';\nexport { Text } from './primitives/text';\nexport * from './theme';\nexport { isDeepEqual } from './utils/deepEqual';\nexport { deepMerge } from './utils/deepMerge';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAW3C,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,MAAM,EACN,UAAU,GACX,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export type { BadgeProps } from './components/badge';\nexport { Badge } from './components/badge';\nexport type { ButtonIconSpec, ButtonProps } from './components/button';\nexport { Button } from './components/button';\nexport type { CardProps } from './components/card';\nexport { Card } from './components/card';\nexport type { CheckboxProps } from './components/checkbox';\nexport { Checkbox } from './components/checkbox';\nexport type { DrawerProps } from './components/drawer';\nexport { Drawer } from './components/drawer';\nexport type { FieldProps } from './components/field';\nexport { Field } from './components/field';\nexport type { HelperTextProps } from './components/helper-text';\nexport { HelperText } from './components/helper-text';\nexport type { IconButtonProps } from './components/icon-button';\nexport { IconButton } from './components/icon-button';\nexport type { LabelProps } from './components/label';\nexport { Label } from './components/label';\nexport type { ListItemProps } from './components/list-item';\nexport { ListItem } from './components/list-item';\nexport type { MenuItem, MenuProps } from './components/menu';\nexport { Menu } from './components/menu';\nexport type { ModalProps } from './components/modal';\nexport { Modal } from './components/modal';\nexport type {\n DrawerNavigationItemProps,\n DrawerNavigationProps,\n NavigationItemIcon,\n NavigationItemProps,\n NavigationItemSpec,\n NavigationListProps,\n TabBarItemProps,\n TabBarProps,\n} from './components/navigation';\nexport {\n DrawerNavigation,\n DrawerNavigationItem,\n NavigationItem,\n NavigationList,\n TabBar,\n TabBarItem,\n} from './components/navigation';\nexport type { RadioProps } from './components/radio';\nexport { Radio } from './components/radio';\nexport type { SwitchProps } from './components/switch';\nexport { Switch } from './components/switch';\nexport type { TabListProps, TabPanelProps, TabProps, TabsProps } from './components/tabs';\nexport { Tab, TabList, TabPanel, Tabs } from './components/tabs';\nexport type { TextInputProps } from './components/text-input';\nexport { TextInput } from './components/text-input';\nexport type { TextareaProps } from './components/textarea';\nexport { Textarea } from './components/textarea';\nexport type { ToastOptions, ToastProps, ToastTone } from './components/toast';\nexport { Toast, ToastProvider, useToast } from './components/toast';\nexport type { TooltipProps } from './components/tooltip';\nexport { Tooltip } from './components/tooltip';\nexport type { FontRuntime } from './context/FontContext';\nexport { FontProvider, useFontContext } from './context/FontContext';\nexport type { I18nInstance, TranslationRuntime, Translator } from './context/TranslationContext';\nexport { TranslationProvider, useTranslationContext } from './context/TranslationContext';\nexport * from './core/responsive';\nexport * from './layout';\nexport type { ButtonBaseProps } from './primitives/button-base';\nexport { ButtonBase } from './primitives/button-base';\nexport type { HeadingLevel, HeadingProps } from './primitives/heading';\nexport { Heading } from './primitives/heading';\nexport type { IconProps, IconProvider } from './primitives/icon';\nexport { Icon } from './primitives/icon';\nexport type { ImageFit, ImageProps, SurfaceImageSource } from './primitives/image';\nexport { Image } from './primitives/image';\nexport type { TextProps } from './primitives/text';\nexport { Text } from './primitives/text';\nexport * from './theme';\nexport { isDeepEqual } from './utils/deepEqual';\nexport { deepMerge } from './utils/deepMerge';\n"]}
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { ImageProps } from './types';
3
+ export declare function Image({ source, fallbackSource, alt, accessibilityLabel, width, height, aspectRatio, fit, resizeMode, radius, style, testID, onError, }: ImageProps): React.JSX.Element | null;
4
+ //# sourceMappingURL=Image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../../src/primitives/image/Image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,wBAAgB,KAAK,CAAC,EACpB,MAAM,EACN,cAAc,EACd,GAAG,EACH,kBAAkB,EAClB,KAAK,EACL,MAAM,EACN,WAAW,EACX,GAAG,EACH,UAAU,EACV,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAO,GACR,EAAE,UAAU,4BAsCZ"}
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { Image as RNImage } from 'react-native';
3
+ import { useTheme } from '../../theme/ThemeContext';
4
+ import { getSourceKey, normalizeSource, resolveAccessibilityLabel, resolveAlt, resolveImageStyle, resolveRenderedSource, resolveResizeMode, } from './resolveImageProps';
5
+ export function Image({ source, fallbackSource, alt, accessibilityLabel, width, height, aspectRatio, fit, resizeMode, radius, style, testID, onError, }) {
6
+ const { theme } = useTheme();
7
+ const [showFallback, setShowFallback] = React.useState(false);
8
+ const primaryKey = React.useMemo(() => getSourceKey(source), [source]);
9
+ React.useEffect(() => {
10
+ setShowFallback(false);
11
+ }, [primaryKey]);
12
+ const normalizedPrimary = normalizeSource(source);
13
+ const normalizedFallback = normalizeSource(fallbackSource);
14
+ const resolvedSource = resolveRenderedSource(normalizedPrimary, normalizedFallback, showFallback);
15
+ if (!resolvedSource)
16
+ return null;
17
+ const resolvedResizeMode = resolveResizeMode(fit, resizeMode);
18
+ const resolvedAccessibilityLabel = resolveAccessibilityLabel(alt, accessibilityLabel);
19
+ const resolvedAlt = resolveAlt(alt, accessibilityLabel);
20
+ const resolvedStyle = resolveImageStyle(theme, { width, height, aspectRatio, radius });
21
+ return (<RNImage accessibilityLabel={resolvedAccessibilityLabel} accessibilityRole="image" alt={resolvedAlt} onError={(event) => {
22
+ onError?.(event);
23
+ if (!showFallback && normalizedFallback) {
24
+ setShowFallback(true);
25
+ }
26
+ }} resizeMode={resolvedResizeMode} source={resolvedSource} style={[resolvedStyle, style]} testID={testID}/>);
27
+ }
28
+ //# sourceMappingURL=Image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.js","sourceRoot":"","sources":["../../../src/primitives/image/Image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,UAAU,KAAK,CAAC,EACpB,MAAM,EACN,cAAc,EACd,GAAG,EACH,kBAAkB,EAClB,KAAK,EACL,MAAM,EACN,WAAW,EACX,GAAG,EACH,UAAU,EACV,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAO,GACI;IACX,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC7B,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,eAAe,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,MAAM,iBAAiB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,kBAAkB,GAAG,eAAe,CAAC,cAAc,CAAC,CAAC;IAC3D,MAAM,cAAc,GAAG,qBAAqB,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,YAAY,CAAC,CAAC;IAElG,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEjC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAC9D,MAAM,0BAA0B,GAAG,yBAAyB,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACtF,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACxD,MAAM,aAAa,GAAG,iBAAiB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IAEvF,OAAO,CACL,CAAC,OAAO,CACN,kBAAkB,CAAC,CAAC,0BAA0B,CAAC,CAC/C,iBAAiB,CAAC,OAAO,CACzB,GAAG,CAAC,CAAC,WAAW,CAAC,CACjB,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YACjB,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YAEjB,IAAI,CAAC,YAAY,IAAI,kBAAkB,EAAE,CAAC;gBACxC,eAAe,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC;QACH,CAAC,CAAC,CACF,UAAU,CAAC,CAAC,kBAAkB,CAAC,CAC/B,MAAM,CAAC,CAAC,cAAc,CAAC,CACvB,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,CAC9B,MAAM,CAAC,CAAC,MAAM,CAAC,EACf,CACH,CAAC;AACJ,CAAC","sourcesContent":["import React from 'react';\nimport { Image as RNImage } from 'react-native';\n\nimport { useTheme } from '../../theme/ThemeContext';\nimport {\n getSourceKey,\n normalizeSource,\n resolveAccessibilityLabel,\n resolveAlt,\n resolveImageStyle,\n resolveRenderedSource,\n resolveResizeMode,\n} from './resolveImageProps';\nimport type { ImageProps } from './types';\n\nexport function Image({\n source,\n fallbackSource,\n alt,\n accessibilityLabel,\n width,\n height,\n aspectRatio,\n fit,\n resizeMode,\n radius,\n style,\n testID,\n onError,\n}: ImageProps) {\n const { theme } = useTheme();\n const [showFallback, setShowFallback] = React.useState(false);\n const primaryKey = React.useMemo(() => getSourceKey(source), [source]);\n\n React.useEffect(() => {\n setShowFallback(false);\n }, [primaryKey]);\n\n const normalizedPrimary = normalizeSource(source);\n const normalizedFallback = normalizeSource(fallbackSource);\n const resolvedSource = resolveRenderedSource(normalizedPrimary, normalizedFallback, showFallback);\n\n if (!resolvedSource) return null;\n\n const resolvedResizeMode = resolveResizeMode(fit, resizeMode);\n const resolvedAccessibilityLabel = resolveAccessibilityLabel(alt, accessibilityLabel);\n const resolvedAlt = resolveAlt(alt, accessibilityLabel);\n const resolvedStyle = resolveImageStyle(theme, { width, height, aspectRatio, radius });\n\n return (\n <RNImage\n accessibilityLabel={resolvedAccessibilityLabel}\n accessibilityRole=\"image\"\n alt={resolvedAlt}\n onError={(event) => {\n onError?.(event);\n\n if (!showFallback && normalizedFallback) {\n setShowFallback(true);\n }\n }}\n resizeMode={resolvedResizeMode}\n source={resolvedSource}\n style={[resolvedStyle, style]}\n testID={testID}\n />\n );\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export { Image } from './Image';
2
+ export type { ImageFit, ImageProps, SurfaceImageSource } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/primitives/image/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { Image } from './Image';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/primitives/image/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC","sourcesContent":["export { Image } from './Image';\nexport type { ImageFit, ImageProps, SurfaceImageSource } from './types';\n"]}
@@ -0,0 +1,12 @@
1
+ import type { ImageResizeMode, ImageSourcePropType } from 'react-native';
2
+ import type { ImageStyle } from 'react-native';
3
+ import type { SurfaceTheme } from '../../theme/types';
4
+ import type { ImageProps, SurfaceImageSource } from './types';
5
+ export declare function normalizeSource(source: SurfaceImageSource | null | undefined): ImageSourcePropType | undefined;
6
+ export declare function resolveRenderedSource(normalizedPrimary: ImageSourcePropType | undefined, normalizedFallback: ImageSourcePropType | undefined, showFallback: boolean): ImageSourcePropType | undefined;
7
+ export declare function getSourceKey(source: SurfaceImageSource | null | undefined): string | null;
8
+ export declare function resolveResizeMode(fit: ImageProps['fit'], resizeMode: ImageProps['resizeMode']): ImageResizeMode;
9
+ export declare function resolveAccessibilityLabel(alt: ImageProps['alt'], accessibilityLabel: ImageProps['accessibilityLabel']): string | undefined;
10
+ export declare function resolveAlt(alt: ImageProps['alt'], accessibilityLabel: ImageProps['accessibilityLabel']): string | undefined;
11
+ export declare function resolveImageStyle(theme: SurfaceTheme, props: Pick<ImageProps, 'width' | 'height' | 'aspectRatio' | 'radius'>): ImageStyle;
12
+ //# sourceMappingURL=resolveImageProps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveImageProps.d.ts","sourceRoot":"","sources":["../../../src/primitives/image/resolveImageProps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE9D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAC5C,mBAAmB,GAAG,SAAS,CAIjC;AAED,wBAAgB,qBAAqB,CACnC,iBAAiB,EAAE,mBAAmB,GAAG,SAAS,EAClD,kBAAkB,EAAE,mBAAmB,GAAG,SAAS,EACnD,YAAY,EAAE,OAAO,GACpB,mBAAmB,GAAG,SAAS,CAGjC;AAYD,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAmBzF;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,UAAU,EAAE,UAAU,CAAC,YAAY,CAAC,GACnC,eAAe,CAEjB;AAED,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,kBAAkB,EAAE,UAAU,CAAC,oBAAoB,CAAC,GACnD,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,kBAAkB,EAAE,UAAU,CAAC,oBAAoB,CAAC,GACnD,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,QAAQ,GAAG,aAAa,GAAG,QAAQ,CAAC,GACrE,UAAU,CAYZ"}
@@ -0,0 +1,67 @@
1
+ import { resolveToken } from '../../theme/resolveToken';
2
+ export function normalizeSource(source) {
3
+ if (source === null || source === undefined)
4
+ return undefined;
5
+ if (typeof source === 'string')
6
+ return { uri: source };
7
+ return source;
8
+ }
9
+ export function resolveRenderedSource(normalizedPrimary, normalizedFallback, showFallback) {
10
+ if (showFallback || !normalizedPrimary)
11
+ return normalizedFallback;
12
+ return normalizedPrimary;
13
+ }
14
+ function getUriFromSource(source) {
15
+ if (Array.isArray(source)) {
16
+ const [first] = source;
17
+ return first?.uri;
18
+ }
19
+ if (typeof source === 'number')
20
+ return undefined;
21
+ return source.uri;
22
+ }
23
+ export function getSourceKey(source) {
24
+ if (source === null || source === undefined)
25
+ return null;
26
+ if (typeof source === 'string')
27
+ return `uri:${source}`;
28
+ if (typeof source === 'number')
29
+ return `asset:${source}`;
30
+ if (Array.isArray(source)) {
31
+ const parts = source
32
+ .map((item) => item.uri)
33
+ .filter((value) => typeof value === 'string');
34
+ return `uri-set:${parts.join('|')}`;
35
+ }
36
+ const uri = getUriFromSource(source);
37
+ if (uri)
38
+ return `uri:${uri}`;
39
+ try {
40
+ return `src:${JSON.stringify(source)}`;
41
+ }
42
+ catch {
43
+ return 'src:unknown';
44
+ }
45
+ }
46
+ export function resolveResizeMode(fit, resizeMode) {
47
+ return fit ?? resizeMode ?? 'cover';
48
+ }
49
+ export function resolveAccessibilityLabel(alt, accessibilityLabel) {
50
+ return accessibilityLabel ?? alt;
51
+ }
52
+ export function resolveAlt(alt, accessibilityLabel) {
53
+ return alt ?? accessibilityLabel;
54
+ }
55
+ export function resolveImageStyle(theme, props) {
56
+ const width = resolveToken(theme.spacing, props.width);
57
+ const height = resolveToken(theme.spacing, props.height);
58
+ const borderRadius = resolveToken(theme.radii, props.radius);
59
+ return {
60
+ width,
61
+ height,
62
+ aspectRatio: props.aspectRatio,
63
+ borderRadius,
64
+ overflow: borderRadius !== undefined ? 'hidden' : undefined,
65
+ };
66
+ }
67
+ //# sourceMappingURL=resolveImageProps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveImageProps.js","sourceRoot":"","sources":["../../../src/primitives/image/resolveImageProps.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageResizeMode, ImageSourcePropType } from 'react-native';\nimport type { ImageStyle } from 'react-native';\n\nimport { resolveToken } from '../../theme/resolveToken';\nimport type { SurfaceTheme } from '../../theme/types';\nimport type { ImageProps, SurfaceImageSource } from './types';\n\nexport function normalizeSource(\n source: SurfaceImageSource | null | undefined,\n): ImageSourcePropType | undefined {\n if (source === null || source === undefined) return undefined;\n if (typeof source === 'string') return { uri: source };\n return source;\n}\n\nexport function resolveRenderedSource(\n normalizedPrimary: ImageSourcePropType | undefined,\n normalizedFallback: ImageSourcePropType | undefined,\n showFallback: boolean,\n): ImageSourcePropType | undefined {\n if (showFallback || !normalizedPrimary) return normalizedFallback;\n return normalizedPrimary;\n}\n\nfunction getUriFromSource(source: ImageSourcePropType): string | undefined {\n if (Array.isArray(source)) {\n const [first] = source;\n return first?.uri;\n }\n\n if (typeof source === 'number') return undefined;\n return source.uri;\n}\n\nexport function getSourceKey(source: SurfaceImageSource | null | undefined): string | null {\n if (source === null || source === undefined) return null;\n if (typeof source === 'string') return `uri:${source}`;\n if (typeof source === 'number') return `asset:${source}`;\n if (Array.isArray(source)) {\n const parts = source\n .map((item) => item.uri)\n .filter((value): value is string => typeof value === 'string');\n return `uri-set:${parts.join('|')}`;\n }\n\n const uri = getUriFromSource(source);\n if (uri) return `uri:${uri}`;\n\n try {\n return `src:${JSON.stringify(source)}`;\n } catch {\n return 'src:unknown';\n }\n}\n\nexport function resolveResizeMode(\n fit: ImageProps['fit'],\n resizeMode: ImageProps['resizeMode'],\n): ImageResizeMode {\n return fit ?? resizeMode ?? 'cover';\n}\n\nexport function resolveAccessibilityLabel(\n alt: ImageProps['alt'],\n accessibilityLabel: ImageProps['accessibilityLabel'],\n): string | undefined {\n return accessibilityLabel ?? alt;\n}\n\nexport function resolveAlt(\n alt: ImageProps['alt'],\n accessibilityLabel: ImageProps['accessibilityLabel'],\n): string | undefined {\n return alt ?? accessibilityLabel;\n}\n\nexport function resolveImageStyle(\n theme: SurfaceTheme,\n props: Pick<ImageProps, 'width' | 'height' | 'aspectRatio' | 'radius'>,\n): ImageStyle {\n const width = resolveToken(theme.spacing, props.width);\n const height = resolveToken(theme.spacing, props.height);\n const borderRadius = resolveToken(theme.radii, props.radius);\n\n return {\n width,\n height,\n aspectRatio: props.aspectRatio,\n borderRadius,\n overflow: borderRadius !== undefined ? 'hidden' : undefined,\n };\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import type { ImageProps as ReactNativeImageProps, ImageResizeMode, ImageSourcePropType } from 'react-native';
2
+ import type { ImageStyle, StyleProp } from 'react-native';
3
+ import type { SurfaceTheme } from '../../theme/types';
4
+ export type SurfaceImageSource = string | ImageSourcePropType;
5
+ export type ImageFit = ImageResizeMode;
6
+ export interface ImageProps {
7
+ source?: SurfaceImageSource | null;
8
+ fallbackSource?: SurfaceImageSource | null;
9
+ alt?: string;
10
+ accessibilityLabel?: string;
11
+ width?: number | string;
12
+ height?: number | string;
13
+ aspectRatio?: number;
14
+ fit?: ImageFit;
15
+ resizeMode?: ImageResizeMode;
16
+ radius?: number | keyof SurfaceTheme['radii'];
17
+ style?: StyleProp<ImageStyle>;
18
+ testID?: string;
19
+ onError?: ReactNativeImageProps['onError'];
20
+ }
21
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/primitives/image/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,IAAI,qBAAqB,EACnC,eAAe,EACf,mBAAmB,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,mBAAmB,CAAC;AAE9D,MAAM,MAAM,QAAQ,GAAG,eAAe,CAAC;AAEvC,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnC,cAAc,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;CAC5C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/primitives/image/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ImageProps as ReactNativeImageProps,\n ImageResizeMode,\n ImageSourcePropType,\n} from 'react-native';\nimport type { ImageStyle, StyleProp } from 'react-native';\n\nimport type { SurfaceTheme } from '../../theme/types';\n\nexport type SurfaceImageSource = string | ImageSourcePropType;\n\nexport type ImageFit = ImageResizeMode;\n\nexport interface ImageProps {\n source?: SurfaceImageSource | null;\n fallbackSource?: SurfaceImageSource | null;\n alt?: string;\n accessibilityLabel?: string;\n width?: number | string;\n height?: number | string;\n aspectRatio?: number;\n fit?: ImageFit;\n resizeMode?: ImageResizeMode;\n radius?: number | keyof SurfaceTheme['radii'];\n style?: StyleProp<ImageStyle>;\n testID?: string;\n onError?: ReactNativeImageProps['onError'];\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/surface",
3
3
  "type": "module",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "description": "Stable UI foundation for React Native and React Native Web.",
6
6
  "homepage": "https://github.com/ankhorage/surface#readme",
7
7
  "bugs": {
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@ankhorage/color-theory": "^0.0.4",
45
- "@ankhorage/contracts": "^1.1.0"
45
+ "@ankhorage/contracts": "^1.2.0"
46
46
  },
47
47
  "files": [
48
48
  "dist",
package/src/index.test.ts CHANGED
@@ -33,6 +33,7 @@ const expectedRootExports = [
33
33
  "export { Tooltip } from './components/tooltip';",
34
34
  "export * from './core/responsive';",
35
35
  "export * from './layout';",
36
+ "export { Image } from './primitives/image';",
36
37
  "export * from './theme';",
37
38
  ] as const;
38
39
 
package/src/index.ts CHANGED
@@ -66,6 +66,8 @@ export type { HeadingLevel, HeadingProps } from './primitives/heading';
66
66
  export { Heading } from './primitives/heading';
67
67
  export type { IconProps, IconProvider } from './primitives/icon';
68
68
  export { Icon } from './primitives/icon';
69
+ export type { ImageFit, ImageProps, SurfaceImageSource } from './primitives/image';
70
+ export { Image } from './primitives/image';
69
71
  export type { TextProps } from './primitives/text';
70
72
  export { Text } from './primitives/text';
71
73
  export * from './theme';
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+ import { Image as RNImage } from 'react-native';
3
+
4
+ import { useTheme } from '../../theme/ThemeContext';
5
+ import {
6
+ getSourceKey,
7
+ normalizeSource,
8
+ resolveAccessibilityLabel,
9
+ resolveAlt,
10
+ resolveImageStyle,
11
+ resolveRenderedSource,
12
+ resolveResizeMode,
13
+ } from './resolveImageProps';
14
+ import type { ImageProps } from './types';
15
+
16
+ export function Image({
17
+ source,
18
+ fallbackSource,
19
+ alt,
20
+ accessibilityLabel,
21
+ width,
22
+ height,
23
+ aspectRatio,
24
+ fit,
25
+ resizeMode,
26
+ radius,
27
+ style,
28
+ testID,
29
+ onError,
30
+ }: ImageProps) {
31
+ const { theme } = useTheme();
32
+ const [showFallback, setShowFallback] = React.useState(false);
33
+ const primaryKey = React.useMemo(() => getSourceKey(source), [source]);
34
+
35
+ React.useEffect(() => {
36
+ setShowFallback(false);
37
+ }, [primaryKey]);
38
+
39
+ const normalizedPrimary = normalizeSource(source);
40
+ const normalizedFallback = normalizeSource(fallbackSource);
41
+ const resolvedSource = resolveRenderedSource(normalizedPrimary, normalizedFallback, showFallback);
42
+
43
+ if (!resolvedSource) return null;
44
+
45
+ const resolvedResizeMode = resolveResizeMode(fit, resizeMode);
46
+ const resolvedAccessibilityLabel = resolveAccessibilityLabel(alt, accessibilityLabel);
47
+ const resolvedAlt = resolveAlt(alt, accessibilityLabel);
48
+ const resolvedStyle = resolveImageStyle(theme, { width, height, aspectRatio, radius });
49
+
50
+ return (
51
+ <RNImage
52
+ accessibilityLabel={resolvedAccessibilityLabel}
53
+ accessibilityRole="image"
54
+ alt={resolvedAlt}
55
+ onError={(event) => {
56
+ onError?.(event);
57
+
58
+ if (!showFallback && normalizedFallback) {
59
+ setShowFallback(true);
60
+ }
61
+ }}
62
+ resizeMode={resolvedResizeMode}
63
+ source={resolvedSource}
64
+ style={[resolvedStyle, style]}
65
+ testID={testID}
66
+ />
67
+ );
68
+ }
@@ -0,0 +1,2 @@
1
+ export { Image } from './Image';
2
+ export type { ImageFit, ImageProps, SurfaceImageSource } from './types';
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { createTheme } from '../../theme/createTheme';
4
+ import {
5
+ getSourceKey,
6
+ normalizeSource,
7
+ resolveImageStyle,
8
+ resolveRenderedSource,
9
+ resolveResizeMode,
10
+ } from './resolveImageProps';
11
+
12
+ describe('normalizeSource', () => {
13
+ it('normalizes string sources into uri objects', () => {
14
+ expect(normalizeSource('https://example.com/image.png')).toEqual({
15
+ uri: 'https://example.com/image.png',
16
+ });
17
+ });
18
+
19
+ it('passes through react-native image sources', () => {
20
+ expect(normalizeSource({ uri: 'https://example.com/image.png' })).toEqual({
21
+ uri: 'https://example.com/image.png',
22
+ });
23
+ });
24
+
25
+ it('returns undefined for nullish sources', () => {
26
+ expect(normalizeSource(null)).toBeUndefined();
27
+ expect(normalizeSource(undefined)).toBeUndefined();
28
+ });
29
+ });
30
+
31
+ describe('resolveResizeMode', () => {
32
+ it('prefers fit over resizeMode', () => {
33
+ expect(resolveResizeMode('contain', 'cover')).toBe('contain');
34
+ });
35
+
36
+ it('defaults to cover when both are absent', () => {
37
+ expect(resolveResizeMode(undefined, undefined)).toBe('cover');
38
+ });
39
+ });
40
+
41
+ describe('resolveRenderedSource', () => {
42
+ it('renders fallback when the primary source is absent', () => {
43
+ const normalizedFallback = normalizeSource('https://example.com/fallback.png');
44
+ expect(resolveRenderedSource(undefined, normalizedFallback, false)).toEqual(normalizedFallback);
45
+ });
46
+ });
47
+
48
+ describe('getSourceKey', () => {
49
+ it('derives stable keys for string and uri sources', () => {
50
+ expect(getSourceKey('https://example.com/image.png')).toBe('uri:https://example.com/image.png');
51
+ expect(getSourceKey({ uri: 'https://example.com/image.png' })).toBe(
52
+ 'uri:https://example.com/image.png',
53
+ );
54
+ });
55
+
56
+ it('derives stable keys for asset sources', () => {
57
+ expect(getSourceKey(12)).toBe('asset:12');
58
+ });
59
+
60
+ it('derives stable keys for uri sets', () => {
61
+ expect(getSourceKey([{ uri: 'a' }, { uri: 'b' }])).toBe('uri-set:a|b');
62
+ });
63
+ });
64
+
65
+ describe('resolveImageStyle', () => {
66
+ it('resolves spacing tokens for width/height and preserves raw strings', () => {
67
+ const theme = createTheme();
68
+ const style = resolveImageStyle(theme, { width: 'm', height: '100%' });
69
+
70
+ expect(style.width).toBe(theme.spacing.m);
71
+ expect(style.height).toBe('100%');
72
+ });
73
+
74
+ it('resolves radius tokens for borderRadius', () => {
75
+ const theme = createTheme();
76
+ const style = resolveImageStyle(theme, { radius: 'full' });
77
+
78
+ expect(style.borderRadius).toBe(theme.radii.full);
79
+ expect(style.overflow).toBe('hidden');
80
+ });
81
+ });
@@ -0,0 +1,92 @@
1
+ import type { ImageResizeMode, ImageSourcePropType } from 'react-native';
2
+ import type { ImageStyle } from 'react-native';
3
+
4
+ import { resolveToken } from '../../theme/resolveToken';
5
+ import type { SurfaceTheme } from '../../theme/types';
6
+ import type { ImageProps, SurfaceImageSource } from './types';
7
+
8
+ export function normalizeSource(
9
+ source: SurfaceImageSource | null | undefined,
10
+ ): ImageSourcePropType | undefined {
11
+ if (source === null || source === undefined) return undefined;
12
+ if (typeof source === 'string') return { uri: source };
13
+ return source;
14
+ }
15
+
16
+ export function resolveRenderedSource(
17
+ normalizedPrimary: ImageSourcePropType | undefined,
18
+ normalizedFallback: ImageSourcePropType | undefined,
19
+ showFallback: boolean,
20
+ ): ImageSourcePropType | undefined {
21
+ if (showFallback || !normalizedPrimary) return normalizedFallback;
22
+ return normalizedPrimary;
23
+ }
24
+
25
+ function getUriFromSource(source: ImageSourcePropType): string | undefined {
26
+ if (Array.isArray(source)) {
27
+ const [first] = source;
28
+ return first?.uri;
29
+ }
30
+
31
+ if (typeof source === 'number') return undefined;
32
+ return source.uri;
33
+ }
34
+
35
+ export function getSourceKey(source: SurfaceImageSource | null | undefined): string | null {
36
+ if (source === null || source === undefined) return null;
37
+ if (typeof source === 'string') return `uri:${source}`;
38
+ if (typeof source === 'number') return `asset:${source}`;
39
+ if (Array.isArray(source)) {
40
+ const parts = source
41
+ .map((item) => item.uri)
42
+ .filter((value): value is string => typeof value === 'string');
43
+ return `uri-set:${parts.join('|')}`;
44
+ }
45
+
46
+ const uri = getUriFromSource(source);
47
+ if (uri) return `uri:${uri}`;
48
+
49
+ try {
50
+ return `src:${JSON.stringify(source)}`;
51
+ } catch {
52
+ return 'src:unknown';
53
+ }
54
+ }
55
+
56
+ export function resolveResizeMode(
57
+ fit: ImageProps['fit'],
58
+ resizeMode: ImageProps['resizeMode'],
59
+ ): ImageResizeMode {
60
+ return fit ?? resizeMode ?? 'cover';
61
+ }
62
+
63
+ export function resolveAccessibilityLabel(
64
+ alt: ImageProps['alt'],
65
+ accessibilityLabel: ImageProps['accessibilityLabel'],
66
+ ): string | undefined {
67
+ return accessibilityLabel ?? alt;
68
+ }
69
+
70
+ export function resolveAlt(
71
+ alt: ImageProps['alt'],
72
+ accessibilityLabel: ImageProps['accessibilityLabel'],
73
+ ): string | undefined {
74
+ return alt ?? accessibilityLabel;
75
+ }
76
+
77
+ export function resolveImageStyle(
78
+ theme: SurfaceTheme,
79
+ props: Pick<ImageProps, 'width' | 'height' | 'aspectRatio' | 'radius'>,
80
+ ): ImageStyle {
81
+ const width = resolveToken(theme.spacing, props.width);
82
+ const height = resolveToken(theme.spacing, props.height);
83
+ const borderRadius = resolveToken(theme.radii, props.radius);
84
+
85
+ return {
86
+ width,
87
+ height,
88
+ aspectRatio: props.aspectRatio,
89
+ borderRadius,
90
+ overflow: borderRadius !== undefined ? 'hidden' : undefined,
91
+ };
92
+ }
@@ -0,0 +1,28 @@
1
+ import type {
2
+ ImageProps as ReactNativeImageProps,
3
+ ImageResizeMode,
4
+ ImageSourcePropType,
5
+ } from 'react-native';
6
+ import type { ImageStyle, StyleProp } from 'react-native';
7
+
8
+ import type { SurfaceTheme } from '../../theme/types';
9
+
10
+ export type SurfaceImageSource = string | ImageSourcePropType;
11
+
12
+ export type ImageFit = ImageResizeMode;
13
+
14
+ export interface ImageProps {
15
+ source?: SurfaceImageSource | null;
16
+ fallbackSource?: SurfaceImageSource | null;
17
+ alt?: string;
18
+ accessibilityLabel?: string;
19
+ width?: number | string;
20
+ height?: number | string;
21
+ aspectRatio?: number;
22
+ fit?: ImageFit;
23
+ resizeMode?: ImageResizeMode;
24
+ radius?: number | keyof SurfaceTheme['radii'];
25
+ style?: StyleProp<ImageStyle>;
26
+ testID?: string;
27
+ onError?: ReactNativeImageProps['onError'];
28
+ }