@ankhorage/zora 1.0.5 → 1.0.7

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 (128) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +284 -0
  3. package/dist/components/media-card/MediaCard.d.ts +4 -0
  4. package/dist/components/media-card/MediaCard.d.ts.map +1 -0
  5. package/dist/components/media-card/MediaCard.js +64 -0
  6. package/dist/components/media-card/MediaCard.js.map +1 -0
  7. package/dist/components/media-card/index.d.ts +3 -0
  8. package/dist/components/media-card/index.d.ts.map +1 -0
  9. package/dist/components/media-card/index.js +2 -0
  10. package/dist/components/media-card/index.js.map +1 -0
  11. package/dist/components/media-card/types.d.ts +36 -0
  12. package/dist/components/media-card/types.d.ts.map +1 -0
  13. package/dist/components/media-card/types.js +2 -0
  14. package/dist/components/media-card/types.js.map +1 -0
  15. package/dist/components/metric-card/MetricCard.d.ts +4 -0
  16. package/dist/components/metric-card/MetricCard.d.ts.map +1 -0
  17. package/dist/components/metric-card/MetricCard.js +43 -0
  18. package/dist/components/metric-card/MetricCard.js.map +1 -0
  19. package/dist/components/metric-card/index.d.ts +3 -0
  20. package/dist/components/metric-card/index.d.ts.map +1 -0
  21. package/dist/components/metric-card/index.js +2 -0
  22. package/dist/components/metric-card/index.js.map +1 -0
  23. package/dist/components/metric-card/types.d.ts +17 -0
  24. package/dist/components/metric-card/types.d.ts.map +1 -0
  25. package/dist/components/metric-card/types.js +2 -0
  26. package/dist/components/metric-card/types.js.map +1 -0
  27. package/dist/components/progress/Progress.d.ts +4 -0
  28. package/dist/components/progress/Progress.d.ts.map +1 -0
  29. package/dist/components/progress/Progress.js +28 -0
  30. package/dist/components/progress/Progress.js.map +1 -0
  31. package/dist/components/progress/index.d.ts +3 -0
  32. package/dist/components/progress/index.d.ts.map +1 -0
  33. package/dist/components/progress/index.js +2 -0
  34. package/dist/components/progress/index.js.map +1 -0
  35. package/dist/components/progress/resolveProgressFraction.d.ts +5 -0
  36. package/dist/components/progress/resolveProgressFraction.d.ts.map +1 -0
  37. package/dist/components/progress/resolveProgressFraction.js +14 -0
  38. package/dist/components/progress/resolveProgressFraction.js.map +1 -0
  39. package/dist/components/progress/types.d.ts +11 -0
  40. package/dist/components/progress/types.d.ts.map +1 -0
  41. package/dist/components/progress/types.js +16 -0
  42. package/dist/components/progress/types.js.map +1 -0
  43. package/dist/components/rating/Rating.d.ts +4 -0
  44. package/dist/components/rating/Rating.d.ts.map +1 -0
  45. package/dist/components/rating/Rating.js +23 -0
  46. package/dist/components/rating/Rating.js.map +1 -0
  47. package/dist/components/rating/index.d.ts +3 -0
  48. package/dist/components/rating/index.d.ts.map +1 -0
  49. package/dist/components/rating/index.js +2 -0
  50. package/dist/components/rating/index.js.map +1 -0
  51. package/dist/components/rating/resolveRatingSegments.d.ts +7 -0
  52. package/dist/components/rating/resolveRatingSegments.d.ts.map +1 -0
  53. package/dist/components/rating/resolveRatingSegments.js +22 -0
  54. package/dist/components/rating/resolveRatingSegments.js.map +1 -0
  55. package/dist/components/rating/types.d.ts +11 -0
  56. package/dist/components/rating/types.d.ts.map +1 -0
  57. package/dist/components/rating/types.js +16 -0
  58. package/dist/components/rating/types.js.map +1 -0
  59. package/dist/index.d.ts +12 -0
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +6 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/patterns/list/List.d.ts +4 -0
  64. package/dist/patterns/list/List.d.ts.map +1 -0
  65. package/dist/patterns/list/List.js +35 -0
  66. package/dist/patterns/list/List.js.map +1 -0
  67. package/dist/patterns/list/ListRow.d.ts +4 -0
  68. package/dist/patterns/list/ListRow.d.ts.map +1 -0
  69. package/dist/patterns/list/ListRow.js +108 -0
  70. package/dist/patterns/list/ListRow.js.map +1 -0
  71. package/dist/patterns/list/ListSection.d.ts +4 -0
  72. package/dist/patterns/list/ListSection.d.ts.map +1 -0
  73. package/dist/patterns/list/ListSection.js +14 -0
  74. package/dist/patterns/list/ListSection.js.map +1 -0
  75. package/dist/patterns/list/index.d.ts +5 -0
  76. package/dist/patterns/list/index.d.ts.map +1 -0
  77. package/dist/patterns/list/index.js +4 -0
  78. package/dist/patterns/list/index.js.map +1 -0
  79. package/dist/patterns/list/resolveListSeparator.d.ts +5 -0
  80. package/dist/patterns/list/resolveListSeparator.d.ts.map +1 -0
  81. package/dist/patterns/list/resolveListSeparator.js +6 -0
  82. package/dist/patterns/list/resolveListSeparator.js.map +1 -0
  83. package/dist/patterns/list/types.d.ts +55 -0
  84. package/dist/patterns/list/types.d.ts.map +1 -0
  85. package/dist/patterns/list/types.js +2 -0
  86. package/dist/patterns/list/types.js.map +1 -0
  87. package/dist/patterns/timeline/Timeline.d.ts +4 -0
  88. package/dist/patterns/timeline/Timeline.d.ts.map +1 -0
  89. package/dist/patterns/timeline/Timeline.js +59 -0
  90. package/dist/patterns/timeline/Timeline.js.map +1 -0
  91. package/dist/patterns/timeline/index.d.ts +3 -0
  92. package/dist/patterns/timeline/index.d.ts.map +1 -0
  93. package/dist/patterns/timeline/index.js +2 -0
  94. package/dist/patterns/timeline/index.js.map +1 -0
  95. package/dist/patterns/timeline/types.d.ts +18 -0
  96. package/dist/patterns/timeline/types.d.ts.map +1 -0
  97. package/dist/patterns/timeline/types.js +2 -0
  98. package/dist/patterns/timeline/types.js.map +1 -0
  99. package/package.json +1 -1
  100. package/src/components/media-card/MediaCard.tsx +120 -0
  101. package/src/components/media-card/index.ts +2 -0
  102. package/src/components/media-card/types.ts +44 -0
  103. package/src/components/metric-card/MetricCard.tsx +84 -0
  104. package/src/components/metric-card/index.ts +2 -0
  105. package/src/components/metric-card/types.ts +18 -0
  106. package/src/components/progress/Progress.tsx +50 -0
  107. package/src/components/progress/index.ts +2 -0
  108. package/src/components/progress/resolveProgressFraction.test.ts +23 -0
  109. package/src/components/progress/resolveProgressFraction.ts +17 -0
  110. package/src/components/progress/types.ts +27 -0
  111. package/src/components/rating/Rating.tsx +38 -0
  112. package/src/components/rating/index.ts +2 -0
  113. package/src/components/rating/resolveRatingSegments.test.ts +60 -0
  114. package/src/components/rating/resolveRatingSegments.ts +34 -0
  115. package/src/components/rating/types.ts +27 -0
  116. package/src/index.ts +19 -0
  117. package/src/patterns/list/List.tsx +72 -0
  118. package/src/patterns/list/ListRow.tsx +193 -0
  119. package/src/patterns/list/ListSection.tsx +36 -0
  120. package/src/patterns/list/index.ts +11 -0
  121. package/src/patterns/list/resolveListSeparator.test.ts +18 -0
  122. package/src/patterns/list/resolveListSeparator.ts +8 -0
  123. package/src/patterns/list/types.ts +67 -0
  124. package/src/patterns/timeline/Timeline.tsx +104 -0
  125. package/src/patterns/timeline/index.ts +2 -0
  126. package/src/patterns/timeline/types.ts +20 -0
  127. package/src/showcaseCoverage.test.ts +8 -0
  128. package/src/theme/themeScopeStructure.test.ts +14 -0
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { Stack } from '../../foundation';
3
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
4
+ import { SectionHeader } from '../section-header';
5
+ import { List } from './List';
6
+ function ListSectionInner({ themeId: _themeId, mode: _mode, testID, title, description, eyebrow, actions, ...props }) {
7
+ const hasHeader = title !== undefined;
8
+ return (<Stack gap="s" testID={testID}>
9
+ {hasHeader ? (<SectionHeader actions={actions} description={description} eyebrow={eyebrow} title={title}/>) : null}
10
+ <List {...props}/>
11
+ </Stack>);
12
+ }
13
+ export const ListSection = withZoraThemeScope(ListSectionInner);
14
+ //# sourceMappingURL=ListSection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListSection.js","sourceRoot":"","sources":["../../../src/patterns/list/ListSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B,SAAS,gBAAgB,CAAC,EACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAO,EACP,OAAO,EACP,GAAG,KAAK,EACS;IACjB,MAAM,SAAS,GAAG,KAAK,KAAK,SAAS,CAAC;IAEtC,OAAO,CACL,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAC5B;MAAA,CAAC,SAAS,CAAC,CAAC,CAAC,CACX,CAAC,aAAa,CACZ,OAAO,CAAC,CAAC,OAAO,CAAC,CACjB,WAAW,CAAC,CAAC,WAAW,CAAC,CACzB,OAAO,CAAC,CAAC,OAAO,CAAC,CACjB,KAAK,CAAC,CAAC,KAAK,CAAC,EACb,CACH,CAAC,CAAC,CAAC,IAAI,CACR;MAAA,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,EAClB;IAAA,EAAE,KAAK,CAAC,CACT,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["import React from 'react';\n\nimport { Stack } from '../../foundation';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport { SectionHeader } from '../section-header';\nimport { List } from './List';\nimport type { ListSectionProps } from './types';\n\nfunction ListSectionInner({\n themeId: _themeId,\n mode: _mode,\n testID,\n title,\n description,\n eyebrow,\n actions,\n ...props\n}: ListSectionProps) {\n const hasHeader = title !== undefined;\n\n return (\n <Stack gap=\"s\" testID={testID}>\n {hasHeader ? (\n <SectionHeader\n actions={actions}\n description={description}\n eyebrow={eyebrow}\n title={title}\n />\n ) : null}\n <List {...props} />\n </Stack>\n );\n}\n\nexport const ListSection = withZoraThemeScope(ListSectionInner);\n"]}
@@ -0,0 +1,5 @@
1
+ export { List } from './List';
2
+ export { ListRow } from './ListRow';
3
+ export { ListSection } from './ListSection';
4
+ export type { ListChildrenProps, ListItemsProps, ListProps, ListRowProps, ListRowVariant, ListSectionProps, } from './types';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/patterns/list/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,YAAY,EACZ,cAAc,EACd,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { List } from './List';
2
+ export { ListRow } from './ListRow';
3
+ export { ListSection } from './ListSection';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/patterns/list/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC","sourcesContent":["export { List } from './List';\nexport { ListRow } from './ListRow';\nexport { ListSection } from './ListSection';\nexport type {\n ListChildrenProps,\n ListItemsProps,\n ListProps,\n ListRowProps,\n ListRowVariant,\n ListSectionProps,\n} from './types';\n"]}
@@ -0,0 +1,5 @@
1
+ import type { ListRowVariant } from './types';
2
+ type ListSeparatorKind = 'none' | 'divider' | 'spacer';
3
+ export declare function resolveListSeparator(variant: ListRowVariant, index: number): ListSeparatorKind;
4
+ export {};
5
+ //# sourceMappingURL=resolveListSeparator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveListSeparator.d.ts","sourceRoot":"","sources":["../../../src/patterns/list/resolveListSeparator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,KAAK,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAG9F"}
@@ -0,0 +1,6 @@
1
+ export function resolveListSeparator(variant, index) {
2
+ if (index === 0)
3
+ return 'none';
4
+ return variant === 'divider' ? 'divider' : 'spacer';
5
+ }
6
+ //# sourceMappingURL=resolveListSeparator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveListSeparator.js","sourceRoot":"","sources":["../../../src/patterns/list/resolveListSeparator.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,oBAAoB,CAAC,OAAuB,EAAE,KAAa;IACzE,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/B,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AACtD,CAAC","sourcesContent":["import type { ListRowVariant } from './types';\n\ntype ListSeparatorKind = 'none' | 'divider' | 'spacer';\n\nexport function resolveListSeparator(variant: ListRowVariant, index: number): ListSeparatorKind {\n if (index === 0) return 'none';\n return variant === 'divider' ? 'divider' : 'spacer';\n}\n"]}
@@ -0,0 +1,55 @@
1
+ import type React from 'react';
2
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
3
+ export type ListRowVariant = 'divider' | 'card';
4
+ interface ListRowBaseProps extends ZoraBaseProps {
5
+ title: React.ReactNode;
6
+ description?: React.ReactNode;
7
+ meta?: React.ReactNode;
8
+ leading?: React.ReactNode;
9
+ trailing?: React.ReactNode;
10
+ selected?: boolean;
11
+ disabled?: boolean;
12
+ compact?: boolean;
13
+ variant?: ListRowVariant;
14
+ }
15
+ interface ListRowPressableProps {
16
+ onPress: () => void;
17
+ action?: never;
18
+ }
19
+ interface ListRowActionProps {
20
+ action: React.ReactNode;
21
+ onPress?: never;
22
+ }
23
+ interface ListRowStaticProps {
24
+ action?: never;
25
+ onPress?: never;
26
+ }
27
+ export type ListRowProps = ListRowBaseProps & (ListRowPressableProps | ListRowActionProps | ListRowStaticProps);
28
+ export interface ListItemsProps extends ZoraBaseProps {
29
+ items: readonly ListRowProps[];
30
+ rowVariant?: ListRowVariant;
31
+ compact?: boolean;
32
+ }
33
+ export interface ListChildrenProps extends ZoraBaseProps {
34
+ children: React.ReactNode;
35
+ }
36
+ export type ListProps = ListItemsProps | ListChildrenProps;
37
+ export interface ListSectionItemsProps extends ZoraBaseProps {
38
+ title?: React.ReactNode;
39
+ description?: React.ReactNode;
40
+ eyebrow?: React.ReactNode;
41
+ actions?: React.ReactNode;
42
+ items: readonly ListRowProps[];
43
+ rowVariant?: ListRowVariant;
44
+ compact?: boolean;
45
+ }
46
+ export interface ListSectionChildrenProps extends ZoraBaseProps {
47
+ title?: React.ReactNode;
48
+ description?: React.ReactNode;
49
+ eyebrow?: React.ReactNode;
50
+ actions?: React.ReactNode;
51
+ children: React.ReactNode;
52
+ }
53
+ export type ListSectionProps = ListSectionItemsProps | ListSectionChildrenProps;
54
+ export {};
55
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/patterns/list/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,cAAc,GAAG,SAAS,GAAG,MAAM,CAAC;AAEhD,UAAU,gBAAiB,SAAQ,aAAa;IAC9C,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,UAAU,qBAAqB;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB;AAED,UAAU,kBAAkB;IAC1B,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,UAAU,kBAAkB;IAC1B,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GACzC,CAAC,qBAAqB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC,CAAC;AAEpE,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,iBAAiB,CAAC;AAE3D,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC7D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,GAAG,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/patterns/list/types.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport type ListRowVariant = 'divider' | 'card';\n\ninterface ListRowBaseProps extends ZoraBaseProps {\n title: React.ReactNode;\n description?: React.ReactNode;\n meta?: React.ReactNode;\n leading?: React.ReactNode;\n trailing?: React.ReactNode;\n selected?: boolean;\n disabled?: boolean;\n compact?: boolean;\n variant?: ListRowVariant;\n}\n\ninterface ListRowPressableProps {\n onPress: () => void;\n action?: never;\n}\n\ninterface ListRowActionProps {\n action: React.ReactNode;\n onPress?: never;\n}\n\ninterface ListRowStaticProps {\n action?: never;\n onPress?: never;\n}\n\nexport type ListRowProps = ListRowBaseProps &\n (ListRowPressableProps | ListRowActionProps | ListRowStaticProps);\n\nexport interface ListItemsProps extends ZoraBaseProps {\n items: readonly ListRowProps[];\n rowVariant?: ListRowVariant;\n compact?: boolean;\n}\n\nexport interface ListChildrenProps extends ZoraBaseProps {\n children: React.ReactNode;\n}\n\nexport type ListProps = ListItemsProps | ListChildrenProps;\n\nexport interface ListSectionItemsProps extends ZoraBaseProps {\n title?: React.ReactNode;\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n actions?: React.ReactNode;\n items: readonly ListRowProps[];\n rowVariant?: ListRowVariant;\n compact?: boolean;\n}\n\nexport interface ListSectionChildrenProps extends ZoraBaseProps {\n title?: React.ReactNode;\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n actions?: React.ReactNode;\n children: React.ReactNode;\n}\n\nexport type ListSectionProps = ListSectionItemsProps | ListSectionChildrenProps;\n"]}
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { TimelineProps } from './types';
3
+ export declare const Timeline: (props: TimelineProps) => React.ReactElement | null;
4
+ //# sourceMappingURL=Timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.d.ts","sourceRoot":"","sources":["../../../src/patterns/timeline/Timeline.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,SAAS,CAAC;AA4F3D,eAAO,MAAM,QAAQ,qDAAoC,CAAC"}
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { Heading } from '../../components/heading';
3
+ import { Icon } from '../../components/icon';
4
+ import { Text } from '../../components/text';
5
+ import { Box, Inline, Stack } from '../../foundation';
6
+ import { resolveIconSize } from '../../internal/recipes';
7
+ import { useZoraTheme } from '../../theme/useZoraTheme';
8
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
9
+ function resolveRoleSemantics(theme, tone) {
10
+ switch (tone) {
11
+ case 'primary':
12
+ return theme.semantics.action.primary;
13
+ case 'danger':
14
+ return theme.semantics.action.danger;
15
+ case 'success':
16
+ return theme.semantics.success;
17
+ case 'warning':
18
+ return theme.semantics.warning;
19
+ case 'neutral':
20
+ default:
21
+ return theme.semantics.action.neutral;
22
+ }
23
+ }
24
+ function TimelineInner({ themeId: _themeId, mode: _mode, testID, items, compact = false, }) {
25
+ const { theme } = useZoraTheme();
26
+ const gap = compact ? 'm' : 'l';
27
+ const renderItem = (item, index) => {
28
+ const status = item.status ?? 'neutral';
29
+ const role = resolveRoleSemantics(theme, status);
30
+ const showConnector = index < items.length - 1;
31
+ const iconSize = Math.max(12, resolveIconSize('s'));
32
+ return (<Inline key={item.id} align="flex-start" gap="m" testID={item.testID} wrap="nowrap">
33
+ <Stack align="center" flexShrink={0} gap="xs" style={{ width: 24 }}>
34
+ <Box bg={status === 'neutral' ? theme.semantics.neutral.surface : role.softBg} borderColor={status === 'neutral' ? theme.semantics.neutral.divider : role.base} borderWidth={1} height={20} radius="full" width={20} style={{ alignItems: 'center', justifyContent: 'center' }}>
35
+ {item.icon ? (<Icon color={status === 'neutral' ? theme.semantics.content.muted : role.base} name={item.icon.name} provider={item.icon.provider} size={iconSize}/>) : null}
36
+ </Box>
37
+
38
+ {showConnector ? (<Box bg={theme.semantics.neutral.divider} flex={1} radius="full" style={{ minHeight: 16, width: 2 }}/>) : null}
39
+ </Stack>
40
+
41
+ <Stack flex={1} gap="xs" testID={testID}>
42
+ <Inline align="flex-start" gap="s" justify="space-between" wrap="wrap">
43
+ <Heading level={compact ? 4 : 3}>{item.title}</Heading>
44
+ {item.meta ? (<Text tone="muted" variant="caption">
45
+ {item.meta}
46
+ </Text>) : null}
47
+ </Inline>
48
+ {item.description ? (<Text tone="muted" variant="bodySmall">
49
+ {item.description}
50
+ </Text>) : null}
51
+ </Stack>
52
+ </Inline>);
53
+ };
54
+ return (<Stack gap={gap} testID={testID}>
55
+ {items.map(renderItem)}
56
+ </Stack>);
57
+ }
58
+ export const Timeline = withZoraThemeScope(TimelineInner);
59
+ //# sourceMappingURL=Timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.js","sourceRoot":"","sources":["../../../src/patterns/timeline/Timeline.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { RoleSemantics, SurfaceTheme } from '@ankhorage/surface';\nimport React from 'react';\n\nimport { Heading } from '../../components/heading';\nimport { Icon } from '../../components/icon';\nimport { Text } from '../../components/text';\nimport { Box, Inline, Stack } from '../../foundation';\nimport type { ZoraTone } from '../../internal/recipes';\nimport { resolveIconSize } from '../../internal/recipes';\nimport { useZoraTheme } from '../../theme/useZoraTheme';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport type { TimelineItem, TimelineProps } from './types';\n\nfunction resolveRoleSemantics(theme: SurfaceTheme, tone: ZoraTone): RoleSemantics {\n switch (tone) {\n case 'primary':\n return theme.semantics.action.primary;\n case 'danger':\n return theme.semantics.action.danger;\n case 'success':\n return theme.semantics.success;\n case 'warning':\n return theme.semantics.warning;\n case 'neutral':\n default:\n return theme.semantics.action.neutral;\n }\n}\n\nfunction TimelineInner({\n themeId: _themeId,\n mode: _mode,\n testID,\n items,\n compact = false,\n}: TimelineProps) {\n const { theme } = useZoraTheme();\n const gap = compact ? 'm' : 'l';\n\n const renderItem = (item: TimelineItem, index: number) => {\n const status = item.status ?? 'neutral';\n const role = resolveRoleSemantics(theme, status);\n const showConnector = index < items.length - 1;\n const iconSize = Math.max(12, resolveIconSize('s'));\n\n return (\n <Inline key={item.id} align=\"flex-start\" gap=\"m\" testID={item.testID} wrap=\"nowrap\">\n <Stack align=\"center\" flexShrink={0} gap=\"xs\" style={{ width: 24 }}>\n <Box\n bg={status === 'neutral' ? theme.semantics.neutral.surface : role.softBg}\n borderColor={status === 'neutral' ? theme.semantics.neutral.divider : role.base}\n borderWidth={1}\n height={20}\n radius=\"full\"\n width={20}\n style={{ alignItems: 'center', justifyContent: 'center' }}\n >\n {item.icon ? (\n <Icon\n color={status === 'neutral' ? theme.semantics.content.muted : role.base}\n name={item.icon.name}\n provider={item.icon.provider}\n size={iconSize}\n />\n ) : null}\n </Box>\n\n {showConnector ? (\n <Box\n bg={theme.semantics.neutral.divider}\n flex={1}\n radius=\"full\"\n style={{ minHeight: 16, width: 2 }}\n />\n ) : null}\n </Stack>\n\n <Stack flex={1} gap=\"xs\" testID={testID}>\n <Inline align=\"flex-start\" gap=\"s\" justify=\"space-between\" wrap=\"wrap\">\n <Heading level={compact ? 4 : 3}>{item.title}</Heading>\n {item.meta ? (\n <Text tone=\"muted\" variant=\"caption\">\n {item.meta}\n </Text>\n ) : null}\n </Inline>\n {item.description ? (\n <Text tone=\"muted\" variant=\"bodySmall\">\n {item.description}\n </Text>\n ) : null}\n </Stack>\n </Inline>\n );\n };\n\n return (\n <Stack gap={gap} testID={testID}>\n {items.map(renderItem)}\n </Stack>\n );\n}\n\nexport const Timeline = withZoraThemeScope(TimelineInner);\n"]}
@@ -0,0 +1,3 @@
1
+ export { Timeline } from './Timeline';
2
+ export type { TimelineItem, TimelineProps } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/patterns/timeline/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { Timeline } from './Timeline';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/patterns/timeline/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { Timeline } from './Timeline';\nexport type { TimelineItem, TimelineProps } from './types';\n"]}
@@ -0,0 +1,18 @@
1
+ import type { ButtonIconSpec } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+ import type { ZoraTone } from '../../internal/recipes';
4
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
5
+ export interface TimelineItem {
6
+ id: string;
7
+ title: React.ReactNode;
8
+ description?: React.ReactNode;
9
+ meta?: React.ReactNode;
10
+ status?: ZoraTone;
11
+ icon?: ButtonIconSpec;
12
+ testID?: string;
13
+ }
14
+ export interface TimelineProps extends ZoraBaseProps {
15
+ items: readonly TimelineItem[];
16
+ compact?: boolean;
17
+ }
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/patterns/timeline/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/patterns/timeline/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ButtonIconSpec } from '@ankhorage/surface';\nimport type React from 'react';\n\nimport type { ZoraTone } from '../../internal/recipes';\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport interface TimelineItem {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n meta?: React.ReactNode;\n status?: ZoraTone;\n icon?: ButtonIconSpec;\n testID?: string;\n}\n\nexport interface TimelineProps extends ZoraBaseProps {\n items: readonly TimelineItem[];\n compact?: boolean;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora",
3
3
  "type": "module",
4
- "version": "1.0.5",
4
+ "version": "1.0.7",
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": {
@@ -0,0 +1,120 @@
1
+ import React from 'react';
2
+ import { Image } from 'react-native';
3
+
4
+ import { Box, Stack } from '../../foundation';
5
+ import { useZoraTheme } from '../../theme/useZoraTheme';
6
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
7
+ import { Card } from '../card';
8
+ import { Heading } from '../heading';
9
+ import { Text } from '../text';
10
+ import type { MediaCardProps } from './types';
11
+
12
+ function resolveImageAspectRatio(
13
+ imageAspectRatio: NonNullable<MediaCardProps['imageAspectRatio']>,
14
+ ) {
15
+ if (!Number.isFinite(imageAspectRatio) || imageAspectRatio <= 0) {
16
+ return 16 / 9;
17
+ }
18
+
19
+ return imageAspectRatio;
20
+ }
21
+
22
+ function MediaCardInner({
23
+ themeId: _themeId,
24
+ mode: _mode,
25
+ testID,
26
+ imageSource,
27
+ imageLabel,
28
+ image,
29
+ imageAspectRatio = 16 / 9,
30
+ title,
31
+ description,
32
+ eyebrow,
33
+ badges,
34
+ actions,
35
+ footer,
36
+ children,
37
+ tone = 'default',
38
+ compact = false,
39
+ onPress,
40
+ }: MediaCardProps) {
41
+ const { theme } = useZoraTheme();
42
+ const gap = compact ? 's' : 'm';
43
+ const isInteractive = Boolean(onPress) && !actions;
44
+ const resolvedAspectRatio = resolveImageAspectRatio(imageAspectRatio);
45
+
46
+ const renderImage = () => {
47
+ if (image) {
48
+ return (
49
+ <Box radius="m" style={{ overflow: 'hidden' }}>
50
+ {image}
51
+ </Box>
52
+ );
53
+ }
54
+
55
+ if (!imageSource) {
56
+ return null;
57
+ }
58
+
59
+ return (
60
+ <Box bg={theme.semantics.neutral.surface} radius="m" style={{ overflow: 'hidden' }}>
61
+ <Box style={{ aspectRatio: resolvedAspectRatio, width: '100%' }}>
62
+ <Image
63
+ accessibilityLabel={imageLabel}
64
+ source={imageSource}
65
+ style={{ height: '100%', width: '100%' }}
66
+ />
67
+ </Box>
68
+ </Box>
69
+ );
70
+ };
71
+
72
+ const hasHeader = [eyebrow, title, description, badges, actions].some((item) => item != null);
73
+
74
+ return (
75
+ <Card
76
+ compact={compact}
77
+ onPress={isInteractive ? onPress : undefined}
78
+ testID={testID}
79
+ tone={tone}
80
+ >
81
+ <Stack gap={gap}>
82
+ {renderImage()}
83
+
84
+ {hasHeader ? (
85
+ <Stack
86
+ align={{ base: 'flex-start', md: 'center' }}
87
+ direction={{ base: 'column', md: 'row' }}
88
+ gap="m"
89
+ justify="space-between"
90
+ >
91
+ <Box flex={1}>
92
+ <Stack gap="xs">
93
+ {eyebrow ? (
94
+ <Text tone="muted" variant="caption" weight="semiBold">
95
+ {eyebrow}
96
+ </Text>
97
+ ) : null}
98
+ <Stack gap="xs">
99
+ <Heading level={compact ? 4 : 3}>{title}</Heading>
100
+ {badges ? <Box>{badges}</Box> : null}
101
+ </Stack>
102
+ {description ? (
103
+ <Text tone="muted" variant="bodySmall">
104
+ {description}
105
+ </Text>
106
+ ) : null}
107
+ </Stack>
108
+ </Box>
109
+ {actions ? <Box>{actions}</Box> : null}
110
+ </Stack>
111
+ ) : null}
112
+
113
+ {children ? <Box>{children}</Box> : null}
114
+ {footer ? <Box pt="xs">{footer}</Box> : null}
115
+ </Stack>
116
+ </Card>
117
+ );
118
+ }
119
+
120
+ export const MediaCard = withZoraThemeScope(MediaCardInner);
@@ -0,0 +1,2 @@
1
+ export { MediaCard } from './MediaCard';
2
+ export type { MediaCardImageProps, MediaCardProps } from './types';
@@ -0,0 +1,44 @@
1
+ import type React from 'react';
2
+ import type { ImageSourcePropType } from 'react-native';
3
+
4
+ import type { ZoraCardTone } from '../../internal/recipes';
5
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
6
+
7
+ interface MediaCardWithImageSource {
8
+ imageSource: ImageSourcePropType;
9
+ imageLabel?: string;
10
+ image?: never;
11
+ }
12
+
13
+ interface MediaCardWithImageSlot {
14
+ image: React.ReactNode;
15
+ imageSource?: never;
16
+ imageLabel?: never;
17
+ }
18
+
19
+ interface MediaCardWithoutImage {
20
+ image?: never;
21
+ imageSource?: never;
22
+ imageLabel?: never;
23
+ }
24
+
25
+ export type MediaCardImageProps =
26
+ | MediaCardWithImageSource
27
+ | MediaCardWithImageSlot
28
+ | MediaCardWithoutImage;
29
+
30
+ interface MediaCardBaseProps extends ZoraBaseProps {
31
+ title: React.ReactNode;
32
+ description?: React.ReactNode;
33
+ eyebrow?: React.ReactNode;
34
+ badges?: React.ReactNode;
35
+ actions?: React.ReactNode;
36
+ footer?: React.ReactNode;
37
+ children?: React.ReactNode;
38
+ tone?: ZoraCardTone;
39
+ compact?: boolean;
40
+ onPress?: () => void;
41
+ imageAspectRatio?: number;
42
+ }
43
+
44
+ export type MediaCardProps = MediaCardBaseProps & MediaCardImageProps;
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+
3
+ import { Box, Inline, Stack } from '../../foundation';
4
+ import { resolveBadgeRecipe, resolveIconSize } from '../../internal/recipes';
5
+ import { useZoraTheme } from '../../theme/useZoraTheme';
6
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
7
+ import { Badge } from '../badge';
8
+ import { Card } from '../card';
9
+ import { Heading } from '../heading';
10
+ import { Icon } from '../icon';
11
+ import { Text } from '../text';
12
+ import type { MetricCardProps } from './types';
13
+
14
+ function MetricCardInner({
15
+ themeId: _themeId,
16
+ mode: _mode,
17
+ testID,
18
+ label,
19
+ value,
20
+ description,
21
+ icon,
22
+ delta,
23
+ deltaTone = 'neutral',
24
+ actions,
25
+ tone = 'default',
26
+ compact = false,
27
+ onPress,
28
+ }: MetricCardProps) {
29
+ const { theme } = useZoraTheme();
30
+ const isInteractive = Boolean(onPress) && !actions;
31
+
32
+ const badgeRecipe = resolveBadgeRecipe({ tone: deltaTone, emphasis: 'soft', size: 's' });
33
+ const iconColor = theme.semantics.content.muted;
34
+
35
+ return (
36
+ <Card
37
+ compact={compact}
38
+ onPress={isInteractive ? onPress : undefined}
39
+ testID={testID}
40
+ tone={tone}
41
+ >
42
+ <Stack gap={compact ? 's' : 'm'}>
43
+ <Inline align="flex-start" gap="m" justify="space-between">
44
+ <Stack flex={1} gap="xs">
45
+ <Inline align="center" gap="xs" wrap="wrap">
46
+ {icon ? (
47
+ <Icon
48
+ color={iconColor}
49
+ name={icon.name}
50
+ provider={icon.provider}
51
+ size={resolveIconSize('s')}
52
+ />
53
+ ) : null}
54
+ <Text tone="muted" variant="caption" weight="semiBold">
55
+ {label}
56
+ </Text>
57
+ {delta != null ? (
58
+ <Badge
59
+ emphasis={badgeRecipe.variant}
60
+ size={badgeRecipe.size}
61
+ tone={badgeRecipe.tone}
62
+ >
63
+ {delta}
64
+ </Badge>
65
+ ) : null}
66
+ </Inline>
67
+
68
+ <Heading level={compact ? 3 : 2}>{value}</Heading>
69
+
70
+ {description ? (
71
+ <Text tone="muted" variant="bodySmall">
72
+ {description}
73
+ </Text>
74
+ ) : null}
75
+ </Stack>
76
+
77
+ {actions ? <Box>{actions}</Box> : null}
78
+ </Inline>
79
+ </Stack>
80
+ </Card>
81
+ );
82
+ }
83
+
84
+ export const MetricCard = withZoraThemeScope(MetricCardInner);
@@ -0,0 +1,2 @@
1
+ export { MetricCard } from './MetricCard';
2
+ export type { MetricCardProps } from './types';
@@ -0,0 +1,18 @@
1
+ import type { ButtonIconSpec } from '@ankhorage/surface';
2
+ import type React from 'react';
3
+
4
+ import type { ZoraCardTone, ZoraTone } from '../../internal/recipes';
5
+ import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
6
+
7
+ export interface MetricCardProps extends ZoraBaseProps {
8
+ label: React.ReactNode;
9
+ value: React.ReactNode;
10
+ description?: React.ReactNode;
11
+ icon?: ButtonIconSpec;
12
+ delta?: React.ReactNode;
13
+ deltaTone?: ZoraTone;
14
+ actions?: React.ReactNode;
15
+ tone?: ZoraCardTone;
16
+ compact?: boolean;
17
+ onPress?: () => void;
18
+ }
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+
3
+ import { Box } from '../../foundation';
4
+ import { useZoraTheme } from '../../theme/useZoraTheme';
5
+ import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
6
+ import { resolveProgressFraction } from './resolveProgressFraction';
7
+ import { type ProgressProps, resolveProgressRole } from './types';
8
+
9
+ function resolveProgressHeight(size: NonNullable<ProgressProps['size']>): number {
10
+ switch (size) {
11
+ case 's':
12
+ return 4;
13
+ case 'm':
14
+ return 6;
15
+ case 'l':
16
+ default:
17
+ return 8;
18
+ }
19
+ }
20
+
21
+ function ProgressInner({
22
+ themeId: _themeId,
23
+ mode: _mode,
24
+ testID,
25
+ value,
26
+ max = 100,
27
+ tone = 'primary',
28
+ size = 'm',
29
+ }: ProgressProps) {
30
+ const { theme } = useZoraTheme();
31
+ const fraction = resolveProgressFraction({ value, max });
32
+ const height = resolveProgressHeight(size);
33
+ const role = resolveProgressRole(theme, tone);
34
+
35
+ return (
36
+ <Box
37
+ accessibilityRole="progressbar"
38
+ bg={theme.semantics.neutral.surface}
39
+ borderColor={theme.semantics.neutral.divider}
40
+ borderWidth={1}
41
+ radius="full"
42
+ testID={testID}
43
+ style={{ height, overflow: 'hidden' }}
44
+ >
45
+ <Box bg={role.base} style={{ height: '100%', width: `${fraction * 100}%` }} />
46
+ </Box>
47
+ );
48
+ }
49
+
50
+ export const Progress = withZoraThemeScope(ProgressInner);
@@ -0,0 +1,2 @@
1
+ export { Progress } from './Progress';
2
+ export type { ProgressProps } from './types';
@@ -0,0 +1,23 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { resolveProgressFraction } from './resolveProgressFraction';
4
+
5
+ describe('resolveProgressFraction', () => {
6
+ test('clamps values to [0, 1]', () => {
7
+ expect(resolveProgressFraction({ value: -5, max: 10 })).toBe(0);
8
+ expect(resolveProgressFraction({ value: 0, max: 10 })).toBe(0);
9
+ expect(resolveProgressFraction({ value: 5, max: 10 })).toBe(0.5);
10
+ expect(resolveProgressFraction({ value: 10, max: 10 })).toBe(1);
11
+ expect(resolveProgressFraction({ value: 15, max: 10 })).toBe(1);
12
+ });
13
+
14
+ test('returns 0 for invalid max', () => {
15
+ expect(resolveProgressFraction({ value: 5, max: 0 })).toBe(0);
16
+ expect(resolveProgressFraction({ value: 5, max: -3 })).toBe(0);
17
+ expect(resolveProgressFraction({ value: 5, max: Number.NaN })).toBe(0);
18
+ });
19
+
20
+ test('returns 0 for invalid value', () => {
21
+ expect(resolveProgressFraction({ value: Number.NaN, max: 10 })).toBe(0);
22
+ });
23
+ });