@ankhorage/zora 2.13.0 → 2.13.2

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.13.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 954029e: Add an explicit fill sizing mode to SidebarLayout so bounded shells can preserve child-owned
8
+ scrolling while existing content-flow layouts keep their current behavior.
9
+
10
+ ## 2.13.1
11
+
12
+ ### Patch Changes
13
+
14
+ - 400863f: Define Screen scroll ownership explicitly: normal screens own scrolling, while `scroll={false}` preserves a bounded viewport for child-owned scroll and gesture surfaces. Require `@ankhorage/surface` 2.2.1 or newer so Stack preserves direct-child flex and min-size semantics throughout that viewport chain.
15
+
3
16
  ## 2.13.0
4
17
 
5
18
  ### Minor Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # ZORA
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v2.12.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v2.13.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.
9
9
 
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
2
  import type { ScreenProps } from './types';
3
- /***
4
- * Page-level container with standard background and optional scroll behavior.
3
+ /**
4
+ * Outer content boundary for one app screen.
5
5
  *
6
-
6
+ * By default Screen owns normal vertical scrolling. Set `scroll={false}` to preserve a bounded
7
+ * viewport and delegate scroll or gesture ownership to specialized children such as lists, maps,
8
+ * chats, canvases, or editors.
7
9
  */
8
10
  export declare const Screen: (props: ScreenProps) => React.ReactElement | null;
9
11
  //# sourceMappingURL=Screen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Screen.d.ts","sourceRoot":"","sources":["../../../src/layout/screen/Screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAoC3C;;;;GAIG;AACH,eAAO,MAAM,MAAM,mDAAkC,CAAC"}
1
+ {"version":3,"file":"Screen.d.ts","sourceRoot":"","sources":["../../../src/layout/screen/Screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAiC3C;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,mDAAkC,CAAC"}
@@ -4,25 +4,27 @@ import { Box, Container, Stack } from '../../foundation';
4
4
  import { resolvePageMaxWidth } from '../../internal/recipes';
5
5
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
6
  function ScreenInner({ themeId: _themeId, mode: _mode, interactionPolicy: _interactionPolicy, children, footer, scroll = true, width = 'default', testID, }) {
7
- const content = (<Container maxWidth={resolvePageMaxWidth(width)} py="xl" testID={testID}>
8
- <Stack gap="l">
7
+ if (!scroll) {
8
+ return (<Box bg="background" flex={1} minHeight={0} minWidth={0} testID={testID}>
9
9
  {children}
10
10
  {footer}
11
- </Stack>
12
- </Container>);
13
- if (!scroll) {
14
- return (<Box bg="background" flex={1}>
15
- {content}
16
11
  </Box>);
17
12
  }
18
- return (<ScrollArea bg="background" style={{ flex: 1 }}>
19
- {content}
13
+ return (<ScrollArea bg="background" style={{ flex: 1, minHeight: 0, minWidth: 0 }}>
14
+ <Container maxWidth={resolvePageMaxWidth(width)} py="xl" testID={testID}>
15
+ <Stack gap="l">
16
+ {children}
17
+ {footer}
18
+ </Stack>
19
+ </Container>
20
20
  </ScrollArea>);
21
21
  }
22
- /***
23
- * Page-level container with standard background and optional scroll behavior.
22
+ /**
23
+ * Outer content boundary for one app screen.
24
24
  *
25
-
25
+ * By default Screen owns normal vertical scrolling. Set `scroll={false}` to preserve a bounded
26
+ * viewport and delegate scroll or gesture ownership to specialized children such as lists, maps,
27
+ * chats, canvases, or editors.
26
28
  */
27
29
  export const Screen = withZoraThemeScope(ScreenInner);
28
30
  //# sourceMappingURL=Screen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Screen.js","sourceRoot":"","sources":["../../../src/layout/screen/Screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGpE,SAAS,WAAW,CAAC,EACnB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,iBAAiB,EAAE,kBAAkB,EACrC,QAAQ,EACR,MAAM,EACN,MAAM,GAAG,IAAI,EACb,KAAK,GAAG,SAAS,EACjB,MAAM,GACM;IACZ,MAAM,OAAO,GAAG,CACd,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CACtE;MAAA,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CACZ;QAAA,CAAC,QAAQ,CACT;QAAA,CAAC,MAAM,CACT;MAAA,EAAE,KAAK,CACT;IAAA,EAAE,SAAS,CAAC,CACb,CAAC;IAEF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CACL,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAC3B;QAAA,CAAC,OAAO,CACV;MAAA,EAAE,GAAG,CAAC,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAC7C;MAAA,CAAC,OAAO,CACV;IAAA,EAAE,UAAU,CAAC,CACd,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC","sourcesContent":["import { ScrollArea } from '@ankhorage/surface';\nimport React from 'react';\n\nimport { Box, Container, Stack } from '../../foundation';\nimport { resolvePageMaxWidth } from '../../internal/recipes';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport type { ScreenProps } from './types';\n\nfunction ScreenInner({\n themeId: _themeId,\n mode: _mode,\n interactionPolicy: _interactionPolicy,\n children,\n footer,\n scroll = true,\n width = 'default',\n testID,\n}: ScreenProps) {\n const content = (\n <Container maxWidth={resolvePageMaxWidth(width)} py=\"xl\" testID={testID}>\n <Stack gap=\"l\">\n {children}\n {footer}\n </Stack>\n </Container>\n );\n\n if (!scroll) {\n return (\n <Box bg=\"background\" flex={1}>\n {content}\n </Box>\n );\n }\n\n return (\n <ScrollArea bg=\"background\" style={{ flex: 1 }}>\n {content}\n </ScrollArea>\n );\n}\n\n/***\n * Page-level container with standard background and optional scroll behavior.\n *\n \n */\nexport const Screen = withZoraThemeScope(ScreenInner);\n"]}
1
+ {"version":3,"file":"Screen.js","sourceRoot":"","sources":["../../../src/layout/screen/Screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGpE,SAAS,WAAW,CAAC,EACnB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,iBAAiB,EAAE,kBAAkB,EACrC,QAAQ,EACR,MAAM,EACN,MAAM,GAAG,IAAI,EACb,KAAK,GAAG,SAAS,EACjB,MAAM,GACM;IACZ,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CACL,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CACtE;QAAA,CAAC,QAAQ,CACT;QAAA,CAAC,MAAM,CACT;MAAA,EAAE,GAAG,CAAC,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CACxE;MAAA,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CACtE;QAAA,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CACZ;UAAA,CAAC,QAAQ,CACT;UAAA,CAAC,MAAM,CACT;QAAA,EAAE,KAAK,CACT;MAAA,EAAE,SAAS,CACb;IAAA,EAAE,UAAU,CAAC,CACd,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC","sourcesContent":["import { ScrollArea } from '@ankhorage/surface';\nimport React from 'react';\n\nimport { Box, Container, Stack } from '../../foundation';\nimport { resolvePageMaxWidth } from '../../internal/recipes';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport type { ScreenProps } from './types';\n\nfunction ScreenInner({\n themeId: _themeId,\n mode: _mode,\n interactionPolicy: _interactionPolicy,\n children,\n footer,\n scroll = true,\n width = 'default',\n testID,\n}: ScreenProps) {\n if (!scroll) {\n return (\n <Box bg=\"background\" flex={1} minHeight={0} minWidth={0} testID={testID}>\n {children}\n {footer}\n </Box>\n );\n }\n\n return (\n <ScrollArea bg=\"background\" style={{ flex: 1, minHeight: 0, minWidth: 0 }}>\n <Container maxWidth={resolvePageMaxWidth(width)} py=\"xl\" testID={testID}>\n <Stack gap=\"l\">\n {children}\n {footer}\n </Stack>\n </Container>\n </ScrollArea>\n );\n}\n\n/**\n * Outer content boundary for one app screen.\n *\n * By default Screen owns normal vertical scrolling. Set `scroll={false}` to preserve a bounded\n * viewport and delegate scroll or gesture ownership to specialized children such as lists, maps,\n * chats, canvases, or editors.\n */\nexport const Screen = withZoraThemeScope(ScreenInner);\n"]}
@@ -3,7 +3,9 @@ import type { SidebarLayoutProps } from './types';
3
3
  /***
4
4
  * Responsive layout with a sidebar and main content area (and optional aside).
5
5
  *
6
-
6
+ * `sizing="content"` keeps normal content-flow behavior. Use `sizing="fill"` when the layout
7
+ * should fill an already bounded parent so sidebar/content children receive the available space
8
+ * for their own scrolling or gesture ownership.
7
9
  */
8
10
  export declare const SidebarLayout: (props: SidebarLayoutProps) => React.ReactElement | null;
9
11
  //# sourceMappingURL=SidebarLayout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SidebarLayout.d.ts","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/SidebarLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAwBlD;;;;GAIG;AACH,eAAO,MAAM,aAAa,0DAAyC,CAAC"}
1
+ {"version":3,"file":"SidebarLayout.d.ts","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/SidebarLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAiClD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,0DAAyC,CAAC"}
@@ -1,19 +1,27 @@
1
1
  import React from 'react';
2
2
  import { Box, Stack } from '../../foundation';
3
3
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
4
- function SidebarLayoutInner({ themeId: _themeId, mode: _mode, interactionPolicy: _interactionPolicy, sidebar, children, aside, sidebarWidth = 280, asideWidth = 280, testID, }) {
5
- return (<Stack direction={{ base: 'column', lg: 'row' }} gap="l" testID={testID} align="flex-start">
6
- <Box width={{ base: '100%', lg: sidebarWidth }}>{sidebar}</Box>
7
- <Box flex={1} width="100%">
4
+ import { resolveSidebarLayoutSizing } from './resolveSidebarLayoutSizing';
5
+ function SidebarLayoutInner({ themeId: _themeId, mode: _mode, interactionPolicy: _interactionPolicy, sidebar, children, aside, sidebarWidth = 280, asideWidth = 280, sizing, testID, }) {
6
+ const layoutSizing = resolveSidebarLayoutSizing(sizing);
7
+ return (<Stack {...layoutSizing.root} direction={{ base: 'column', lg: 'row' }} gap="l" testID={testID}>
8
+ <Box {...layoutSizing.child} width={{ base: '100%', lg: sidebarWidth }}>
9
+ {sidebar}
10
+ </Box>
11
+ <Box {...layoutSizing.child} flex={1} width="100%">
8
12
  {children}
9
13
  </Box>
10
- {aside ? <Box width={{ base: '100%', lg: asideWidth }}>{aside}</Box> : null}
14
+ {aside ? (<Box {...layoutSizing.child} width={{ base: '100%', lg: asideWidth }}>
15
+ {aside}
16
+ </Box>) : null}
11
17
  </Stack>);
12
18
  }
13
19
  /***
14
20
  * Responsive layout with a sidebar and main content area (and optional aside).
15
21
  *
16
-
22
+ * `sizing="content"` keeps normal content-flow behavior. Use `sizing="fill"` when the layout
23
+ * should fill an already bounded parent so sidebar/content children receive the available space
24
+ * for their own scrolling or gesture ownership.
17
25
  */
18
26
  export const SidebarLayout = withZoraThemeScope(SidebarLayoutInner);
19
27
  //# sourceMappingURL=SidebarLayout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SidebarLayout.js","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/SidebarLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGpE,SAAS,kBAAkB,CAAC,EAC1B,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,iBAAiB,EAAE,kBAAkB,EACrC,OAAO,EACP,QAAQ,EACR,KAAK,EACL,YAAY,GAAG,GAAG,EAClB,UAAU,GAAG,GAAG,EAChB,MAAM,GACa;IACnB,OAAO,CACL,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,YAAY,CACzF;MAAA,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,CAC9D;MAAA,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CACxB;QAAA,CAAC,QAAQ,CACX;MAAA,EAAE,GAAG,CACL;MAAA,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAC7E;IAAA,EAAE,KAAK,CAAC,CACT,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,CAAC","sourcesContent":["import React from 'react';\n\nimport { Box, Stack } from '../../foundation';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport type { SidebarLayoutProps } from './types';\n\nfunction SidebarLayoutInner({\n themeId: _themeId,\n mode: _mode,\n interactionPolicy: _interactionPolicy,\n sidebar,\n children,\n aside,\n sidebarWidth = 280,\n asideWidth = 280,\n testID,\n}: SidebarLayoutProps) {\n return (\n <Stack direction={{ base: 'column', lg: 'row' }} gap=\"l\" testID={testID} align=\"flex-start\">\n <Box width={{ base: '100%', lg: sidebarWidth }}>{sidebar}</Box>\n <Box flex={1} width=\"100%\">\n {children}\n </Box>\n {aside ? <Box width={{ base: '100%', lg: asideWidth }}>{aside}</Box> : null}\n </Stack>\n );\n}\n\n/***\n * Responsive layout with a sidebar and main content area (and optional aside).\n *\n \n */\nexport const SidebarLayout = withZoraThemeScope(SidebarLayoutInner);\n"]}
1
+ {"version":3,"file":"SidebarLayout.js","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/SidebarLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAG1E,SAAS,kBAAkB,CAAC,EAC1B,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,iBAAiB,EAAE,kBAAkB,EACrC,OAAO,EACP,QAAQ,EACR,KAAK,EACL,YAAY,GAAG,GAAG,EAClB,UAAU,GAAG,GAAG,EAChB,MAAM,EACN,MAAM,GACa;IACnB,MAAM,YAAY,GAAG,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAExD,OAAO,CACL,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAC7F;MAAA,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,CACrE;QAAA,CAAC,OAAO,CACV;MAAA,EAAE,GAAG,CACL;MAAA,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAChD;QAAA,CAAC,QAAQ,CACX;MAAA,EAAE,GAAG,CACL;MAAA,CAAC,KAAK,CAAC,CAAC,CAAC,CACP,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CACnE;UAAA,CAAC,KAAK,CACR;QAAA,EAAE,GAAG,CAAC,CACP,CAAC,CAAC,CAAC,IAAI,CACV;IAAA,EAAE,KAAK,CAAC,CACT,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,CAAC","sourcesContent":["import React from 'react';\n\nimport { Box, Stack } from '../../foundation';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport { resolveSidebarLayoutSizing } from './resolveSidebarLayoutSizing';\nimport type { SidebarLayoutProps } from './types';\n\nfunction SidebarLayoutInner({\n themeId: _themeId,\n mode: _mode,\n interactionPolicy: _interactionPolicy,\n sidebar,\n children,\n aside,\n sidebarWidth = 280,\n asideWidth = 280,\n sizing,\n testID,\n}: SidebarLayoutProps) {\n const layoutSizing = resolveSidebarLayoutSizing(sizing);\n\n return (\n <Stack {...layoutSizing.root} direction={{ base: 'column', lg: 'row' }} gap=\"l\" testID={testID}>\n <Box {...layoutSizing.child} width={{ base: '100%', lg: sidebarWidth }}>\n {sidebar}\n </Box>\n <Box {...layoutSizing.child} flex={1} width=\"100%\">\n {children}\n </Box>\n {aside ? (\n <Box {...layoutSizing.child} width={{ base: '100%', lg: asideWidth }}>\n {aside}\n </Box>\n ) : null}\n </Stack>\n );\n}\n\n/***\n * Responsive layout with a sidebar and main content area (and optional aside).\n *\n * `sizing=\"content\"` keeps normal content-flow behavior. Use `sizing=\"fill\"` when the layout\n * should fill an already bounded parent so sidebar/content children receive the available space\n * for their own scrolling or gesture ownership.\n */\nexport const SidebarLayout = withZoraThemeScope(SidebarLayoutInner);\n"]}
@@ -0,0 +1,14 @@
1
+ import type { SidebarLayoutSizing } from './types';
2
+ export declare function resolveSidebarLayoutSizing(sizing?: SidebarLayoutSizing): {
3
+ readonly root: {
4
+ readonly align: "flex-start" | "stretch";
5
+ readonly flex: 1 | undefined;
6
+ readonly minHeight: 0 | undefined;
7
+ readonly minWidth: 0 | undefined;
8
+ };
9
+ readonly child: {
10
+ readonly minHeight: 0 | undefined;
11
+ readonly minWidth: 0 | undefined;
12
+ };
13
+ };
14
+ //# sourceMappingURL=resolveSidebarLayoutSizing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveSidebarLayoutSizing.d.ts","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/resolveSidebarLayoutSizing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEnD,wBAAgB,0BAA0B,CAAC,MAAM,GAAE,mBAA+B;;;;;;;;;;;EAejF"}
@@ -0,0 +1,16 @@
1
+ export function resolveSidebarLayoutSizing(sizing = 'content') {
2
+ const fillsAvailableSpace = sizing === 'fill';
3
+ return {
4
+ root: {
5
+ align: fillsAvailableSpace ? 'stretch' : 'flex-start',
6
+ flex: fillsAvailableSpace ? 1 : undefined,
7
+ minHeight: fillsAvailableSpace ? 0 : undefined,
8
+ minWidth: fillsAvailableSpace ? 0 : undefined,
9
+ },
10
+ child: {
11
+ minHeight: fillsAvailableSpace ? 0 : undefined,
12
+ minWidth: fillsAvailableSpace ? 0 : undefined,
13
+ },
14
+ };
15
+ }
16
+ //# sourceMappingURL=resolveSidebarLayoutSizing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveSidebarLayoutSizing.js","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/resolveSidebarLayoutSizing.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,0BAA0B,CAAC,SAA8B,SAAS;IAChF,MAAM,mBAAmB,GAAG,MAAM,KAAK,MAAM,CAAC;IAE9C,OAAO;QACL,IAAI,EAAE;YACJ,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY;YACrD,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YACzC,SAAS,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YAC9C,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;SAC9C;QACD,KAAK,EAAE;YACL,SAAS,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YAC9C,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;SAC9C;KACO,CAAC;AACb,CAAC","sourcesContent":["import type { SidebarLayoutSizing } from './types';\n\nexport function resolveSidebarLayoutSizing(sizing: SidebarLayoutSizing = 'content') {\n const fillsAvailableSpace = sizing === 'fill';\n\n return {\n root: {\n align: fillsAvailableSpace ? 'stretch' : 'flex-start',\n flex: fillsAvailableSpace ? 1 : undefined,\n minHeight: fillsAvailableSpace ? 0 : undefined,\n minWidth: fillsAvailableSpace ? 0 : undefined,\n },\n child: {\n minHeight: fillsAvailableSpace ? 0 : undefined,\n minWidth: fillsAvailableSpace ? 0 : undefined,\n },\n } as const;\n}\n"]}
@@ -1,10 +1,12 @@
1
1
  import type React from 'react';
2
2
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
3
+ export type SidebarLayoutSizing = 'content' | 'fill';
3
4
  export interface SidebarLayoutProps extends ZoraBaseProps {
4
5
  sidebar: React.ReactNode;
5
6
  children?: React.ReactNode;
6
7
  aside?: React.ReactNode;
7
8
  sidebarWidth?: number;
8
9
  asideWidth?: number;
10
+ sizing?: SidebarLayoutSizing;
9
11
  }
10
12
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/types.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport interface SidebarLayoutProps extends ZoraBaseProps {\n sidebar: React.ReactNode;\n children?: React.ReactNode;\n aside?: React.ReactNode;\n sidebarWidth?: number;\n asideWidth?: number;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/layout/sidebar-layout/types.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport type SidebarLayoutSizing = 'content' | 'fill';\n\nexport interface SidebarLayoutProps extends ZoraBaseProps {\n sidebar: React.ReactNode;\n children?: React.ReactNode;\n aside?: React.ReactNode;\n sidebarWidth?: number;\n asideWidth?: number;\n sizing?: SidebarLayoutSizing;\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  export declare function useZoraTheme(): {
2
2
  theme: import("@ankhorage/surface").SurfaceTheme;
3
3
  mode: "light" | "dark";
4
- setThemeConfig: (config: Partial<import("@ankhorage/surface").ThemeConfig>) => void;
4
+ setThemeConfig: (config: Partial<import("@ankhorage/contracts").ThemeConfig>) => void;
5
5
  setMode: (mode: "light" | "dark") => void;
6
6
  _hasProvider?: boolean;
7
7
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora",
3
3
  "type": "module",
4
- "version": "2.13.0",
4
+ "version": "2.13.2",
5
5
  "description": "Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.",
6
6
  "homepage": "https://github.com/ankhorage/zora#readme",
7
7
  "bugs": {
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@ankhorage/color-theory": "^0.0.8",
45
45
  "@ankhorage/contracts": "^7.6.0",
46
- "@ankhorage/surface": "^2.2.0"
46
+ "@ankhorage/surface": "^2.2.1"
47
47
  },
48
48
  "files": [
49
49
  "dist",
@@ -0,0 +1,22 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ import { describe, expect, it } from 'bun:test';
4
+
5
+ describe('Screen scroll ownership contract', () => {
6
+ it('keeps scroll=false as a bounded delegated viewport', async () => {
7
+ const source = await readFile(new URL('./Screen.tsx', import.meta.url), 'utf8');
8
+
9
+ expect(source).toContain('if (!scroll)');
10
+ expect(source).toContain('flex={1} minHeight={0} minWidth={0}');
11
+ expect(source).toContain('{children}');
12
+ expect(source).not.toContain('{content}');
13
+ });
14
+
15
+ it('keeps normal scrolling owned by Screen', async () => {
16
+ const source = await readFile(new URL('./Screen.tsx', import.meta.url), 'utf8');
17
+
18
+ expect(source).toContain('<ScrollArea');
19
+ expect(source).toContain('style={{ flex: 1, minHeight: 0, minWidth: 0 }}');
20
+ expect(source).toContain('<Container maxWidth={resolvePageMaxWidth(width)}');
21
+ });
22
+ });
@@ -16,33 +16,32 @@ function ScreenInner({
16
16
  width = 'default',
17
17
  testID,
18
18
  }: ScreenProps) {
19
- const content = (
20
- <Container maxWidth={resolvePageMaxWidth(width)} py="xl" testID={testID}>
21
- <Stack gap="l">
22
- {children}
23
- {footer}
24
- </Stack>
25
- </Container>
26
- );
27
-
28
19
  if (!scroll) {
29
20
  return (
30
- <Box bg="background" flex={1}>
31
- {content}
21
+ <Box bg="background" flex={1} minHeight={0} minWidth={0} testID={testID}>
22
+ {children}
23
+ {footer}
32
24
  </Box>
33
25
  );
34
26
  }
35
27
 
36
28
  return (
37
- <ScrollArea bg="background" style={{ flex: 1 }}>
38
- {content}
29
+ <ScrollArea bg="background" style={{ flex: 1, minHeight: 0, minWidth: 0 }}>
30
+ <Container maxWidth={resolvePageMaxWidth(width)} py="xl" testID={testID}>
31
+ <Stack gap="l">
32
+ {children}
33
+ {footer}
34
+ </Stack>
35
+ </Container>
39
36
  </ScrollArea>
40
37
  );
41
38
  }
42
39
 
43
- /***
44
- * Page-level container with standard background and optional scroll behavior.
40
+ /**
41
+ * Outer content boundary for one app screen.
45
42
  *
46
-
43
+ * By default Screen owns normal vertical scrolling. Set `scroll={false}` to preserve a bounded
44
+ * viewport and delegate scroll or gesture ownership to specialized children such as lists, maps,
45
+ * chats, canvases, or editors.
47
46
  */
48
47
  export const Screen = withZoraThemeScope(ScreenInner);
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { resolveSidebarLayoutSizing } from './resolveSidebarLayoutSizing';
4
+
5
+ describe('SidebarLayout sizing contract', () => {
6
+ it('keeps content-flow sizing as the default', () => {
7
+ expect(resolveSidebarLayoutSizing()).toEqual({
8
+ root: {
9
+ align: 'flex-start',
10
+ flex: undefined,
11
+ minHeight: undefined,
12
+ minWidth: undefined,
13
+ },
14
+ child: {
15
+ minHeight: undefined,
16
+ minWidth: undefined,
17
+ },
18
+ });
19
+ });
20
+
21
+ it('fills a bounded parent for child-owned scroll areas', () => {
22
+ expect(resolveSidebarLayoutSizing('fill')).toEqual({
23
+ root: {
24
+ align: 'stretch',
25
+ flex: 1,
26
+ minHeight: 0,
27
+ minWidth: 0,
28
+ },
29
+ child: {
30
+ minHeight: 0,
31
+ minWidth: 0,
32
+ },
33
+ });
34
+ });
35
+ });
@@ -2,6 +2,7 @@ import React from 'react';
2
2
 
3
3
  import { Box, Stack } from '../../foundation';
4
4
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
5
+ import { resolveSidebarLayoutSizing } from './resolveSidebarLayoutSizing';
5
6
  import type { SidebarLayoutProps } from './types';
6
7
 
7
8
  function SidebarLayoutInner({
@@ -13,15 +14,24 @@ function SidebarLayoutInner({
13
14
  aside,
14
15
  sidebarWidth = 280,
15
16
  asideWidth = 280,
17
+ sizing,
16
18
  testID,
17
19
  }: SidebarLayoutProps) {
20
+ const layoutSizing = resolveSidebarLayoutSizing(sizing);
21
+
18
22
  return (
19
- <Stack direction={{ base: 'column', lg: 'row' }} gap="l" testID={testID} align="flex-start">
20
- <Box width={{ base: '100%', lg: sidebarWidth }}>{sidebar}</Box>
21
- <Box flex={1} width="100%">
23
+ <Stack {...layoutSizing.root} direction={{ base: 'column', lg: 'row' }} gap="l" testID={testID}>
24
+ <Box {...layoutSizing.child} width={{ base: '100%', lg: sidebarWidth }}>
25
+ {sidebar}
26
+ </Box>
27
+ <Box {...layoutSizing.child} flex={1} width="100%">
22
28
  {children}
23
29
  </Box>
24
- {aside ? <Box width={{ base: '100%', lg: asideWidth }}>{aside}</Box> : null}
30
+ {aside ? (
31
+ <Box {...layoutSizing.child} width={{ base: '100%', lg: asideWidth }}>
32
+ {aside}
33
+ </Box>
34
+ ) : null}
25
35
  </Stack>
26
36
  );
27
37
  }
@@ -29,6 +39,8 @@ function SidebarLayoutInner({
29
39
  /***
30
40
  * Responsive layout with a sidebar and main content area (and optional aside).
31
41
  *
32
-
42
+ * `sizing="content"` keeps normal content-flow behavior. Use `sizing="fill"` when the layout
43
+ * should fill an already bounded parent so sidebar/content children receive the available space
44
+ * for their own scrolling or gesture ownership.
33
45
  */
34
46
  export const SidebarLayout = withZoraThemeScope(SidebarLayoutInner);
@@ -0,0 +1,18 @@
1
+ import type { SidebarLayoutSizing } from './types';
2
+
3
+ export function resolveSidebarLayoutSizing(sizing: SidebarLayoutSizing = 'content') {
4
+ const fillsAvailableSpace = sizing === 'fill';
5
+
6
+ return {
7
+ root: {
8
+ align: fillsAvailableSpace ? 'stretch' : 'flex-start',
9
+ flex: fillsAvailableSpace ? 1 : undefined,
10
+ minHeight: fillsAvailableSpace ? 0 : undefined,
11
+ minWidth: fillsAvailableSpace ? 0 : undefined,
12
+ },
13
+ child: {
14
+ minHeight: fillsAvailableSpace ? 0 : undefined,
15
+ minWidth: fillsAvailableSpace ? 0 : undefined,
16
+ },
17
+ } as const;
18
+ }
@@ -2,10 +2,13 @@ import type React from 'react';
2
2
 
3
3
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
4
4
 
5
+ export type SidebarLayoutSizing = 'content' | 'fill';
6
+
5
7
  export interface SidebarLayoutProps extends ZoraBaseProps {
6
8
  sidebar: React.ReactNode;
7
9
  children?: React.ReactNode;
8
10
  aside?: React.ReactNode;
9
11
  sidebarWidth?: number;
10
12
  asideWidth?: number;
13
+ sizing?: SidebarLayoutSizing;
11
14
  }