@solostylist/ui-kit-native 1.0.0 → 1.0.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.
Files changed (247) hide show
  1. package/dist/hooks/index.d.ts +6 -0
  2. package/dist/hooks/index.d.ts.map +1 -0
  3. package/dist/hooks/index.js +5 -0
  4. package/dist/hooks/use-count-down.d.ts +72 -0
  5. package/dist/hooks/use-count-down.d.ts.map +1 -0
  6. package/dist/hooks/use-count-down.js +203 -0
  7. package/dist/hooks/use-is-passed-position.d.ts +20 -0
  8. package/dist/hooks/use-is-passed-position.d.ts.map +1 -0
  9. package/dist/hooks/use-is-passed-position.js +31 -0
  10. package/dist/index.d.ts +36 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +37 -0
  13. package/dist/s-avatar/index.d.ts +3 -0
  14. package/dist/s-avatar/index.d.ts.map +1 -0
  15. package/dist/s-avatar/index.js +1 -0
  16. package/dist/s-avatar/s-avatar.d.ts +43 -0
  17. package/dist/s-avatar/s-avatar.d.ts.map +1 -0
  18. package/dist/s-avatar/s-avatar.js +119 -0
  19. package/dist/s-button/index.d.ts +3 -0
  20. package/dist/s-button/index.d.ts.map +1 -0
  21. package/dist/s-button/index.js +1 -0
  22. package/dist/s-button/s-button.d.ts +55 -0
  23. package/dist/s-button/s-button.d.ts.map +1 -0
  24. package/dist/s-button/s-button.js +161 -0
  25. package/dist/s-button-link/index.d.ts +2 -0
  26. package/dist/s-button-link/index.d.ts.map +1 -0
  27. package/dist/s-button-link/index.js +1 -0
  28. package/dist/s-button-link/s-button-link.d.ts +43 -0
  29. package/dist/s-button-link/s-button-link.d.ts.map +1 -0
  30. package/dist/s-button-link/s-button-link.js +59 -0
  31. package/dist/s-chat-input/index.d.ts +2 -0
  32. package/dist/s-chat-input/index.d.ts.map +1 -0
  33. package/dist/s-chat-input/index.js +1 -0
  34. package/dist/s-chat-input/s-chat-input.d.ts +52 -0
  35. package/dist/s-chat-input/s-chat-input.d.ts.map +1 -0
  36. package/dist/s-chat-input/s-chat-input.js +135 -0
  37. package/dist/s-chat-message/index.d.ts +3 -0
  38. package/dist/s-chat-message/index.d.ts.map +1 -0
  39. package/dist/s-chat-message/index.js +2 -0
  40. package/dist/s-chat-message/s-chat-message.d.ts +61 -0
  41. package/dist/s-chat-message/s-chat-message.d.ts.map +1 -0
  42. package/dist/s-chat-message/s-chat-message.js +132 -0
  43. package/dist/s-checkbox/index.d.ts +3 -0
  44. package/dist/s-checkbox/index.d.ts.map +1 -0
  45. package/dist/s-checkbox/index.js +1 -0
  46. package/dist/s-checkbox/s-checkbox.d.ts +32 -0
  47. package/dist/s-checkbox/s-checkbox.d.ts.map +1 -0
  48. package/dist/s-checkbox/s-checkbox.js +52 -0
  49. package/dist/s-chip/index.d.ts +3 -0
  50. package/dist/s-chip/index.d.ts.map +1 -0
  51. package/dist/s-chip/index.js +1 -0
  52. package/dist/s-chip/s-chip.d.ts +52 -0
  53. package/dist/s-chip/s-chip.d.ts.map +1 -0
  54. package/dist/s-chip/s-chip.js +151 -0
  55. package/dist/s-code-block/index.d.ts +3 -0
  56. package/dist/s-code-block/index.d.ts.map +1 -0
  57. package/dist/s-code-block/index.js +2 -0
  58. package/dist/s-code-block/s-code-block.d.ts +40 -0
  59. package/dist/s-code-block/s-code-block.d.ts.map +1 -0
  60. package/dist/s-code-block/s-code-block.js +69 -0
  61. package/dist/s-comment-message/index.d.ts +3 -0
  62. package/dist/s-comment-message/index.d.ts.map +1 -0
  63. package/dist/s-comment-message/index.js +2 -0
  64. package/dist/s-comment-message/s-comment-message.d.ts +54 -0
  65. package/dist/s-comment-message/s-comment-message.d.ts.map +1 -0
  66. package/dist/s-comment-message/s-comment-message.js +109 -0
  67. package/dist/s-copyable-text/index.d.ts +3 -0
  68. package/dist/s-copyable-text/index.d.ts.map +1 -0
  69. package/dist/s-copyable-text/index.js +1 -0
  70. package/dist/s-copyable-text/s-copyable-text.d.ts +24 -0
  71. package/dist/s-copyable-text/s-copyable-text.d.ts.map +1 -0
  72. package/dist/s-copyable-text/s-copyable-text.js +52 -0
  73. package/dist/s-countdown/index.d.ts +7 -0
  74. package/dist/s-countdown/index.d.ts.map +1 -0
  75. package/dist/s-countdown/index.js +6 -0
  76. package/dist/s-countdown/s-count-box.d.ts +44 -0
  77. package/dist/s-countdown/s-count-box.d.ts.map +1 -0
  78. package/dist/s-countdown/s-count-box.js +135 -0
  79. package/dist/s-countdown/s-countdown.d.ts +72 -0
  80. package/dist/s-countdown/s-countdown.d.ts.map +1 -0
  81. package/dist/s-countdown/s-countdown.js +94 -0
  82. package/dist/s-data-table/index.d.ts +3 -0
  83. package/dist/s-data-table/index.d.ts.map +1 -0
  84. package/dist/s-data-table/index.js +2 -0
  85. package/dist/s-data-table/s-data-table.d.ts +64 -0
  86. package/dist/s-data-table/s-data-table.d.ts.map +1 -0
  87. package/dist/s-data-table/s-data-table.js +75 -0
  88. package/dist/s-date-picker/index.d.ts +3 -0
  89. package/dist/s-date-picker/index.d.ts.map +1 -0
  90. package/dist/s-date-picker/index.js +2 -0
  91. package/dist/s-date-picker/s-date-picker.d.ts +41 -0
  92. package/dist/s-date-picker/s-date-picker.d.ts.map +1 -0
  93. package/dist/s-date-picker/s-date-picker.js +161 -0
  94. package/dist/s-date-time-picker/index.d.ts +3 -0
  95. package/dist/s-date-time-picker/index.d.ts.map +1 -0
  96. package/dist/s-date-time-picker/index.js +2 -0
  97. package/dist/s-date-time-picker/s-date-time-picker.d.ts +47 -0
  98. package/dist/s-date-time-picker/s-date-time-picker.d.ts.map +1 -0
  99. package/dist/s-date-time-picker/s-date-time-picker.js +298 -0
  100. package/dist/s-divider/index.d.ts +2 -0
  101. package/dist/s-divider/index.d.ts.map +1 -0
  102. package/dist/s-divider/index.js +1 -0
  103. package/dist/s-divider/s-divider.d.ts +24 -0
  104. package/dist/s-divider/s-divider.d.ts.map +1 -0
  105. package/dist/s-divider/s-divider.js +30 -0
  106. package/dist/s-file-dropzone/index.d.ts +3 -0
  107. package/dist/s-file-dropzone/index.d.ts.map +1 -0
  108. package/dist/s-file-dropzone/index.js +2 -0
  109. package/dist/s-file-dropzone/s-file-dropzone.d.ts +63 -0
  110. package/dist/s-file-dropzone/s-file-dropzone.d.ts.map +1 -0
  111. package/dist/s-file-dropzone/s-file-dropzone.js +115 -0
  112. package/dist/s-file-icon/index.d.ts +3 -0
  113. package/dist/s-file-icon/index.d.ts.map +1 -0
  114. package/dist/s-file-icon/index.js +2 -0
  115. package/dist/s-file-icon/s-file-icon.d.ts +23 -0
  116. package/dist/s-file-icon/s-file-icon.d.ts.map +1 -0
  117. package/dist/s-file-icon/s-file-icon.js +86 -0
  118. package/dist/s-form/s-form.d.ts +0 -1
  119. package/dist/s-form/s-form.d.ts.map +1 -1
  120. package/dist/s-form/s-form.js +0 -1
  121. package/dist/s-icon-button/s-icon-button.d.ts +9 -9
  122. package/dist/s-icon-button/s-icon-button.d.ts.map +1 -1
  123. package/dist/s-icon-button/s-icon-button.js +38 -4
  124. package/dist/s-image-comparison/index.d.ts +2 -0
  125. package/dist/s-image-comparison/index.d.ts.map +1 -0
  126. package/dist/s-image-comparison/index.js +1 -0
  127. package/dist/s-image-comparison/s-image-comparison.d.ts +35 -0
  128. package/dist/s-image-comparison/s-image-comparison.d.ts.map +1 -0
  129. package/dist/s-image-comparison/s-image-comparison.js +111 -0
  130. package/dist/s-label/index.d.ts +3 -0
  131. package/dist/s-label/index.d.ts.map +1 -0
  132. package/dist/s-label/index.js +1 -0
  133. package/dist/s-label/s-label.d.ts +24 -0
  134. package/dist/s-label/s-label.d.ts.map +1 -0
  135. package/dist/s-label/s-label.js +19 -0
  136. package/dist/s-language-switcher/index.d.ts +4 -0
  137. package/dist/s-language-switcher/index.d.ts.map +1 -0
  138. package/dist/s-language-switcher/index.js +2 -0
  139. package/dist/s-language-switcher/s-language-switcher.d.ts +53 -0
  140. package/dist/s-language-switcher/s-language-switcher.d.ts.map +1 -0
  141. package/dist/s-language-switcher/s-language-switcher.js +57 -0
  142. package/dist/s-lazy-image/index.d.ts +3 -0
  143. package/dist/s-lazy-image/index.d.ts.map +1 -0
  144. package/dist/s-lazy-image/index.js +1 -0
  145. package/dist/s-lazy-image/s-lazy-image.d.ts +43 -0
  146. package/dist/s-lazy-image/s-lazy-image.d.ts.map +1 -0
  147. package/dist/s-lazy-image/s-lazy-image.js +74 -0
  148. package/dist/s-moving-border/index.d.ts +2 -0
  149. package/dist/s-moving-border/index.d.ts.map +1 -0
  150. package/dist/s-moving-border/index.js +1 -0
  151. package/dist/s-moving-border/s-moving-border.d.ts +38 -0
  152. package/dist/s-moving-border/s-moving-border.d.ts.map +1 -0
  153. package/dist/s-moving-border/s-moving-border.js +153 -0
  154. package/dist/s-multi-select/index.d.ts +2 -0
  155. package/dist/s-multi-select/index.d.ts.map +1 -0
  156. package/dist/s-multi-select/index.js +1 -0
  157. package/dist/s-multi-select/s-multi-select.d.ts +53 -0
  158. package/dist/s-multi-select/s-multi-select.d.ts.map +1 -0
  159. package/dist/s-multi-select/s-multi-select.js +150 -0
  160. package/dist/s-pagination/index.d.ts +3 -0
  161. package/dist/s-pagination/index.d.ts.map +1 -0
  162. package/dist/s-pagination/index.js +2 -0
  163. package/dist/s-pagination/s-pagination.d.ts +49 -0
  164. package/dist/s-pagination/s-pagination.d.ts.map +1 -0
  165. package/dist/s-pagination/s-pagination.js +147 -0
  166. package/dist/s-rating/index.d.ts +3 -0
  167. package/dist/s-rating/index.d.ts.map +1 -0
  168. package/dist/s-rating/index.js +1 -0
  169. package/dist/s-rating/s-rating.d.ts +55 -0
  170. package/dist/s-rating/s-rating.d.ts.map +1 -0
  171. package/dist/s-rating/s-rating.js +157 -0
  172. package/dist/s-review/index.d.ts +3 -0
  173. package/dist/s-review/index.d.ts.map +1 -0
  174. package/dist/s-review/index.js +1 -0
  175. package/dist/s-review/s-review.d.ts +55 -0
  176. package/dist/s-review/s-review.d.ts.map +1 -0
  177. package/dist/s-review/s-review.js +148 -0
  178. package/dist/s-scroll-to-top/index.d.ts +3 -0
  179. package/dist/s-scroll-to-top/index.d.ts.map +1 -0
  180. package/dist/s-scroll-to-top/index.js +2 -0
  181. package/dist/s-scroll-to-top/s-scroll-to-top.d.ts +36 -0
  182. package/dist/s-scroll-to-top/s-scroll-to-top.d.ts.map +1 -0
  183. package/dist/s-scroll-to-top/s-scroll-to-top.js +61 -0
  184. package/dist/s-select/s-select.d.ts +1 -3
  185. package/dist/s-select/s-select.d.ts.map +1 -1
  186. package/dist/s-select/s-select.js +61 -101
  187. package/dist/s-select-list/index.d.ts +3 -0
  188. package/dist/s-select-list/index.d.ts.map +1 -0
  189. package/dist/s-select-list/index.js +2 -0
  190. package/dist/s-select-list/s-select-list.d.ts +34 -0
  191. package/dist/s-select-list/s-select-list.d.ts.map +1 -0
  192. package/dist/s-select-list/s-select-list.js +115 -0
  193. package/dist/s-skeleton/index.d.ts +3 -0
  194. package/dist/s-skeleton/index.d.ts.map +1 -0
  195. package/dist/s-skeleton/index.js +1 -0
  196. package/dist/s-skeleton/s-skeleton.d.ts +27 -0
  197. package/dist/s-skeleton/s-skeleton.d.ts.map +1 -0
  198. package/dist/s-skeleton/s-skeleton.js +126 -0
  199. package/dist/s-switch/index.d.ts +3 -0
  200. package/dist/s-switch/index.d.ts.map +1 -0
  201. package/dist/s-switch/index.js +1 -0
  202. package/dist/s-switch/s-switch.d.ts +30 -0
  203. package/dist/s-switch/s-switch.d.ts.map +1 -0
  204. package/dist/s-switch/s-switch.js +44 -0
  205. package/dist/s-text-field/s-text-field.d.ts +0 -2
  206. package/dist/s-text-field/s-text-field.d.ts.map +1 -1
  207. package/dist/s-text-field/s-text-field.js +10 -23
  208. package/dist/s-text-shimmer/index.d.ts +2 -0
  209. package/dist/s-text-shimmer/index.d.ts.map +1 -0
  210. package/dist/s-text-shimmer/index.js +1 -0
  211. package/dist/s-text-shimmer/s-text-shimmer.d.ts +22 -0
  212. package/dist/s-text-shimmer/s-text-shimmer.d.ts.map +1 -0
  213. package/dist/s-text-shimmer/s-text-shimmer.js +102 -0
  214. package/dist/s-text-truncation/index.d.ts +2 -0
  215. package/dist/s-text-truncation/index.d.ts.map +1 -0
  216. package/dist/s-text-truncation/index.js +1 -0
  217. package/dist/s-text-truncation/s-text-truncation.d.ts +42 -0
  218. package/dist/s-text-truncation/s-text-truncation.d.ts.map +1 -0
  219. package/dist/s-text-truncation/s-text-truncation.js +88 -0
  220. package/dist/s-tip/index.d.ts +3 -0
  221. package/dist/s-tip/index.d.ts.map +1 -0
  222. package/dist/s-tip/index.js +1 -0
  223. package/dist/s-tip/s-tip.d.ts +17 -0
  224. package/dist/s-tip/s-tip.d.ts.map +1 -0
  225. package/dist/s-tip/s-tip.js +25 -0
  226. package/dist/s-tooltip/index.d.ts +2 -0
  227. package/dist/s-tooltip/index.d.ts.map +1 -0
  228. package/dist/s-tooltip/index.js +1 -0
  229. package/dist/s-tooltip/s-tooltip.d.ts +23 -0
  230. package/dist/s-tooltip/s-tooltip.d.ts.map +1 -0
  231. package/dist/s-tooltip/s-tooltip.js +17 -0
  232. package/dist/s-zoom-image/index.d.ts +3 -0
  233. package/dist/s-zoom-image/index.d.ts.map +1 -0
  234. package/dist/s-zoom-image/index.js +1 -0
  235. package/dist/s-zoom-image/s-zoom-image.d.ts +38 -0
  236. package/dist/s-zoom-image/s-zoom-image.d.ts.map +1 -0
  237. package/dist/s-zoom-image/s-zoom-image.js +149 -0
  238. package/dist/theme/theme-primitives.d.ts +10 -0
  239. package/dist/theme/theme-primitives.d.ts.map +1 -1
  240. package/dist/theme/theme-primitives.js +11 -0
  241. package/dist/utils/bytes-to-size.d.ts +9 -0
  242. package/dist/utils/bytes-to-size.d.ts.map +1 -0
  243. package/dist/utils/bytes-to-size.js +17 -0
  244. package/dist/utils/index.d.ts +2 -0
  245. package/dist/utils/index.d.ts.map +1 -0
  246. package/dist/utils/index.js +1 -0
  247. package/package.json +175 -5
@@ -0,0 +1,147 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { View } from 'react-native';
4
+ import { SButton } from '../s-button';
5
+ import { SIconButton } from '../s-icon-button';
6
+ import { SSkeleton } from '../s-skeleton';
7
+ import { SText } from '../s-text';
8
+ import { useSTheme } from '../theme';
9
+ /**
10
+ * Skeleton loading component for SPagination.
11
+ * Matches the web SPagination skeleton layout: [icon-button] [rounded bar] [icon-button]
12
+ */
13
+ const SPaginationSkeleton = ({ size = 'medium', style, }) => {
14
+ // Icon button size matches web SSkeleton component="icon-button" (32px circular)
15
+ const iconSize = size === 'small' ? 26 : 32;
16
+ // Bar width matches web SSkeleton variant="rounded" width={120}
17
+ const barWidth = size === 'small' ? 90 : 120;
18
+ const barHeight = size === 'small' ? 26 : 32;
19
+ return (_jsxs(View, { style: [
20
+ {
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ justifyContent: 'center',
24
+ gap: 8,
25
+ },
26
+ style,
27
+ ], children: [_jsx(SSkeleton, { variant: "circular", width: iconSize, height: iconSize }), _jsx(SSkeleton, { variant: "rounded", width: barWidth, height: barHeight }), _jsx(SSkeleton, { variant: "circular", width: iconSize, height: iconSize })] }));
28
+ };
29
+ /**
30
+ * Generate array of page numbers to display
31
+ */
32
+ const generatePageNumbers = (currentPage, totalPages, siblingCount, boundaryCount) => {
33
+ // Calculate total number of page buttons including ellipsis
34
+ const totalButtons = siblingCount * 2 + 1 + boundaryCount * 2 + 2; // +2 for potential ellipsis
35
+ // If total pages is less than total buttons, show all pages
36
+ if (totalPages <= totalButtons) {
37
+ return Array.from({ length: totalPages }, (_, i) => i + 1);
38
+ }
39
+ const pages = [];
40
+ // Always show first boundaryCount pages
41
+ for (let i = 1; i <= boundaryCount; i++) {
42
+ pages.push(i);
43
+ }
44
+ // Calculate range around current page
45
+ const leftSiblingIndex = Math.max(currentPage - siblingCount, boundaryCount + 1);
46
+ const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages - boundaryCount);
47
+ // Add ellipsis before current page range if needed
48
+ if (leftSiblingIndex > boundaryCount + 2) {
49
+ pages.push('ellipsis');
50
+ }
51
+ else if (leftSiblingIndex === boundaryCount + 2) {
52
+ pages.push(boundaryCount + 1);
53
+ }
54
+ // Add current page and siblings
55
+ for (let i = leftSiblingIndex; i <= rightSiblingIndex; i++) {
56
+ if (i > boundaryCount && i <= totalPages - boundaryCount) {
57
+ pages.push(i);
58
+ }
59
+ }
60
+ // Add ellipsis after current page range if needed
61
+ if (rightSiblingIndex < totalPages - boundaryCount - 1) {
62
+ pages.push('ellipsis');
63
+ }
64
+ else if (rightSiblingIndex === totalPages - boundaryCount - 1) {
65
+ pages.push(totalPages - boundaryCount);
66
+ }
67
+ // Always show last boundaryCount pages
68
+ for (let i = totalPages - boundaryCount + 1; i <= totalPages; i++) {
69
+ if (!pages.includes(i)) {
70
+ pages.push(i);
71
+ }
72
+ }
73
+ return pages;
74
+ };
75
+ /**
76
+ * Get button dimensions based on size
77
+ */
78
+ const getButtonDimensions = (size) => {
79
+ switch (size) {
80
+ case 'small':
81
+ return { minWidth: 32, height: 32, gap: 4, fontSize: 12 };
82
+ case 'medium':
83
+ return { minWidth: 40, height: 40, gap: 8, fontSize: 14 };
84
+ case 'large':
85
+ return { minWidth: 48, height: 48, gap: 8, fontSize: 16 };
86
+ }
87
+ };
88
+ /**
89
+ * Enhanced pagination component with responsive sizing and loading states.
90
+ * Built on React Native Paper components with consistent theming.
91
+ */
92
+ export const SPagination = ({ count = 0, page = 1, loading = false, onChange, size = 'medium', color = 'primary', variant = 'text', shape = 'circular', showFirstButton = false, showLastButton = false, hidePrevButton = false, hideNextButton = false, disabled = false, siblingCount = 1, boundaryCount = 1, style, }) => {
93
+ const { theme } = useSTheme();
94
+ // All hooks must be called before any conditional returns
95
+ const pageNumbers = useMemo(() => generatePageNumbers(page, count, siblingCount, boundaryCount), [page, count, siblingCount, boundaryCount]);
96
+ const buttonSize = size === 'large' ? 'large' : size === 'small' ? 'small' : 'medium';
97
+ const dimensions = getButtonDimensions(size);
98
+ // Shape-based border radius: circular = fully round, rounded = slightly rounded
99
+ const borderRadius = shape === 'circular' ? dimensions.height / 2 : theme.borderRadius.md;
100
+ const handlePageChange = (newPage) => {
101
+ if (onChange && newPage !== page && newPage >= 1 && newPage <= count) {
102
+ onChange({}, newPage);
103
+ }
104
+ };
105
+ // Hide pagination when there are no pages to display
106
+ if (count === 0) {
107
+ return _jsx(_Fragment, {});
108
+ }
109
+ // Show skeleton loading state while data is being fetched
110
+ if (loading) {
111
+ return _jsx(SPaginationSkeleton, { size: size, style: style });
112
+ }
113
+ // Determine inactive button variant based on pagination variant prop
114
+ // 'text': inactive pages have no border (text style)
115
+ // 'outlined': inactive pages have outlined border
116
+ const inactiveVariant = variant === 'outlined' ? 'outlined' : 'text';
117
+ return (_jsxs(View, { style: [
118
+ {
119
+ flexDirection: 'row',
120
+ alignItems: 'center',
121
+ justifyContent: 'center',
122
+ flexWrap: 'wrap',
123
+ gap: dimensions.gap,
124
+ },
125
+ style,
126
+ ], children: [showFirstButton && (_jsx(SIconButton, { icon: "page-first", size: buttonSize, disabled: disabled || page === 1, onPress: () => handlePageChange(1) })), !hidePrevButton && (_jsx(SIconButton, { icon: "chevron-left", size: buttonSize, disabled: disabled || page === 1, onPress: () => handlePageChange(page - 1) })), pageNumbers.map((pageNum, index) => {
127
+ if (pageNum === 'ellipsis') {
128
+ return (_jsx(View, { style: {
129
+ paddingHorizontal: size === 'small' ? 4 : 8,
130
+ justifyContent: 'center',
131
+ alignItems: 'center',
132
+ }, children: _jsx(SText, { variant: "body2", children: "..." }) }, `ellipsis-${index}`));
133
+ }
134
+ const isActive = pageNum === page;
135
+ return (_jsx(SButton, { variant: isActive ? 'contained' : inactiveVariant, color: color, size: buttonSize, disabled: disabled, onPress: () => handlePageChange(pageNum), style: [
136
+ {
137
+ minWidth: 0,
138
+ width: dimensions.minWidth,
139
+ height: dimensions.height,
140
+ borderRadius,
141
+ },
142
+ !isActive && variant === 'outlined' && { borderColor: theme.colors.divider },
143
+ ], contentStyle: { paddingHorizontal: 0 }, labelStyle: [{ marginHorizontal: 0 }, !isActive ? { color: theme.colors.text.primary } : undefined], children: String(pageNum) }, pageNum));
144
+ }), !hideNextButton && (_jsx(SIconButton, { icon: "chevron-right", size: buttonSize, disabled: disabled || page === count, onPress: () => handlePageChange(page + 1) })), showLastButton && (_jsx(SIconButton, { icon: "page-last", size: buttonSize, disabled: disabled || page === count, onPress: () => handlePageChange(count) }))] }));
145
+ };
146
+ SPagination.displayName = 'SPagination';
147
+ export default SPagination;
@@ -0,0 +1,3 @@
1
+ export { SRating, default } from './s-rating';
2
+ export type { SRatingProps, SRatingSize, SRatingColor } from './s-rating';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-rating/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ export { SRating, default } from './s-rating';
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { type StyleProp, type ViewStyle } from 'react-native';
3
+ /**
4
+ * Rating size variants - matches web SRating from @solostylist/ui-kit
5
+ */
6
+ export type SRatingSize = 'small' | 'medium' | 'large';
7
+ /**
8
+ * Rating color variants
9
+ */
10
+ export type SRatingColor = 'default' | 'primary' | 'warning';
11
+ /**
12
+ * Props interface for SRating component
13
+ */
14
+ export interface SRatingProps {
15
+ /** Current rating value */
16
+ value?: number | null;
17
+ /** Default value for uncontrolled component */
18
+ defaultValue?: number;
19
+ /** Maximum rating value (number of stars) */
20
+ max?: number;
21
+ /** Size of the rating icons */
22
+ size?: SRatingSize;
23
+ /** Color variant of the rating */
24
+ color?: SRatingColor;
25
+ /** Whether the rating is read-only */
26
+ readOnly?: boolean;
27
+ /** Whether the rating is disabled */
28
+ disabled?: boolean;
29
+ /** Precision of rating (0.5 for half stars, 1 for full stars) */
30
+ precision?: 0.5 | 1;
31
+ /** Callback fired when the rating changes */
32
+ onChange?: (event: any, value: number | null) => void;
33
+ /** Callback fired when hover state changes */
34
+ onChangeActive?: (event: any, value: number) => void;
35
+ /** Custom filled icon name (Material Community Icons) */
36
+ icon?: string;
37
+ /** Custom empty icon name (Material Community Icons) */
38
+ emptyIcon?: string;
39
+ /** Label text for accessibility */
40
+ name?: string;
41
+ /** Additional styles for the container */
42
+ style?: StyleProp<ViewStyle>;
43
+ /** Whether to highlight only the selected icon (vs all icons up to selected) */
44
+ highlightSelectedOnly?: boolean;
45
+ }
46
+ /**
47
+ * A star rating component that allows users to provide feedback and ratings.
48
+ * Built with custom implementation for React Native with consistent theming.
49
+ */
50
+ export declare const SRating: {
51
+ ({ value: controlledValue, defaultValue, max, size, color, readOnly, disabled, precision, onChange, onChangeActive, icon, emptyIcon, name, style, highlightSelectedOnly, }: SRatingProps): React.JSX.Element;
52
+ displayName: string;
53
+ };
54
+ export default SRating;
55
+ //# sourceMappingURL=s-rating.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s-rating.d.ts","sourceRoot":"","sources":["../../src/s-rating/s-rating.tsx"],"names":[],"mappings":"AACA,OAAO,KAA4B,MAAM,OAAO,CAAC;AACjD,OAAO,EAA0B,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAItF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,SAAS,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACtD,8CAA8C;IAC9C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAkCD;;;GAGG;AACH,eAAO,MAAM,OAAO;gLAgBjB,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO;;CAwNlC,CAAC;AAIF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,157 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MaterialCommunityIcons } from '@expo/vector-icons';
3
+ import { useMemo, useState } from 'react';
4
+ import { TouchableOpacity, View } from 'react-native';
5
+ import { brand, gray, lightBrand, lightGray, lightOrange, orange } from '@solostylist/core';
6
+ import { useSTheme } from '../theme';
7
+ /**
8
+ * Get color palettes for light and dark modes
9
+ */
10
+ const getColorPalettes = (color) => {
11
+ switch (color) {
12
+ case 'default':
13
+ return { light: lightGray, dark: gray };
14
+ case 'primary':
15
+ return { light: lightBrand, dark: brand };
16
+ case 'warning':
17
+ return { light: lightOrange, dark: orange };
18
+ default:
19
+ return { light: lightOrange, dark: orange };
20
+ }
21
+ };
22
+ /**
23
+ * Get icon size based on rating size
24
+ */
25
+ const getIconSize = (size) => {
26
+ switch (size) {
27
+ case 'small':
28
+ return 18;
29
+ case 'medium':
30
+ return 24;
31
+ case 'large':
32
+ return 32;
33
+ default:
34
+ return 24;
35
+ }
36
+ };
37
+ /**
38
+ * A star rating component that allows users to provide feedback and ratings.
39
+ * Built with custom implementation for React Native with consistent theming.
40
+ */
41
+ export const SRating = ({ value: controlledValue, defaultValue = 0, max = 5, size = 'medium', color = 'warning', readOnly = false, disabled = false, precision = 1, onChange, onChangeActive, icon = 'star', emptyIcon = 'star-outline', name, style, highlightSelectedOnly = false, }) => {
42
+ const { theme } = useSTheme();
43
+ const [internalValue, setInternalValue] = useState(defaultValue);
44
+ const [hoverValue, setHoverValue] = useState(null);
45
+ // Use controlled value if provided, otherwise use internal state
46
+ const currentValue = controlledValue !== undefined ? controlledValue : internalValue;
47
+ const displayValue = hoverValue !== null ? hoverValue : (currentValue ?? 0);
48
+ const { light: lightColor, dark: darkColor } = getColorPalettes(color);
49
+ const colorPalette = theme.dark ? darkColor : lightColor;
50
+ const iconSize = getIconSize(size);
51
+ // Calculate colors based on theme mode
52
+ const colors = useMemo(() => {
53
+ const isDark = theme.dark;
54
+ // Filled color - matches web rating styling (golden/amber for default)
55
+ const filledColor = isDark ? colorPalette[400] : colorPalette[600];
56
+ // Empty/unfilled color
57
+ const emptyColor = isDark ? gray[600] : lightGray[400];
58
+ // Disabled color
59
+ const disabledColor = isDark ? gray[700] : lightGray[300];
60
+ return {
61
+ filledColor,
62
+ emptyColor,
63
+ disabledColor,
64
+ };
65
+ }, [theme.dark, colorPalette]);
66
+ const handlePress = (index, isHalf = false) => {
67
+ if (readOnly || disabled)
68
+ return;
69
+ let newValue;
70
+ if (precision === 0.5 && isHalf) {
71
+ newValue = index + 0.5;
72
+ }
73
+ else {
74
+ newValue = index + 1;
75
+ }
76
+ // Allow deselecting by clicking the same value
77
+ if (newValue === currentValue) {
78
+ newValue = 0;
79
+ }
80
+ if (controlledValue === undefined) {
81
+ setInternalValue(newValue);
82
+ }
83
+ onChange?.(null, newValue === 0 ? null : newValue);
84
+ };
85
+ const handleHoverEnter = (index, isHalf = false) => {
86
+ if (readOnly || disabled)
87
+ return;
88
+ let hoverVal;
89
+ if (precision === 0.5 && isHalf) {
90
+ hoverVal = index + 0.5;
91
+ }
92
+ else {
93
+ hoverVal = index + 1;
94
+ }
95
+ setHoverValue(hoverVal);
96
+ onChangeActive?.(null, hoverVal);
97
+ };
98
+ const handleHoverLeave = () => {
99
+ if (readOnly || disabled)
100
+ return;
101
+ setHoverValue(null);
102
+ onChangeActive?.(null, -1);
103
+ };
104
+ const renderIcon = (index) => {
105
+ const isFilled = highlightSelectedOnly ? displayValue === index + 1 : displayValue >= index + 1;
106
+ const isHalfFilled = !highlightSelectedOnly && precision === 0.5 && displayValue === index + 0.5;
107
+ const iconColor = disabled
108
+ ? colors.disabledColor
109
+ : isFilled || isHalfFilled
110
+ ? colors.filledColor
111
+ : colors.emptyColor;
112
+ const iconName = isFilled ? icon : emptyIcon;
113
+ if (precision === 0.5) {
114
+ // Render half-star support
115
+ return (_jsxs(View, { style: {
116
+ position: 'relative',
117
+ }, children: [_jsx(TouchableOpacity, { onPress: () => handlePress(index, false), onPressIn: () => handleHoverEnter(index, false), onPressOut: handleHoverLeave, disabled: readOnly || disabled, activeOpacity: readOnly || disabled ? 1 : 0.7, accessibilityRole: "button", accessibilityLabel: name ? `${name} ${index + 1} stars` : `${index + 1} stars`, accessibilityState: { disabled: disabled || readOnly }, children: _jsxs(View, { style: {
118
+ flexDirection: 'row',
119
+ overflow: 'hidden',
120
+ }, children: [_jsx(MaterialCommunityIcons, { name: emptyIcon, size: iconSize, color: disabled ? colors.disabledColor : colors.emptyColor }), isHalfFilled && (_jsx(View, { style: {
121
+ position: 'absolute',
122
+ left: 0,
123
+ top: 0,
124
+ width: iconSize / 2,
125
+ overflow: 'hidden',
126
+ }, children: _jsx(MaterialCommunityIcons, { name: icon, size: iconSize, color: disabled ? colors.disabledColor : colors.filledColor }) })), isFilled && (_jsx(View, { style: [
127
+ {
128
+ position: 'absolute',
129
+ left: 0,
130
+ top: 0,
131
+ width: iconSize / 2,
132
+ overflow: 'hidden',
133
+ },
134
+ { width: iconSize },
135
+ ], children: _jsx(MaterialCommunityIcons, { name: icon, size: iconSize, color: disabled ? colors.disabledColor : colors.filledColor }) }))] }) }), _jsx(TouchableOpacity, { style: {
136
+ position: 'absolute',
137
+ left: 0,
138
+ top: 0,
139
+ width: iconSize / 2,
140
+ height: iconSize,
141
+ }, onPress: () => handlePress(index, true), onPressIn: () => handleHoverEnter(index, true), onPressOut: handleHoverLeave, disabled: readOnly || disabled, activeOpacity: 0, accessibilityLabel: name ? `${name} ${index + 0.5} stars` : `${index + 0.5} stars` })] }, index));
142
+ }
143
+ // Full star only (precision = 1)
144
+ return (_jsx(TouchableOpacity, { onPress: () => handlePress(index), onPressIn: () => handleHoverEnter(index), onPressOut: handleHoverLeave, disabled: readOnly || disabled, activeOpacity: readOnly || disabled ? 1 : 0.7, accessibilityRole: "button", accessibilityLabel: name ? `${name} ${index + 1} stars` : `${index + 1} stars`, accessibilityState: { disabled: disabled || readOnly }, children: _jsx(MaterialCommunityIcons, { name: iconName, size: iconSize, color: iconColor }) }, index));
145
+ };
146
+ return (_jsx(View, { style: [
147
+ {
148
+ flexDirection: 'row',
149
+ alignItems: 'center',
150
+ gap: 2,
151
+ opacity: disabled ? 0.5 : 1,
152
+ },
153
+ style,
154
+ ], accessibilityRole: "adjustable", accessibilityLabel: name, children: Array.from({ length: max }, (_, index) => renderIcon(index)) }));
155
+ };
156
+ SRating.displayName = 'SRating';
157
+ export default SRating;
@@ -0,0 +1,3 @@
1
+ export { SReview, default } from './s-review';
2
+ export type { SReviewProps } from './s-review';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-review/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ export { SReview, default } from './s-review';
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { type ImageSourcePropType, type StyleProp, type ViewStyle } from 'react-native';
3
+ /**
4
+ * Props interface for SReview component
5
+ */
6
+ export interface SReviewProps {
7
+ /** Unique identifier for the review (default: auto-generated) */
8
+ id?: string | number;
9
+ /** Name of the reviewer */
10
+ userName: string;
11
+ /** URL or local asset for the user avatar image (falls back to initials if not provided) */
12
+ userAvatar?: string | ImageSourcePropType;
13
+ /** Rating value */
14
+ rating: number;
15
+ /** The review text content */
16
+ reviewContent: string;
17
+ /** Date and time of the review (automatically converts to relative time) */
18
+ datetime: Date | string;
19
+ /** Array of image URLs or local assets to display with the review */
20
+ images?: (string | ImageSourcePropType)[];
21
+ /** Current user vote status for this review (default: null) */
22
+ userHelpfulVote?: 'yes' | 'no' | null;
23
+ /** Number of "Yes" votes for helpfulness (default: 0) */
24
+ helpfulYes?: number;
25
+ /** Number of "No" votes for helpfulness (default: 0) */
26
+ helpfulNo?: number;
27
+ /** Callback when user votes on helpfulness (returns null when un-voting) */
28
+ onHelpfulVote?: (reviewId: string | number, vote: 'yes' | 'no' | null) => void;
29
+ /** Whether to show the helpful voting section (default: true) */
30
+ showHelpfulSection?: boolean;
31
+ /** Size of the avatar in pixels (default: 48) */
32
+ avatarSize?: number;
33
+ /** Maximum rating value (default: 5) */
34
+ maxRating?: number;
35
+ /** Whether the rating is read-only (default: true) */
36
+ readOnly?: boolean;
37
+ /** Whether to show the divider at the bottom of the review (default: true) */
38
+ showDivider?: boolean;
39
+ /** Whether to show skeleton loading state instead of content (default: false) */
40
+ loading?: boolean;
41
+ /** Additional styles for the container */
42
+ style?: StyleProp<ViewStyle>;
43
+ /** Callback when an image is tapped */
44
+ onImagePress?: (image: string | ImageSourcePropType, index: number) => void;
45
+ }
46
+ /**
47
+ * A comprehensive review component that displays user reviews with avatar, name, rating, content, and helpful voting system.
48
+ * Built for React Native with consistent theming.
49
+ */
50
+ export declare const SReview: {
51
+ ({ id, userName, userAvatar, rating, reviewContent, datetime, images, userHelpfulVote, helpfulYes, helpfulNo, onHelpfulVote, showHelpfulSection, avatarSize, maxRating, readOnly, showDivider, loading, style, onImagePress, }: SReviewProps): React.JSX.Element;
52
+ displayName: string;
53
+ };
54
+ export default SReview;
55
+ //# sourceMappingURL=s-review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s-review.d.ts","sourceRoot":"","sources":["../../src/s-review/s-review.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8C,MAAM,OAAO,CAAC;AACnE,OAAO,EAKL,KAAK,mBAAmB,EACxB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAUtB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC1C,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,EAAE,IAAI,GAAG,MAAM,CAAC;IACxB,qEAAqE;IACrE,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,mBAAmB,CAAC,EAAE,CAAC;IAC1C,+DAA+D;IAC/D,eAAe,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC;IACtC,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/E,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,uCAAuC;IACvC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7E;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO;oOAoBjB,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO;;CA+QlC,CAAC;AAIF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,148 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useMemo, useState } from 'react';
3
+ import { Image, Pressable, ScrollView, View, } from 'react-native';
4
+ import { brand, getRelativeTime, gray, lightBrand, lightGray, red } from '@solostylist/core';
5
+ import { SAvatar } from '../s-avatar';
6
+ import { SDivider } from '../s-divider';
7
+ import { SIconButton } from '../s-icon-button';
8
+ import { SRating } from '../s-rating';
9
+ import { SSkeleton } from '../s-skeleton';
10
+ import { SText } from '../s-text';
11
+ import { useSTheme } from '../theme';
12
+ /**
13
+ * A comprehensive review component that displays user reviews with avatar, name, rating, content, and helpful voting system.
14
+ * Built for React Native with consistent theming.
15
+ */
16
+ export const SReview = ({ id, userName, userAvatar, rating, reviewContent, datetime, images, userHelpfulVote = null, helpfulYes = 0, helpfulNo = 0, onHelpfulVote, showHelpfulSection = true, avatarSize = 48, maxRating = 5, readOnly = true, showDivider = true, loading = false, style, onImagePress, }) => {
17
+ const { theme } = useSTheme();
18
+ const generatedId = useId();
19
+ const reviewId = id ?? generatedId;
20
+ const [localHelpfulYes, setLocalHelpfulYes] = useState(helpfulYes);
21
+ const [localHelpfulNo, setLocalHelpfulNo] = useState(helpfulNo);
22
+ const [userVote, setUserVote] = useState(userHelpfulVote);
23
+ const relativeTime = getRelativeTime(datetime);
24
+ useEffect(() => {
25
+ setUserVote(userHelpfulVote);
26
+ }, [userHelpfulVote]);
27
+ useEffect(() => {
28
+ setLocalHelpfulYes(helpfulYes);
29
+ }, [helpfulYes]);
30
+ useEffect(() => {
31
+ setLocalHelpfulNo(helpfulNo);
32
+ }, [helpfulNo]);
33
+ const handleHelpfulVote = (vote) => {
34
+ let newVote = vote;
35
+ if (userVote === vote) {
36
+ // User is un-voting
37
+ if (vote === 'yes') {
38
+ setLocalHelpfulYes((prev) => Math.max(0, prev - 1));
39
+ }
40
+ else {
41
+ setLocalHelpfulNo((prev) => Math.max(0, prev - 1));
42
+ }
43
+ setUserVote(null);
44
+ newVote = null;
45
+ }
46
+ else {
47
+ // User is voting or changing vote
48
+ if (userVote === 'yes') {
49
+ setLocalHelpfulYes((prev) => Math.max(0, prev - 1));
50
+ }
51
+ else if (userVote === 'no') {
52
+ setLocalHelpfulNo((prev) => Math.max(0, prev - 1));
53
+ }
54
+ if (vote === 'yes') {
55
+ setLocalHelpfulYes((prev) => prev + 1);
56
+ }
57
+ else {
58
+ setLocalHelpfulNo((prev) => prev + 1);
59
+ }
60
+ setUserVote(vote);
61
+ newVote = vote;
62
+ }
63
+ if (onHelpfulVote) {
64
+ onHelpfulVote(reviewId, newVote);
65
+ }
66
+ };
67
+ // Calculate colors based on theme mode
68
+ const colors = useMemo(() => {
69
+ const isDark = theme.dark;
70
+ // Get color palettes
71
+ const primaryPalette = isDark ? brand : lightBrand;
72
+ const errorPalette = isDark ? red : red;
73
+ const grayPalette = isDark ? gray : lightGray;
74
+ return {
75
+ thumbUpActive: primaryPalette[500],
76
+ thumbDownActive: errorPalette[500],
77
+ thumbInactive: grayPalette[500],
78
+ textPrimary: theme.colors.text.primary,
79
+ textSecondary: grayPalette[600],
80
+ captionText: grayPalette[600],
81
+ };
82
+ }, [theme.dark, theme.colors.text.primary]);
83
+ // Skeleton loading state
84
+ if (loading) {
85
+ return (_jsxs(View, { style: [
86
+ {
87
+ paddingVertical: theme.spacing.md,
88
+ },
89
+ style,
90
+ ], children: [_jsxs(View, { style: {
91
+ flexDirection: 'row',
92
+ gap: theme.spacing.md,
93
+ }, children: [_jsx(SSkeleton, { variant: "circular", width: avatarSize, height: avatarSize }), _jsxs(View, { style: {
94
+ flex: 1,
95
+ }, children: [_jsxs(View, { style: {
96
+ flexDirection: 'row',
97
+ alignItems: 'center',
98
+ gap: theme.spacing.md,
99
+ marginBottom: theme.spacing.xs,
100
+ }, children: [_jsx(SSkeleton, { variant: "text", width: 120, height: 14 }), _jsx(SSkeleton, { variant: "text", width: 80, height: 12 })] }), _jsx(SSkeleton, { variant: "text", width: 100, height: 20, style: { marginBottom: theme.spacing.xs } }), _jsx(SSkeleton, { variant: "text", width: "100%", height: 16, style: { marginBottom: 4 } }), _jsx(SSkeleton, { variant: "text", width: "90%", height: 16, style: { marginBottom: theme.spacing.md } }), showHelpfulSection && (_jsxs(View, { style: {
101
+ flexDirection: 'row',
102
+ alignItems: 'center',
103
+ gap: theme.spacing.xs,
104
+ marginTop: theme.spacing.sm,
105
+ }, children: [_jsx(SSkeleton, { variant: "text", width: 140, height: 14 }), _jsx(SSkeleton, { variant: "circular", width: 32, height: 32, style: { borderRadius: theme.borderRadius.sm } }), _jsx(SSkeleton, { variant: "text", width: 20, height: 14 }), _jsx(SSkeleton, { variant: "circular", width: 32, height: 32, style: { borderRadius: theme.borderRadius.sm } }), _jsx(SSkeleton, { variant: "text", width: 20, height: 14 })] }))] })] }), showDivider && _jsx(SDivider, {})] }));
106
+ }
107
+ return (_jsxs(View, { style: [
108
+ {
109
+ paddingVertical: theme.spacing.md,
110
+ },
111
+ style,
112
+ ], children: [_jsxs(View, { style: {
113
+ flexDirection: 'row',
114
+ gap: theme.spacing.md,
115
+ }, children: [_jsx(View, { style: {
116
+ paddingTop: 2,
117
+ }, children: _jsx(SAvatar, { name: userName, avatar: userAvatar, size: avatarSize }) }), _jsxs(View, { style: {
118
+ flex: 1,
119
+ }, children: [_jsxs(View, { style: {
120
+ flexDirection: 'row',
121
+ alignItems: 'center',
122
+ gap: theme.spacing.md,
123
+ marginBottom: theme.spacing.xs,
124
+ }, children: [_jsx(SText, { children: userName }), _jsx(SText, { variant: "caption", children: relativeTime })] }), _jsx(View, { style: {
125
+ marginBottom: theme.spacing.xs,
126
+ }, children: _jsx(SRating, { value: rating, max: maxRating, readOnly: readOnly, size: "small" }) }), _jsx(SText, { style: {
127
+ marginBottom: theme.spacing.md,
128
+ }, children: reviewContent }), images && images.length > 0 && (_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: {
129
+ flexDirection: 'row',
130
+ gap: theme.spacing.xs,
131
+ marginBottom: theme.spacing.md,
132
+ }, children: images.map((image, index) => (_jsx(Pressable, { style: {
133
+ width: 80,
134
+ height: 80,
135
+ borderRadius: theme.borderRadius.sm,
136
+ overflow: 'hidden',
137
+ }, onPress: () => onImagePress?.(image, index), children: _jsx(Image, { source: typeof image === 'string' ? { uri: image } : image, style: {
138
+ width: '100%',
139
+ height: '100%',
140
+ resizeMode: 'cover',
141
+ } }) }, index))) })), showHelpfulSection && (_jsxs(View, { style: {
142
+ flexDirection: 'row',
143
+ alignItems: 'center',
144
+ gap: theme.spacing.xs,
145
+ }, children: [_jsx(SText, { variant: "caption", style: { marginRight: theme.spacing.sm }, children: "Was this review helpful?" }), _jsx(SIconButton, { icon: userVote === 'yes' ? 'thumb-up' : 'thumb-up-outline', iconColor: userVote === 'yes' ? colors.thumbUpActive : colors.thumbInactive, onPress: () => handleHelpfulVote('yes') }), _jsx(SText, { variant: "caption", style: { marginLeft: 2 }, children: localHelpfulYes }), _jsx(SIconButton, { icon: userVote === 'no' ? 'thumb-down' : 'thumb-down-outline', iconColor: userVote === 'no' ? colors.thumbDownActive : colors.thumbInactive, onPress: () => handleHelpfulVote('no'), style: { marginLeft: theme.spacing.xs } }), _jsx(SText, { variant: "caption", style: { marginLeft: 2 }, children: localHelpfulNo })] }))] })] }), showDivider && _jsx(SDivider, {})] }));
146
+ };
147
+ SReview.displayName = 'SReview';
148
+ export default SReview;
@@ -0,0 +1,3 @@
1
+ export { SScrollToTop, type SScrollToTopProps } from './s-scroll-to-top';
2
+ export { default } from './s-scroll-to-top';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/s-scroll-to-top/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { SScrollToTop } from './s-scroll-to-top';
2
+ export { default } from './s-scroll-to-top';
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { type StyleProp, type ViewStyle } from 'react-native';
3
+ /**
4
+ * Props interface for SScrollToTop component
5
+ */
6
+ export interface SScrollToTopProps {
7
+ /** Handler called when the scroll-to-top button is clicked */
8
+ onPress?: () => void;
9
+ /** Whether the button should be visible (controlled externally via scroll position) */
10
+ visible: boolean;
11
+ /** Custom icon to display instead of the default arrow */
12
+ icon?: React.ReactNode;
13
+ /** Position from the bottom edge (default: 20) */
14
+ bottom?: number;
15
+ /** Position from the right edge (default: 20) */
16
+ right?: number;
17
+ /** Position from the left edge (omit to use right positioning) */
18
+ left?: number;
19
+ /** Button size (width and height, default: 40) */
20
+ size?: number;
21
+ /** Background color (defaults to theme background.paper) */
22
+ backgroundColor?: string;
23
+ /** Icon color (defaults to theme text color) */
24
+ iconColor?: string;
25
+ /** Custom style for the button container */
26
+ style?: StyleProp<ViewStyle>;
27
+ }
28
+ /**
29
+ * A floating button that appears when the user scrolls down and allows them to quickly return to the top.
30
+ *
31
+ * Note: For React Native, visibility must be controlled externally via the `visible` prop.
32
+ * Track scroll position using ScrollView's `onScroll` event and update `visible` accordingly.
33
+ */
34
+ export declare const SScrollToTop: React.MemoExoticComponent<({ onPress, visible, icon, bottom, right, left, size, backgroundColor, iconColor, style, }: SScrollToTopProps) => React.JSX.Element>;
35
+ export default SScrollToTop;
36
+ //# sourceMappingURL=s-scroll-to-top.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s-scroll-to-top.d.ts","sourceRoot":"","sources":["../../src/s-scroll-to-top/s-scroll-to-top.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoB,MAAM,OAAO,CAAC;AACzC,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGnF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uFAAuF;IACvF,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,wHAYpB,iBAAiB,KAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAmEzC,CAAC;AAIF,eAAe,YAAY,CAAC"}