@lmb-it/kitsconcerto-native 0.1.272 → 0.1.273
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/components/Avatar/index.cjs +49 -3
- package/components/Avatar/index.cjs.map +1 -1
- package/components/Avatar/index.js +49 -3
- package/components/Avatar/index.js.map +1 -1
- package/components/Gallery/index.cjs +57 -1
- package/components/Gallery/index.cjs.map +1 -1
- package/components/Gallery/index.js +57 -1
- package/components/Gallery/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -94,9 +94,55 @@ function Avatar(props) {
|
|
|
94
94
|
justifyContent: "center",
|
|
95
95
|
bgColor: surfaceBg,
|
|
96
96
|
children: masked ? /* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: "user", size: iconSize, color: "#FFFFFF" }) : hasImage ? (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
/*
|
|
98
|
+
* width/height via style: the Image primitive types dimensions
|
|
99
|
+
* differently per platform (web string vs native number).
|
|
100
|
+
*
|
|
101
|
+
* ⛔ `alt` IS REQUIRED HERE AND WAS MISSING — every Avatar with
|
|
102
|
+
* a `src` warned, on every render. gluestack's own image creator
|
|
103
|
+
* does `if (typeof alt !== 'string') console.warn('Please pass
|
|
104
|
+
* alt prop to Image component')`, so the check is on the TYPE,
|
|
105
|
+
* not on emptiness: a missing prop warns and an empty string
|
|
106
|
+
* does not. Measured in AlphaMatch on 5 October 2026 — 21
|
|
107
|
+
* warnings in a single sign-in from `AMTabBar`'s Me slot alone,
|
|
108
|
+
* which is one Avatar.
|
|
109
|
+
*
|
|
110
|
+
* ⛔ AND IT IS EMPTY ON PURPOSE, NOT `ariaLabel`. The Box above
|
|
111
|
+
* already carries `role="img"` and `aria-label={ariaLabel}`, so
|
|
112
|
+
* this image is PRESENTATION inside an element that is already
|
|
113
|
+
* named; repeating the label here would announce the person's
|
|
114
|
+
* name twice. Empty is the standing answer for a decorative
|
|
115
|
+
* image, and it is this library's own: `PhoneInput` passes
|
|
116
|
+
* `alt={countryISO ?? ""}` for exactly this reason.
|
|
117
|
+
*
|
|
118
|
+
* ⛔ `aria-hidden` IS THE OTHER HALF, AND `alt=""` ALONE WAS NOT
|
|
119
|
+
* ENOUGH — found by Codex, 5 October 2026. gluestack does not
|
|
120
|
+
* only read `alt`; it also forces
|
|
121
|
+
* `role={props.role || 'img'}`, so an empty alt leaves a
|
|
122
|
+
* SECOND, NAMELESS image role sitting under the labelled Box.
|
|
123
|
+
* On the web an `<img alt="">` is decorative and harmless; on
|
|
124
|
+
* React Native that role becomes `accessibilityRole="image"`
|
|
125
|
+
* and a screen reader can land on an unnamed image. Hiding the
|
|
126
|
+
* node is what makes it presentational on both platforms —
|
|
127
|
+
* RN maps `aria-hidden` to `accessibilityElementsHidden` on
|
|
128
|
+
* iOS and `importantForAccessibility="no-hide-descendants"` on
|
|
129
|
+
* Android. `alt=""` stays because it is what silences the
|
|
130
|
+
* warning: gluestack checks `typeof alt !== 'string'`.
|
|
131
|
+
*
|
|
132
|
+
* ⚠️ `Gallery` HAS THE SAME MISSING `alt`, one file away, and is
|
|
133
|
+
* NOT fixed here — it was outside what this change was opened
|
|
134
|
+
* for. Recorded in the session report rather than swept in.
|
|
135
|
+
*/
|
|
136
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
137
|
+
index_native$1.default,
|
|
138
|
+
{
|
|
139
|
+
src,
|
|
140
|
+
alt: "",
|
|
141
|
+
"aria-hidden": true,
|
|
142
|
+
style: { width: px, height: px },
|
|
143
|
+
objectFit: "cover"
|
|
144
|
+
}
|
|
145
|
+
)
|
|
100
146
|
) : showInitials ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
101
147
|
index_native$2.default,
|
|
102
148
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../../../src/components/Avatar/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IAvatarProps, IAvatarStackProps, AvatarSize} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/* ─── Size resolution ─────────────────────────────────────────── */\nconst SIZE_PX: Record<string, number> = {xs: 24, sm: 32, md: 40, lg: 56, xl: 72};\n\nfunction resolveSize(size: AvatarSize | undefined): number {\n if (typeof size === \"number\") return size;\n return SIZE_PX[size ?? \"md\"] ?? 40;\n}\n\n/* ─── Status dot → semantic token ─────────────────────────────── */\nconst STATUS_TOKENS: Record<string, string> = {\n online: \"success\",\n offline: \"gray.400\",\n away: \"warning\",\n busy: \"danger\",\n};\n\n/* ─── Deterministic monogram palette (all confirmed theme tokens) ─ */\nconst MONOGRAM_PALETTE = [\"brand\", \"primary.800\", \"success\", \"danger\", \"ink.700\"];\n\nfunction hashIndex(input: string, mod: number): number {\n let h = 0;\n for (let i = 0; i < input.length; i++) h = (h * 31 + input.charCodeAt(i)) >>> 0;\n return mod > 0 ? h % mod : 0;\n}\n\nfunction deriveInitials(name?: string, initials?: string): string {\n if (initials) return initials.slice(0, 3).toUpperCase();\n if (!name) return \"\";\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase();\n return (parts[0]!.charAt(0) + parts[parts.length - 1]!.charAt(0)).toUpperCase();\n}\n\n/**\n * Avatar — image / monogram / icon identity tile.\n *\n * Pure presentational and cross-platform: composed from the `Box` + `Text` +\n * `Image` + `Icon` primitives, so it renders identically on web and native\n * with no platform split. Tappability is the caller's concern (wrap in a\n * Pressable / Button).\n *\n * <Avatar src=\"https://…/me.jpg\" size=\"lg\" />\n * <Avatar name=\"Fouad K\" status=\"online\" />\n * <Avatar initials=\"C\" masked /> // identity-masked (Flex)\n * <Avatar name=\"Acme\" shape=\"rounded\" badge={<Icon name=\"briefcase\" />} />\n */\nexport function Avatar(props: IAvatarProps) {\n const {mergedProps} = useComponentDefaults<IAvatarProps>(\"Avatar\", props);\n const {\n src,\n name,\n initials,\n icon,\n size = \"md\",\n shape = \"circle\",\n color,\n status = \"none\",\n ring = false,\n ringColor = \"brand\",\n masked = false,\n maskFillColor = \"indigo.600\",\n badge,\n alt,\n testID,\n ...rest\n } = mergedProps;\n\n const px = resolveSize(size);\n const radius = shape === \"circle\" ? px / 2 : Math.round(px * 0.28);\n const fontSize = Math.max(10, Math.round(px * 0.4));\n const iconSize = Math.round(px * 0.5);\n const dot = Math.max(8, Math.round(px * 0.28));\n const ringW = px >= 56 ? 3 : 2;\n\n const resolvedInitials = deriveInitials(name, initials);\n const hasImage = !!src;\n const showInitials = !hasImage && resolvedInitials.length > 0;\n const showIcon = !hasImage && !showInitials && icon != null;\n\n const monogramBg =\n color ?? (MONOGRAM_PALETTE[hashIndex(name ?? resolvedInitials, MONOGRAM_PALETTE.length)] ?? \"brand\");\n const surfaceBg = masked ? maskFillColor : hasImage ? \"gray.100\" : monogramBg;\n const initialsColor = \"#FFFFFF\";\n\n const statusColor = STATUS_TOKENS[status] ?? \"gray.400\";\n const ariaLabel = alt ?? name ?? \"avatar\";\n\n const a11yProps = {\n role: \"img\",\n \"aria-label\": ariaLabel,\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box\n position=\"relative\"\n width={px}\n height={px}\n {...rest}\n {...(a11yProps as any)}\n >\n {/* Surface (clips the image / centres the monogram).\n overflow:hidden only when an image is present — clipping the\n monogram <Text> cuts off the glyph tops on native. */}\n <Box\n width=\"100%\"\n height=\"100%\"\n borderRadius={radius}\n overflow={hasImage && !masked ? \"hidden\" : \"visible\"}\n alignItems=\"center\"\n justifyContent=\"center\"\n bgColor={surfaceBg}\n >\n {masked ? (\n <Icon name=\"user\" size={iconSize} color=\"#FFFFFF\" />\n ) : hasImage ? (\n // width/height via style: the Image primitive types dimensions\n // differently per platform (web string vs native number).\n <Image src={src} style={{width: px, height: px} as any} objectFit=\"cover\" />\n ) : showInitials ? (\n <Text\n color={initialsColor}\n fontSize={fontSize}\n fontWeight=\"700\"\n lineHeight={Math.round(fontSize * 1.2)}\n textAlign=\"center\"\n >\n {resolvedInitials}\n </Text>\n ) : showIcon ? (\n <Icon name={icon as never} size={iconSize} color=\"gray.500\" />\n ) : null}\n </Box>\n\n {/* Ring overlay (solid active ring) */}\n {ring && (\n <Box\n position=\"absolute\"\n top={0}\n right={0}\n bottom={0}\n left={0}\n borderRadius={radius}\n borderWidth={ringW}\n borderStyle=\"solid\"\n borderColor={ringColor}\n />\n )}\n\n {/* Badge OR presence dot (badge wins the corner) */}\n {badge != null ? (\n <Box position=\"absolute\" bottom={-2} right={-2} zIndex={2}>\n {badge}\n </Box>\n ) : status !== \"none\" ? (\n <Box\n position=\"absolute\"\n bottom={0}\n right={0}\n zIndex={2}\n width={dot}\n height={dot}\n borderRadius={dot}\n bgColor={statusColor}\n borderWidth={2}\n borderColor=\"surface-card\"\n />\n ) : null}\n </Box>\n );\n}\n\n/**\n * AvatarStack — overlapping row of avatars with a \"+N\" overflow tile.\n * Each avatar gets a surface-coloured cut-out ring so it reads cleanly\n * against the one behind it.\n *\n * <AvatarStack items={members} max={4} activeIndex={0} />\n */\nexport function AvatarStack(props: IAvatarStackProps) {\n const {mergedProps} = useComponentDefaults<IAvatarStackProps>(\"AvatarStack\", props);\n const {items, max = 4, size = \"md\", overlap = 0.35, activeIndex, ringColor = \"brand\", testID, ...rest} = mergedProps;\n\n const px = resolveSize(size);\n const clampedOverlap = Math.min(Math.max(overlap, 0), 0.9);\n const overlapPx = Math.round(px * clampedOverlap);\n const strokeW = px >= 56 ? 3 : 2;\n\n const visible = items.slice(0, max);\n const overflow = items.length - visible.length;\n\n const cell = (key: string, node: React.ReactNode, i: number) => (\n <Box\n key={key}\n marginLeft={i === 0 ? 0 : -overlapPx}\n zIndex={items.length - i}\n borderRadius={9999}\n borderWidth={strokeW}\n borderColor=\"surface-card\"\n bgColor=\"surface-card\"\n >\n {node}\n </Box>\n );\n\n const a11yProps = {\n role: \"group\",\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box flexDirection=\"row\" alignItems=\"center\" {...rest} {...(a11yProps as any)}>\n {visible.map((item, i) =>\n cell(\n `avatar-${i}`,\n <Avatar\n {...item}\n size={size}\n ring={i === activeIndex || item.ring}\n ringColor={item.ringColor ?? ringColor}\n />,\n i,\n ),\n )}\n {overflow > 0 && cell(\"avatar-overflow\", <Avatar initials={`+${overflow}`} size={size} color=\"ink.400\" />, visible.length)}\n </Box>\n );\n}\n\nexport default Avatar;\n"],"names":["useComponentDefaults","jsxs","Box","jsx","Icon","Image","Text"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../../src/components/Avatar/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IAvatarProps, IAvatarStackProps, AvatarSize} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/* ─── Size resolution ─────────────────────────────────────────── */\nconst SIZE_PX: Record<string, number> = {xs: 24, sm: 32, md: 40, lg: 56, xl: 72};\n\nfunction resolveSize(size: AvatarSize | undefined): number {\n if (typeof size === \"number\") return size;\n return SIZE_PX[size ?? \"md\"] ?? 40;\n}\n\n/* ─── Status dot → semantic token ─────────────────────────────── */\nconst STATUS_TOKENS: Record<string, string> = {\n online: \"success\",\n offline: \"gray.400\",\n away: \"warning\",\n busy: \"danger\",\n};\n\n/* ─── Deterministic monogram palette (all confirmed theme tokens) ─ */\nconst MONOGRAM_PALETTE = [\"brand\", \"primary.800\", \"success\", \"danger\", \"ink.700\"];\n\nfunction hashIndex(input: string, mod: number): number {\n let h = 0;\n for (let i = 0; i < input.length; i++) h = (h * 31 + input.charCodeAt(i)) >>> 0;\n return mod > 0 ? h % mod : 0;\n}\n\nfunction deriveInitials(name?: string, initials?: string): string {\n if (initials) return initials.slice(0, 3).toUpperCase();\n if (!name) return \"\";\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase();\n return (parts[0]!.charAt(0) + parts[parts.length - 1]!.charAt(0)).toUpperCase();\n}\n\n/**\n * Avatar — image / monogram / icon identity tile.\n *\n * Pure presentational and cross-platform: composed from the `Box` + `Text` +\n * `Image` + `Icon` primitives, so it renders identically on web and native\n * with no platform split. Tappability is the caller's concern (wrap in a\n * Pressable / Button).\n *\n * <Avatar src=\"https://…/me.jpg\" size=\"lg\" />\n * <Avatar name=\"Fouad K\" status=\"online\" />\n * <Avatar initials=\"C\" masked /> // identity-masked (Flex)\n * <Avatar name=\"Acme\" shape=\"rounded\" badge={<Icon name=\"briefcase\" />} />\n */\nexport function Avatar(props: IAvatarProps) {\n const {mergedProps} = useComponentDefaults<IAvatarProps>(\"Avatar\", props);\n const {\n src,\n name,\n initials,\n icon,\n size = \"md\",\n shape = \"circle\",\n color,\n status = \"none\",\n ring = false,\n ringColor = \"brand\",\n masked = false,\n maskFillColor = \"indigo.600\",\n badge,\n alt,\n testID,\n ...rest\n } = mergedProps;\n\n const px = resolveSize(size);\n const radius = shape === \"circle\" ? px / 2 : Math.round(px * 0.28);\n const fontSize = Math.max(10, Math.round(px * 0.4));\n const iconSize = Math.round(px * 0.5);\n const dot = Math.max(8, Math.round(px * 0.28));\n const ringW = px >= 56 ? 3 : 2;\n\n const resolvedInitials = deriveInitials(name, initials);\n const hasImage = !!src;\n const showInitials = !hasImage && resolvedInitials.length > 0;\n const showIcon = !hasImage && !showInitials && icon != null;\n\n const monogramBg =\n color ?? (MONOGRAM_PALETTE[hashIndex(name ?? resolvedInitials, MONOGRAM_PALETTE.length)] ?? \"brand\");\n const surfaceBg = masked ? maskFillColor : hasImage ? \"gray.100\" : monogramBg;\n const initialsColor = \"#FFFFFF\";\n\n const statusColor = STATUS_TOKENS[status] ?? \"gray.400\";\n const ariaLabel = alt ?? name ?? \"avatar\";\n\n const a11yProps = {\n role: \"img\",\n \"aria-label\": ariaLabel,\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box\n position=\"relative\"\n width={px}\n height={px}\n {...rest}\n {...(a11yProps as any)}\n >\n {/* Surface (clips the image / centres the monogram).\n overflow:hidden only when an image is present — clipping the\n monogram <Text> cuts off the glyph tops on native. */}\n <Box\n width=\"100%\"\n height=\"100%\"\n borderRadius={radius}\n overflow={hasImage && !masked ? \"hidden\" : \"visible\"}\n alignItems=\"center\"\n justifyContent=\"center\"\n bgColor={surfaceBg}\n >\n {masked ? (\n <Icon name=\"user\" size={iconSize} color=\"#FFFFFF\" />\n ) : hasImage ? (\n /*\n * width/height via style: the Image primitive types dimensions\n * differently per platform (web string vs native number).\n *\n * ⛔ `alt` IS REQUIRED HERE AND WAS MISSING — every Avatar with\n * a `src` warned, on every render. gluestack's own image creator\n * does `if (typeof alt !== 'string') console.warn('Please pass\n * alt prop to Image component')`, so the check is on the TYPE,\n * not on emptiness: a missing prop warns and an empty string\n * does not. Measured in AlphaMatch on 5 October 2026 — 21\n * warnings in a single sign-in from `AMTabBar`'s Me slot alone,\n * which is one Avatar.\n *\n * ⛔ AND IT IS EMPTY ON PURPOSE, NOT `ariaLabel`. The Box above\n * already carries `role=\"img\"` and `aria-label={ariaLabel}`, so\n * this image is PRESENTATION inside an element that is already\n * named; repeating the label here would announce the person's\n * name twice. Empty is the standing answer for a decorative\n * image, and it is this library's own: `PhoneInput` passes\n * `alt={countryISO ?? \"\"}` for exactly this reason.\n *\n * ⛔ `aria-hidden` IS THE OTHER HALF, AND `alt=\"\"` ALONE WAS NOT\n * ENOUGH — found by Codex, 5 October 2026. gluestack does not\n * only read `alt`; it also forces\n * `role={props.role || 'img'}`, so an empty alt leaves a\n * SECOND, NAMELESS image role sitting under the labelled Box.\n * On the web an `<img alt=\"\">` is decorative and harmless; on\n * React Native that role becomes `accessibilityRole=\"image\"`\n * and a screen reader can land on an unnamed image. Hiding the\n * node is what makes it presentational on both platforms —\n * RN maps `aria-hidden` to `accessibilityElementsHidden` on\n * iOS and `importantForAccessibility=\"no-hide-descendants\"` on\n * Android. `alt=\"\"` stays because it is what silences the\n * warning: gluestack checks `typeof alt !== 'string'`.\n *\n * ⚠️ `Gallery` HAS THE SAME MISSING `alt`, one file away, and is\n * NOT fixed here — it was outside what this change was opened\n * for. Recorded in the session report rather than swept in.\n */\n <Image\n src={src}\n alt=\"\"\n aria-hidden\n style={{width: px, height: px} as any}\n objectFit=\"cover\"\n />\n ) : showInitials ? (\n <Text\n color={initialsColor}\n fontSize={fontSize}\n fontWeight=\"700\"\n lineHeight={Math.round(fontSize * 1.2)}\n textAlign=\"center\"\n >\n {resolvedInitials}\n </Text>\n ) : showIcon ? (\n <Icon name={icon as never} size={iconSize} color=\"gray.500\" />\n ) : null}\n </Box>\n\n {/* Ring overlay (solid active ring) */}\n {ring && (\n <Box\n position=\"absolute\"\n top={0}\n right={0}\n bottom={0}\n left={0}\n borderRadius={radius}\n borderWidth={ringW}\n borderStyle=\"solid\"\n borderColor={ringColor}\n />\n )}\n\n {/* Badge OR presence dot (badge wins the corner) */}\n {badge != null ? (\n <Box position=\"absolute\" bottom={-2} right={-2} zIndex={2}>\n {badge}\n </Box>\n ) : status !== \"none\" ? (\n <Box\n position=\"absolute\"\n bottom={0}\n right={0}\n zIndex={2}\n width={dot}\n height={dot}\n borderRadius={dot}\n bgColor={statusColor}\n borderWidth={2}\n borderColor=\"surface-card\"\n />\n ) : null}\n </Box>\n );\n}\n\n/**\n * AvatarStack — overlapping row of avatars with a \"+N\" overflow tile.\n * Each avatar gets a surface-coloured cut-out ring so it reads cleanly\n * against the one behind it.\n *\n * <AvatarStack items={members} max={4} activeIndex={0} />\n */\nexport function AvatarStack(props: IAvatarStackProps) {\n const {mergedProps} = useComponentDefaults<IAvatarStackProps>(\"AvatarStack\", props);\n const {items, max = 4, size = \"md\", overlap = 0.35, activeIndex, ringColor = \"brand\", testID, ...rest} = mergedProps;\n\n const px = resolveSize(size);\n const clampedOverlap = Math.min(Math.max(overlap, 0), 0.9);\n const overlapPx = Math.round(px * clampedOverlap);\n const strokeW = px >= 56 ? 3 : 2;\n\n const visible = items.slice(0, max);\n const overflow = items.length - visible.length;\n\n const cell = (key: string, node: React.ReactNode, i: number) => (\n <Box\n key={key}\n marginLeft={i === 0 ? 0 : -overlapPx}\n zIndex={items.length - i}\n borderRadius={9999}\n borderWidth={strokeW}\n borderColor=\"surface-card\"\n bgColor=\"surface-card\"\n >\n {node}\n </Box>\n );\n\n const a11yProps = {\n role: \"group\",\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box flexDirection=\"row\" alignItems=\"center\" {...rest} {...(a11yProps as any)}>\n {visible.map((item, i) =>\n cell(\n `avatar-${i}`,\n <Avatar\n {...item}\n size={size}\n ring={i === activeIndex || item.ring}\n ringColor={item.ringColor ?? ringColor}\n />,\n i,\n ),\n )}\n {overflow > 0 && cell(\"avatar-overflow\", <Avatar initials={`+${overflow}`} size={size} color=\"ink.400\" />, visible.length)}\n </Box>\n );\n}\n\nexport default Avatar;\n"],"names":["useComponentDefaults","jsxs","Box","jsx","Icon","Image","Text"],"mappings":"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"}
|
|
@@ -92,9 +92,55 @@ function Avatar(props) {
|
|
|
92
92
|
justifyContent: "center",
|
|
93
93
|
bgColor: surfaceBg,
|
|
94
94
|
children: masked ? /* @__PURE__ */ jsx(Icon, { name: "user", size: iconSize, color: "#FFFFFF" }) : hasImage ? (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
/*
|
|
96
|
+
* width/height via style: the Image primitive types dimensions
|
|
97
|
+
* differently per platform (web string vs native number).
|
|
98
|
+
*
|
|
99
|
+
* ⛔ `alt` IS REQUIRED HERE AND WAS MISSING — every Avatar with
|
|
100
|
+
* a `src` warned, on every render. gluestack's own image creator
|
|
101
|
+
* does `if (typeof alt !== 'string') console.warn('Please pass
|
|
102
|
+
* alt prop to Image component')`, so the check is on the TYPE,
|
|
103
|
+
* not on emptiness: a missing prop warns and an empty string
|
|
104
|
+
* does not. Measured in AlphaMatch on 5 October 2026 — 21
|
|
105
|
+
* warnings in a single sign-in from `AMTabBar`'s Me slot alone,
|
|
106
|
+
* which is one Avatar.
|
|
107
|
+
*
|
|
108
|
+
* ⛔ AND IT IS EMPTY ON PURPOSE, NOT `ariaLabel`. The Box above
|
|
109
|
+
* already carries `role="img"` and `aria-label={ariaLabel}`, so
|
|
110
|
+
* this image is PRESENTATION inside an element that is already
|
|
111
|
+
* named; repeating the label here would announce the person's
|
|
112
|
+
* name twice. Empty is the standing answer for a decorative
|
|
113
|
+
* image, and it is this library's own: `PhoneInput` passes
|
|
114
|
+
* `alt={countryISO ?? ""}` for exactly this reason.
|
|
115
|
+
*
|
|
116
|
+
* ⛔ `aria-hidden` IS THE OTHER HALF, AND `alt=""` ALONE WAS NOT
|
|
117
|
+
* ENOUGH — found by Codex, 5 October 2026. gluestack does not
|
|
118
|
+
* only read `alt`; it also forces
|
|
119
|
+
* `role={props.role || 'img'}`, so an empty alt leaves a
|
|
120
|
+
* SECOND, NAMELESS image role sitting under the labelled Box.
|
|
121
|
+
* On the web an `<img alt="">` is decorative and harmless; on
|
|
122
|
+
* React Native that role becomes `accessibilityRole="image"`
|
|
123
|
+
* and a screen reader can land on an unnamed image. Hiding the
|
|
124
|
+
* node is what makes it presentational on both platforms —
|
|
125
|
+
* RN maps `aria-hidden` to `accessibilityElementsHidden` on
|
|
126
|
+
* iOS and `importantForAccessibility="no-hide-descendants"` on
|
|
127
|
+
* Android. `alt=""` stays because it is what silences the
|
|
128
|
+
* warning: gluestack checks `typeof alt !== 'string'`.
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ `Gallery` HAS THE SAME MISSING `alt`, one file away, and is
|
|
131
|
+
* NOT fixed here — it was outside what this change was opened
|
|
132
|
+
* for. Recorded in the session report rather than swept in.
|
|
133
|
+
*/
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
Image,
|
|
136
|
+
{
|
|
137
|
+
src,
|
|
138
|
+
alt: "",
|
|
139
|
+
"aria-hidden": true,
|
|
140
|
+
style: { width: px, height: px },
|
|
141
|
+
objectFit: "cover"
|
|
142
|
+
}
|
|
143
|
+
)
|
|
98
144
|
) : showInitials ? /* @__PURE__ */ jsx(
|
|
99
145
|
Text,
|
|
100
146
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/components/Avatar/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IAvatarProps, IAvatarStackProps, AvatarSize} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/* ─── Size resolution ─────────────────────────────────────────── */\nconst SIZE_PX: Record<string, number> = {xs: 24, sm: 32, md: 40, lg: 56, xl: 72};\n\nfunction resolveSize(size: AvatarSize | undefined): number {\n if (typeof size === \"number\") return size;\n return SIZE_PX[size ?? \"md\"] ?? 40;\n}\n\n/* ─── Status dot → semantic token ─────────────────────────────── */\nconst STATUS_TOKENS: Record<string, string> = {\n online: \"success\",\n offline: \"gray.400\",\n away: \"warning\",\n busy: \"danger\",\n};\n\n/* ─── Deterministic monogram palette (all confirmed theme tokens) ─ */\nconst MONOGRAM_PALETTE = [\"brand\", \"primary.800\", \"success\", \"danger\", \"ink.700\"];\n\nfunction hashIndex(input: string, mod: number): number {\n let h = 0;\n for (let i = 0; i < input.length; i++) h = (h * 31 + input.charCodeAt(i)) >>> 0;\n return mod > 0 ? h % mod : 0;\n}\n\nfunction deriveInitials(name?: string, initials?: string): string {\n if (initials) return initials.slice(0, 3).toUpperCase();\n if (!name) return \"\";\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase();\n return (parts[0]!.charAt(0) + parts[parts.length - 1]!.charAt(0)).toUpperCase();\n}\n\n/**\n * Avatar — image / monogram / icon identity tile.\n *\n * Pure presentational and cross-platform: composed from the `Box` + `Text` +\n * `Image` + `Icon` primitives, so it renders identically on web and native\n * with no platform split. Tappability is the caller's concern (wrap in a\n * Pressable / Button).\n *\n * <Avatar src=\"https://…/me.jpg\" size=\"lg\" />\n * <Avatar name=\"Fouad K\" status=\"online\" />\n * <Avatar initials=\"C\" masked /> // identity-masked (Flex)\n * <Avatar name=\"Acme\" shape=\"rounded\" badge={<Icon name=\"briefcase\" />} />\n */\nexport function Avatar(props: IAvatarProps) {\n const {mergedProps} = useComponentDefaults<IAvatarProps>(\"Avatar\", props);\n const {\n src,\n name,\n initials,\n icon,\n size = \"md\",\n shape = \"circle\",\n color,\n status = \"none\",\n ring = false,\n ringColor = \"brand\",\n masked = false,\n maskFillColor = \"indigo.600\",\n badge,\n alt,\n testID,\n ...rest\n } = mergedProps;\n\n const px = resolveSize(size);\n const radius = shape === \"circle\" ? px / 2 : Math.round(px * 0.28);\n const fontSize = Math.max(10, Math.round(px * 0.4));\n const iconSize = Math.round(px * 0.5);\n const dot = Math.max(8, Math.round(px * 0.28));\n const ringW = px >= 56 ? 3 : 2;\n\n const resolvedInitials = deriveInitials(name, initials);\n const hasImage = !!src;\n const showInitials = !hasImage && resolvedInitials.length > 0;\n const showIcon = !hasImage && !showInitials && icon != null;\n\n const monogramBg =\n color ?? (MONOGRAM_PALETTE[hashIndex(name ?? resolvedInitials, MONOGRAM_PALETTE.length)] ?? \"brand\");\n const surfaceBg = masked ? maskFillColor : hasImage ? \"gray.100\" : monogramBg;\n const initialsColor = \"#FFFFFF\";\n\n const statusColor = STATUS_TOKENS[status] ?? \"gray.400\";\n const ariaLabel = alt ?? name ?? \"avatar\";\n\n const a11yProps = {\n role: \"img\",\n \"aria-label\": ariaLabel,\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box\n position=\"relative\"\n width={px}\n height={px}\n {...rest}\n {...(a11yProps as any)}\n >\n {/* Surface (clips the image / centres the monogram).\n overflow:hidden only when an image is present — clipping the\n monogram <Text> cuts off the glyph tops on native. */}\n <Box\n width=\"100%\"\n height=\"100%\"\n borderRadius={radius}\n overflow={hasImage && !masked ? \"hidden\" : \"visible\"}\n alignItems=\"center\"\n justifyContent=\"center\"\n bgColor={surfaceBg}\n >\n {masked ? (\n <Icon name=\"user\" size={iconSize} color=\"#FFFFFF\" />\n ) : hasImage ? (\n // width/height via style: the Image primitive types dimensions\n // differently per platform (web string vs native number).\n <Image src={src} style={{width: px, height: px} as any} objectFit=\"cover\" />\n ) : showInitials ? (\n <Text\n color={initialsColor}\n fontSize={fontSize}\n fontWeight=\"700\"\n lineHeight={Math.round(fontSize * 1.2)}\n textAlign=\"center\"\n >\n {resolvedInitials}\n </Text>\n ) : showIcon ? (\n <Icon name={icon as never} size={iconSize} color=\"gray.500\" />\n ) : null}\n </Box>\n\n {/* Ring overlay (solid active ring) */}\n {ring && (\n <Box\n position=\"absolute\"\n top={0}\n right={0}\n bottom={0}\n left={0}\n borderRadius={radius}\n borderWidth={ringW}\n borderStyle=\"solid\"\n borderColor={ringColor}\n />\n )}\n\n {/* Badge OR presence dot (badge wins the corner) */}\n {badge != null ? (\n <Box position=\"absolute\" bottom={-2} right={-2} zIndex={2}>\n {badge}\n </Box>\n ) : status !== \"none\" ? (\n <Box\n position=\"absolute\"\n bottom={0}\n right={0}\n zIndex={2}\n width={dot}\n height={dot}\n borderRadius={dot}\n bgColor={statusColor}\n borderWidth={2}\n borderColor=\"surface-card\"\n />\n ) : null}\n </Box>\n );\n}\n\n/**\n * AvatarStack — overlapping row of avatars with a \"+N\" overflow tile.\n * Each avatar gets a surface-coloured cut-out ring so it reads cleanly\n * against the one behind it.\n *\n * <AvatarStack items={members} max={4} activeIndex={0} />\n */\nexport function AvatarStack(props: IAvatarStackProps) {\n const {mergedProps} = useComponentDefaults<IAvatarStackProps>(\"AvatarStack\", props);\n const {items, max = 4, size = \"md\", overlap = 0.35, activeIndex, ringColor = \"brand\", testID, ...rest} = mergedProps;\n\n const px = resolveSize(size);\n const clampedOverlap = Math.min(Math.max(overlap, 0), 0.9);\n const overlapPx = Math.round(px * clampedOverlap);\n const strokeW = px >= 56 ? 3 : 2;\n\n const visible = items.slice(0, max);\n const overflow = items.length - visible.length;\n\n const cell = (key: string, node: React.ReactNode, i: number) => (\n <Box\n key={key}\n marginLeft={i === 0 ? 0 : -overlapPx}\n zIndex={items.length - i}\n borderRadius={9999}\n borderWidth={strokeW}\n borderColor=\"surface-card\"\n bgColor=\"surface-card\"\n >\n {node}\n </Box>\n );\n\n const a11yProps = {\n role: \"group\",\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box flexDirection=\"row\" alignItems=\"center\" {...rest} {...(a11yProps as any)}>\n {visible.map((item, i) =>\n cell(\n `avatar-${i}`,\n <Avatar\n {...item}\n size={size}\n ring={i === activeIndex || item.ring}\n ringColor={item.ringColor ?? ringColor}\n />,\n i,\n ),\n )}\n {overflow > 0 && cell(\"avatar-overflow\", <Avatar initials={`+${overflow}`} size={size} color=\"ink.400\" />, visible.length)}\n </Box>\n );\n}\n\nexport default Avatar;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/Avatar/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IAvatarProps, IAvatarStackProps, AvatarSize} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/* ─── Size resolution ─────────────────────────────────────────── */\nconst SIZE_PX: Record<string, number> = {xs: 24, sm: 32, md: 40, lg: 56, xl: 72};\n\nfunction resolveSize(size: AvatarSize | undefined): number {\n if (typeof size === \"number\") return size;\n return SIZE_PX[size ?? \"md\"] ?? 40;\n}\n\n/* ─── Status dot → semantic token ─────────────────────────────── */\nconst STATUS_TOKENS: Record<string, string> = {\n online: \"success\",\n offline: \"gray.400\",\n away: \"warning\",\n busy: \"danger\",\n};\n\n/* ─── Deterministic monogram palette (all confirmed theme tokens) ─ */\nconst MONOGRAM_PALETTE = [\"brand\", \"primary.800\", \"success\", \"danger\", \"ink.700\"];\n\nfunction hashIndex(input: string, mod: number): number {\n let h = 0;\n for (let i = 0; i < input.length; i++) h = (h * 31 + input.charCodeAt(i)) >>> 0;\n return mod > 0 ? h % mod : 0;\n}\n\nfunction deriveInitials(name?: string, initials?: string): string {\n if (initials) return initials.slice(0, 3).toUpperCase();\n if (!name) return \"\";\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase();\n return (parts[0]!.charAt(0) + parts[parts.length - 1]!.charAt(0)).toUpperCase();\n}\n\n/**\n * Avatar — image / monogram / icon identity tile.\n *\n * Pure presentational and cross-platform: composed from the `Box` + `Text` +\n * `Image` + `Icon` primitives, so it renders identically on web and native\n * with no platform split. Tappability is the caller's concern (wrap in a\n * Pressable / Button).\n *\n * <Avatar src=\"https://…/me.jpg\" size=\"lg\" />\n * <Avatar name=\"Fouad K\" status=\"online\" />\n * <Avatar initials=\"C\" masked /> // identity-masked (Flex)\n * <Avatar name=\"Acme\" shape=\"rounded\" badge={<Icon name=\"briefcase\" />} />\n */\nexport function Avatar(props: IAvatarProps) {\n const {mergedProps} = useComponentDefaults<IAvatarProps>(\"Avatar\", props);\n const {\n src,\n name,\n initials,\n icon,\n size = \"md\",\n shape = \"circle\",\n color,\n status = \"none\",\n ring = false,\n ringColor = \"brand\",\n masked = false,\n maskFillColor = \"indigo.600\",\n badge,\n alt,\n testID,\n ...rest\n } = mergedProps;\n\n const px = resolveSize(size);\n const radius = shape === \"circle\" ? px / 2 : Math.round(px * 0.28);\n const fontSize = Math.max(10, Math.round(px * 0.4));\n const iconSize = Math.round(px * 0.5);\n const dot = Math.max(8, Math.round(px * 0.28));\n const ringW = px >= 56 ? 3 : 2;\n\n const resolvedInitials = deriveInitials(name, initials);\n const hasImage = !!src;\n const showInitials = !hasImage && resolvedInitials.length > 0;\n const showIcon = !hasImage && !showInitials && icon != null;\n\n const monogramBg =\n color ?? (MONOGRAM_PALETTE[hashIndex(name ?? resolvedInitials, MONOGRAM_PALETTE.length)] ?? \"brand\");\n const surfaceBg = masked ? maskFillColor : hasImage ? \"gray.100\" : monogramBg;\n const initialsColor = \"#FFFFFF\";\n\n const statusColor = STATUS_TOKENS[status] ?? \"gray.400\";\n const ariaLabel = alt ?? name ?? \"avatar\";\n\n const a11yProps = {\n role: \"img\",\n \"aria-label\": ariaLabel,\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box\n position=\"relative\"\n width={px}\n height={px}\n {...rest}\n {...(a11yProps as any)}\n >\n {/* Surface (clips the image / centres the monogram).\n overflow:hidden only when an image is present — clipping the\n monogram <Text> cuts off the glyph tops on native. */}\n <Box\n width=\"100%\"\n height=\"100%\"\n borderRadius={radius}\n overflow={hasImage && !masked ? \"hidden\" : \"visible\"}\n alignItems=\"center\"\n justifyContent=\"center\"\n bgColor={surfaceBg}\n >\n {masked ? (\n <Icon name=\"user\" size={iconSize} color=\"#FFFFFF\" />\n ) : hasImage ? (\n /*\n * width/height via style: the Image primitive types dimensions\n * differently per platform (web string vs native number).\n *\n * ⛔ `alt` IS REQUIRED HERE AND WAS MISSING — every Avatar with\n * a `src` warned, on every render. gluestack's own image creator\n * does `if (typeof alt !== 'string') console.warn('Please pass\n * alt prop to Image component')`, so the check is on the TYPE,\n * not on emptiness: a missing prop warns and an empty string\n * does not. Measured in AlphaMatch on 5 October 2026 — 21\n * warnings in a single sign-in from `AMTabBar`'s Me slot alone,\n * which is one Avatar.\n *\n * ⛔ AND IT IS EMPTY ON PURPOSE, NOT `ariaLabel`. The Box above\n * already carries `role=\"img\"` and `aria-label={ariaLabel}`, so\n * this image is PRESENTATION inside an element that is already\n * named; repeating the label here would announce the person's\n * name twice. Empty is the standing answer for a decorative\n * image, and it is this library's own: `PhoneInput` passes\n * `alt={countryISO ?? \"\"}` for exactly this reason.\n *\n * ⛔ `aria-hidden` IS THE OTHER HALF, AND `alt=\"\"` ALONE WAS NOT\n * ENOUGH — found by Codex, 5 October 2026. gluestack does not\n * only read `alt`; it also forces\n * `role={props.role || 'img'}`, so an empty alt leaves a\n * SECOND, NAMELESS image role sitting under the labelled Box.\n * On the web an `<img alt=\"\">` is decorative and harmless; on\n * React Native that role becomes `accessibilityRole=\"image\"`\n * and a screen reader can land on an unnamed image. Hiding the\n * node is what makes it presentational on both platforms —\n * RN maps `aria-hidden` to `accessibilityElementsHidden` on\n * iOS and `importantForAccessibility=\"no-hide-descendants\"` on\n * Android. `alt=\"\"` stays because it is what silences the\n * warning: gluestack checks `typeof alt !== 'string'`.\n *\n * ⚠️ `Gallery` HAS THE SAME MISSING `alt`, one file away, and is\n * NOT fixed here — it was outside what this change was opened\n * for. Recorded in the session report rather than swept in.\n */\n <Image\n src={src}\n alt=\"\"\n aria-hidden\n style={{width: px, height: px} as any}\n objectFit=\"cover\"\n />\n ) : showInitials ? (\n <Text\n color={initialsColor}\n fontSize={fontSize}\n fontWeight=\"700\"\n lineHeight={Math.round(fontSize * 1.2)}\n textAlign=\"center\"\n >\n {resolvedInitials}\n </Text>\n ) : showIcon ? (\n <Icon name={icon as never} size={iconSize} color=\"gray.500\" />\n ) : null}\n </Box>\n\n {/* Ring overlay (solid active ring) */}\n {ring && (\n <Box\n position=\"absolute\"\n top={0}\n right={0}\n bottom={0}\n left={0}\n borderRadius={radius}\n borderWidth={ringW}\n borderStyle=\"solid\"\n borderColor={ringColor}\n />\n )}\n\n {/* Badge OR presence dot (badge wins the corner) */}\n {badge != null ? (\n <Box position=\"absolute\" bottom={-2} right={-2} zIndex={2}>\n {badge}\n </Box>\n ) : status !== \"none\" ? (\n <Box\n position=\"absolute\"\n bottom={0}\n right={0}\n zIndex={2}\n width={dot}\n height={dot}\n borderRadius={dot}\n bgColor={statusColor}\n borderWidth={2}\n borderColor=\"surface-card\"\n />\n ) : null}\n </Box>\n );\n}\n\n/**\n * AvatarStack — overlapping row of avatars with a \"+N\" overflow tile.\n * Each avatar gets a surface-coloured cut-out ring so it reads cleanly\n * against the one behind it.\n *\n * <AvatarStack items={members} max={4} activeIndex={0} />\n */\nexport function AvatarStack(props: IAvatarStackProps) {\n const {mergedProps} = useComponentDefaults<IAvatarStackProps>(\"AvatarStack\", props);\n const {items, max = 4, size = \"md\", overlap = 0.35, activeIndex, ringColor = \"brand\", testID, ...rest} = mergedProps;\n\n const px = resolveSize(size);\n const clampedOverlap = Math.min(Math.max(overlap, 0), 0.9);\n const overlapPx = Math.round(px * clampedOverlap);\n const strokeW = px >= 56 ? 3 : 2;\n\n const visible = items.slice(0, max);\n const overflow = items.length - visible.length;\n\n const cell = (key: string, node: React.ReactNode, i: number) => (\n <Box\n key={key}\n marginLeft={i === 0 ? 0 : -overlapPx}\n zIndex={items.length - i}\n borderRadius={9999}\n borderWidth={strokeW}\n borderColor=\"surface-card\"\n bgColor=\"surface-card\"\n >\n {node}\n </Box>\n );\n\n const a11yProps = {\n role: \"group\",\n ...(testID ? {testID, \"data-testid\": testID} : {}),\n } as Record<string, unknown>;\n\n return (\n <Box flexDirection=\"row\" alignItems=\"center\" {...rest} {...(a11yProps as any)}>\n {visible.map((item, i) =>\n cell(\n `avatar-${i}`,\n <Avatar\n {...item}\n size={size}\n ring={i === activeIndex || item.ring}\n ringColor={item.ringColor ?? ringColor}\n />,\n i,\n ),\n )}\n {overflow > 0 && cell(\"avatar-overflow\", <Avatar initials={`+${overflow}`} size={size} color=\"ink.400\" />, visible.length)}\n </Box>\n );\n}\n\nexport default Avatar;\n"],"names":[],"mappings":"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"}
|
|
@@ -37,7 +37,63 @@ function Gallery(rawProps) {
|
|
|
37
37
|
borderWidth: item.selected ? 2 : 0,
|
|
38
38
|
borderColor: item.selected ? "brand" : "transparent",
|
|
39
39
|
children: [
|
|
40
|
-
item.src != null && /*
|
|
40
|
+
item.src != null && /*
|
|
41
|
+
* ⛔ `alt` WAS MISSING, SO EVERY TILE WITH AN IMAGE
|
|
42
|
+
* WARNED — the same omission the Avatar carried, found
|
|
43
|
+
* beside it on 5 October 2026. gluestack's image creator
|
|
44
|
+
* does `if (typeof alt !== 'string') console.warn('Please
|
|
45
|
+
* pass alt prop to Image component')`, so the check is on
|
|
46
|
+
* the TYPE: a missing prop warns, an empty string does
|
|
47
|
+
* not.
|
|
48
|
+
*
|
|
49
|
+
* ⛔ AND `IGalleryItem.alt` HAS EXISTED ALL ALONG,
|
|
50
|
+
* published and documented as "Accessibility label", and
|
|
51
|
+
* this component read it NOWHERE. A caller could set it,
|
|
52
|
+
* tsc accepted it, and it reached nothing — so the fix is
|
|
53
|
+
* to honour the contract that was already written down,
|
|
54
|
+
* not to invent one.
|
|
55
|
+
*
|
|
56
|
+
* ⛔ `aria-hidden` IS CONDITIONAL HERE, WHERE THE
|
|
57
|
+
* AVATAR'S IS UNCONDITIONAL — the two structures differ.
|
|
58
|
+
* The Avatar's image sits inside a Box that already
|
|
59
|
+
* carries `role="img"` and `aria-label`, so it is ALWAYS
|
|
60
|
+
* redundant. This tile's Box carries no role and no label
|
|
61
|
+
* at all, so when a caller HAS described the image it is
|
|
62
|
+
* the only thing that can say so and it must stay in the
|
|
63
|
+
* tree.
|
|
64
|
+
*
|
|
65
|
+
* ⛔ AND WITH NO DESCRIPTION IT IS HIDDEN, NOT EXPOSED
|
|
66
|
+
* EMPTY — found by Codex, 5 October 2026, and the first
|
|
67
|
+
* draft of this fix had it backwards. gluestack forces
|
|
68
|
+
* `role={props.role || 'img'}` whatever `alt` says, so
|
|
69
|
+
* `alt=""` alone leaves an EXPOSED, NAMELESS image node on
|
|
70
|
+
* React Native. "No labelled ancestor" was the wrong
|
|
71
|
+
* reason to leave it exposed: an image with no name cannot
|
|
72
|
+
* speak for the tile either way.
|
|
73
|
+
*
|
|
74
|
+
* ⛔ AND IT DOES NOT FALL BACK TO `title`. The draft
|
|
75
|
+
* did, and `title` is ALREADY rendered as a Text sibling a
|
|
76
|
+
* few lines below — a screen reader would have read it
|
|
77
|
+
* twice: "image, Rewire" then "Rewire". The caption
|
|
78
|
+
* announces itself; the image either adds something the
|
|
79
|
+
* caller wrote in `alt`, or it adds nothing and gets out
|
|
80
|
+
* of the way.
|
|
81
|
+
*
|
|
82
|
+
* ⚠️ `alt=""` STAYS IN BOTH BRANCHES because it is what
|
|
83
|
+
* silences the warning: gluestack tests
|
|
84
|
+
* `typeof alt !== 'string'`, so a missing prop warns and
|
|
85
|
+
* an empty string does not.
|
|
86
|
+
*/
|
|
87
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
88
|
+
index_native.default,
|
|
89
|
+
{
|
|
90
|
+
src: item.src,
|
|
91
|
+
alt: item.alt ?? "",
|
|
92
|
+
"aria-hidden": !item.alt,
|
|
93
|
+
style: { width: "100%", height: "100%" },
|
|
94
|
+
objectFit: "cover"
|
|
95
|
+
}
|
|
96
|
+
),
|
|
41
97
|
item.topLeft != null && /* @__PURE__ */ jsxRuntime.jsx(index.default, { position: "absolute", top: 6, left: 6, children: item.topLeft }),
|
|
42
98
|
item.bottomRight != null && /* @__PURE__ */ jsxRuntime.jsx(index.default, { position: "absolute", bottom: 6, right: 6, children: item.bottomRight }),
|
|
43
99
|
item.selected && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../../../src/components/Gallery/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IGalleryProps} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/**\n * Gallery — responsive image grid with per-tile overlay slots, caption, and a\n * `selected` state (ring + check) for multiselect grids.\n *\n * Cross-platform and presentational (Box + Image + Text + Icon). Tappability is\n * the caller's concern — wrap tiles, or drive `selected` from your own handler.\n *\n * <Gallery columns={2} items={[\n * {src, title:'Rewire', meta:'12 May · Newtown', topLeft:<Chip label=\"VERIFIED\" size=\"sm\" />},\n * ]} />\n */\nexport function Gallery(rawProps: IGalleryProps) {\n const props = useComponentDefaults<IGalleryProps>(\"Gallery\", rawProps).mergedProps;\n const {items, columns = 2, gap = 8, aspectRatio = 1, radius = 12, testID, ...rest} = props;\n\n const cols = Math.max(1, Math.floor(columns));\n const widthPct = `${100 / cols}%`;\n const half = gap / 2;\n\n return (\n <Box\n flexDirection=\"row\"\n flexWrap=\"wrap\"\n marginLeft={-half}\n marginRight={-half}\n {...(rest as any)}\n {...(testID ? ({testID, \"data-testid\": testID} as any) : {})}\n >\n {items.map((item, i) => (\n <Box key={i} width={widthPct} paddingLeft={half} paddingRight={half} paddingBottom={gap}>\n {/* image card */}\n <Box\n position=\"relative\"\n width=\"100%\"\n aspectRatio={aspectRatio}\n borderRadius={radius}\n overflow=\"hidden\"\n bgColor=\"gray.100\"\n borderWidth={item.selected ? 2 : 0}\n borderColor={item.selected ? \"brand\" : \"transparent\"}\n >\n {item.src != null && (\n <Image src={item.src} style={{width: \"100%\", height: \"100%\"} as any} objectFit=\"cover\" />\n )}\n\n {item.topLeft != null && (\n <Box position=\"absolute\" top={6} left={6}>\n {item.topLeft}\n </Box>\n )}\n {item.bottomRight != null && (\n <Box position=\"absolute\" bottom={6} right={6}>\n {item.bottomRight}\n </Box>\n )}\n {item.selected && (\n <Box\n position=\"absolute\"\n top={6}\n right={6}\n width={22}\n height={22}\n borderRadius={999}\n bgColor=\"brand\"\n alignItems=\"center\"\n justifyContent=\"center\"\n borderWidth={2}\n borderColor=\"#FFFFFF\"\n >\n <Icon name=\"check\" size={12} color=\"#FFFFFF\" />\n </Box>\n )}\n </Box>\n\n {(item.title != null || item.meta != null) && (\n <Box paddingTop={6} gap={2}>\n {item.title != null && (\n <Text fontSize={13} fontWeight=\"700\" color=\"text\">\n {item.title}\n </Text>\n )}\n {item.meta != null && (\n <Text fontSize={11} color=\"text-secondary\">\n {item.meta}\n </Text>\n )}\n </Box>\n )}\n </Box>\n ))}\n </Box>\n );\n}\n\nexport default Gallery;\n"],"names":["useComponentDefaults","jsx","Box","jsxs","Image","Icon","Text"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../../src/components/Gallery/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IGalleryProps} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/**\n * Gallery — responsive image grid with per-tile overlay slots, caption, and a\n * `selected` state (ring + check) for multiselect grids.\n *\n * Cross-platform and presentational (Box + Image + Text + Icon). Tappability is\n * the caller's concern — wrap tiles, or drive `selected` from your own handler.\n *\n * <Gallery columns={2} items={[\n * {src, title:'Rewire', meta:'12 May · Newtown', topLeft:<Chip label=\"VERIFIED\" size=\"sm\" />},\n * ]} />\n */\nexport function Gallery(rawProps: IGalleryProps) {\n const props = useComponentDefaults<IGalleryProps>(\"Gallery\", rawProps).mergedProps;\n const {items, columns = 2, gap = 8, aspectRatio = 1, radius = 12, testID, ...rest} = props;\n\n const cols = Math.max(1, Math.floor(columns));\n const widthPct = `${100 / cols}%`;\n const half = gap / 2;\n\n return (\n <Box\n flexDirection=\"row\"\n flexWrap=\"wrap\"\n marginLeft={-half}\n marginRight={-half}\n {...(rest as any)}\n {...(testID ? ({testID, \"data-testid\": testID} as any) : {})}\n >\n {items.map((item, i) => (\n <Box key={i} width={widthPct} paddingLeft={half} paddingRight={half} paddingBottom={gap}>\n {/* image card */}\n <Box\n position=\"relative\"\n width=\"100%\"\n aspectRatio={aspectRatio}\n borderRadius={radius}\n overflow=\"hidden\"\n bgColor=\"gray.100\"\n borderWidth={item.selected ? 2 : 0}\n borderColor={item.selected ? \"brand\" : \"transparent\"}\n >\n {item.src != null && (\n /*\n * ⛔ `alt` WAS MISSING, SO EVERY TILE WITH AN IMAGE\n * WARNED — the same omission the Avatar carried, found\n * beside it on 5 October 2026. gluestack's image creator\n * does `if (typeof alt !== 'string') console.warn('Please\n * pass alt prop to Image component')`, so the check is on\n * the TYPE: a missing prop warns, an empty string does\n * not.\n *\n * ⛔ AND `IGalleryItem.alt` HAS EXISTED ALL ALONG,\n * published and documented as \"Accessibility label\", and\n * this component read it NOWHERE. A caller could set it,\n * tsc accepted it, and it reached nothing — so the fix is\n * to honour the contract that was already written down,\n * not to invent one.\n *\n * ⛔ `aria-hidden` IS CONDITIONAL HERE, WHERE THE\n * AVATAR'S IS UNCONDITIONAL — the two structures differ.\n * The Avatar's image sits inside a Box that already\n * carries `role=\"img\"` and `aria-label`, so it is ALWAYS\n * redundant. This tile's Box carries no role and no label\n * at all, so when a caller HAS described the image it is\n * the only thing that can say so and it must stay in the\n * tree.\n *\n * ⛔ AND WITH NO DESCRIPTION IT IS HIDDEN, NOT EXPOSED\n * EMPTY — found by Codex, 5 October 2026, and the first\n * draft of this fix had it backwards. gluestack forces\n * `role={props.role || 'img'}` whatever `alt` says, so\n * `alt=\"\"` alone leaves an EXPOSED, NAMELESS image node on\n * React Native. \"No labelled ancestor\" was the wrong\n * reason to leave it exposed: an image with no name cannot\n * speak for the tile either way.\n *\n * ⛔ AND IT DOES NOT FALL BACK TO `title`. The draft\n * did, and `title` is ALREADY rendered as a Text sibling a\n * few lines below — a screen reader would have read it\n * twice: \"image, Rewire\" then \"Rewire\". The caption\n * announces itself; the image either adds something the\n * caller wrote in `alt`, or it adds nothing and gets out\n * of the way.\n *\n * ⚠️ `alt=\"\"` STAYS IN BOTH BRANCHES because it is what\n * silences the warning: gluestack tests\n * `typeof alt !== 'string'`, so a missing prop warns and\n * an empty string does not.\n */\n <Image\n src={item.src}\n alt={item.alt ?? \"\"}\n aria-hidden={!item.alt}\n style={{width: \"100%\", height: \"100%\"} as any}\n objectFit=\"cover\"\n />\n )}\n\n {item.topLeft != null && (\n <Box position=\"absolute\" top={6} left={6}>\n {item.topLeft}\n </Box>\n )}\n {item.bottomRight != null && (\n <Box position=\"absolute\" bottom={6} right={6}>\n {item.bottomRight}\n </Box>\n )}\n {item.selected && (\n <Box\n position=\"absolute\"\n top={6}\n right={6}\n width={22}\n height={22}\n borderRadius={999}\n bgColor=\"brand\"\n alignItems=\"center\"\n justifyContent=\"center\"\n borderWidth={2}\n borderColor=\"#FFFFFF\"\n >\n <Icon name=\"check\" size={12} color=\"#FFFFFF\" />\n </Box>\n )}\n </Box>\n\n {(item.title != null || item.meta != null) && (\n <Box paddingTop={6} gap={2}>\n {item.title != null && (\n <Text fontSize={13} fontWeight=\"700\" color=\"text\">\n {item.title}\n </Text>\n )}\n {item.meta != null && (\n <Text fontSize={11} color=\"text-secondary\">\n {item.meta}\n </Text>\n )}\n </Box>\n )}\n </Box>\n ))}\n </Box>\n );\n}\n\nexport default Gallery;\n"],"names":["useComponentDefaults","jsx","Box","jsxs","Image","Icon","Text"],"mappings":"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"}
|
|
@@ -35,7 +35,63 @@ function Gallery(rawProps) {
|
|
|
35
35
|
borderWidth: item.selected ? 2 : 0,
|
|
36
36
|
borderColor: item.selected ? "brand" : "transparent",
|
|
37
37
|
children: [
|
|
38
|
-
item.src != null && /*
|
|
38
|
+
item.src != null && /*
|
|
39
|
+
* ⛔ `alt` WAS MISSING, SO EVERY TILE WITH AN IMAGE
|
|
40
|
+
* WARNED — the same omission the Avatar carried, found
|
|
41
|
+
* beside it on 5 October 2026. gluestack's image creator
|
|
42
|
+
* does `if (typeof alt !== 'string') console.warn('Please
|
|
43
|
+
* pass alt prop to Image component')`, so the check is on
|
|
44
|
+
* the TYPE: a missing prop warns, an empty string does
|
|
45
|
+
* not.
|
|
46
|
+
*
|
|
47
|
+
* ⛔ AND `IGalleryItem.alt` HAS EXISTED ALL ALONG,
|
|
48
|
+
* published and documented as "Accessibility label", and
|
|
49
|
+
* this component read it NOWHERE. A caller could set it,
|
|
50
|
+
* tsc accepted it, and it reached nothing — so the fix is
|
|
51
|
+
* to honour the contract that was already written down,
|
|
52
|
+
* not to invent one.
|
|
53
|
+
*
|
|
54
|
+
* ⛔ `aria-hidden` IS CONDITIONAL HERE, WHERE THE
|
|
55
|
+
* AVATAR'S IS UNCONDITIONAL — the two structures differ.
|
|
56
|
+
* The Avatar's image sits inside a Box that already
|
|
57
|
+
* carries `role="img"` and `aria-label`, so it is ALWAYS
|
|
58
|
+
* redundant. This tile's Box carries no role and no label
|
|
59
|
+
* at all, so when a caller HAS described the image it is
|
|
60
|
+
* the only thing that can say so and it must stay in the
|
|
61
|
+
* tree.
|
|
62
|
+
*
|
|
63
|
+
* ⛔ AND WITH NO DESCRIPTION IT IS HIDDEN, NOT EXPOSED
|
|
64
|
+
* EMPTY — found by Codex, 5 October 2026, and the first
|
|
65
|
+
* draft of this fix had it backwards. gluestack forces
|
|
66
|
+
* `role={props.role || 'img'}` whatever `alt` says, so
|
|
67
|
+
* `alt=""` alone leaves an EXPOSED, NAMELESS image node on
|
|
68
|
+
* React Native. "No labelled ancestor" was the wrong
|
|
69
|
+
* reason to leave it exposed: an image with no name cannot
|
|
70
|
+
* speak for the tile either way.
|
|
71
|
+
*
|
|
72
|
+
* ⛔ AND IT DOES NOT FALL BACK TO `title`. The draft
|
|
73
|
+
* did, and `title` is ALREADY rendered as a Text sibling a
|
|
74
|
+
* few lines below — a screen reader would have read it
|
|
75
|
+
* twice: "image, Rewire" then "Rewire". The caption
|
|
76
|
+
* announces itself; the image either adds something the
|
|
77
|
+
* caller wrote in `alt`, or it adds nothing and gets out
|
|
78
|
+
* of the way.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ `alt=""` STAYS IN BOTH BRANCHES because it is what
|
|
81
|
+
* silences the warning: gluestack tests
|
|
82
|
+
* `typeof alt !== 'string'`, so a missing prop warns and
|
|
83
|
+
* an empty string does not.
|
|
84
|
+
*/
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
Image,
|
|
87
|
+
{
|
|
88
|
+
src: item.src,
|
|
89
|
+
alt: item.alt ?? "",
|
|
90
|
+
"aria-hidden": !item.alt,
|
|
91
|
+
style: { width: "100%", height: "100%" },
|
|
92
|
+
objectFit: "cover"
|
|
93
|
+
}
|
|
94
|
+
),
|
|
39
95
|
item.topLeft != null && /* @__PURE__ */ jsx(Box, { position: "absolute", top: 6, left: 6, children: item.topLeft }),
|
|
40
96
|
item.bottomRight != null && /* @__PURE__ */ jsx(Box, { position: "absolute", bottom: 6, right: 6, children: item.bottomRight }),
|
|
41
97
|
item.selected && /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/components/Gallery/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IGalleryProps} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/**\n * Gallery — responsive image grid with per-tile overlay slots, caption, and a\n * `selected` state (ring + check) for multiselect grids.\n *\n * Cross-platform and presentational (Box + Image + Text + Icon). Tappability is\n * the caller's concern — wrap tiles, or drive `selected` from your own handler.\n *\n * <Gallery columns={2} items={[\n * {src, title:'Rewire', meta:'12 May · Newtown', topLeft:<Chip label=\"VERIFIED\" size=\"sm\" />},\n * ]} />\n */\nexport function Gallery(rawProps: IGalleryProps) {\n const props = useComponentDefaults<IGalleryProps>(\"Gallery\", rawProps).mergedProps;\n const {items, columns = 2, gap = 8, aspectRatio = 1, radius = 12, testID, ...rest} = props;\n\n const cols = Math.max(1, Math.floor(columns));\n const widthPct = `${100 / cols}%`;\n const half = gap / 2;\n\n return (\n <Box\n flexDirection=\"row\"\n flexWrap=\"wrap\"\n marginLeft={-half}\n marginRight={-half}\n {...(rest as any)}\n {...(testID ? ({testID, \"data-testid\": testID} as any) : {})}\n >\n {items.map((item, i) => (\n <Box key={i} width={widthPct} paddingLeft={half} paddingRight={half} paddingBottom={gap}>\n {/* image card */}\n <Box\n position=\"relative\"\n width=\"100%\"\n aspectRatio={aspectRatio}\n borderRadius={radius}\n overflow=\"hidden\"\n bgColor=\"gray.100\"\n borderWidth={item.selected ? 2 : 0}\n borderColor={item.selected ? \"brand\" : \"transparent\"}\n >\n {item.src != null && (\n <Image src={item.src} style={{width: \"100%\", height: \"100%\"} as any} objectFit=\"cover\" />\n )}\n\n {item.topLeft != null && (\n <Box position=\"absolute\" top={6} left={6}>\n {item.topLeft}\n </Box>\n )}\n {item.bottomRight != null && (\n <Box position=\"absolute\" bottom={6} right={6}>\n {item.bottomRight}\n </Box>\n )}\n {item.selected && (\n <Box\n position=\"absolute\"\n top={6}\n right={6}\n width={22}\n height={22}\n borderRadius={999}\n bgColor=\"brand\"\n alignItems=\"center\"\n justifyContent=\"center\"\n borderWidth={2}\n borderColor=\"#FFFFFF\"\n >\n <Icon name=\"check\" size={12} color=\"#FFFFFF\" />\n </Box>\n )}\n </Box>\n\n {(item.title != null || item.meta != null) && (\n <Box paddingTop={6} gap={2}>\n {item.title != null && (\n <Text fontSize={13} fontWeight=\"700\" color=\"text\">\n {item.title}\n </Text>\n )}\n {item.meta != null && (\n <Text fontSize={11} color=\"text-secondary\">\n {item.meta}\n </Text>\n )}\n </Box>\n )}\n </Box>\n ))}\n </Box>\n );\n}\n\nexport default Gallery;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/Gallery/index.tsx"],"sourcesContent":["import React from \"react\";\nimport type {IGalleryProps} from \"@lmb-it/kitsconcerto-types\";\nimport {Box, Text, Image, Icon} from \"../../primitives\";\nimport {useComponentDefaults} from \"../../hooks\";\n\n/**\n * Gallery — responsive image grid with per-tile overlay slots, caption, and a\n * `selected` state (ring + check) for multiselect grids.\n *\n * Cross-platform and presentational (Box + Image + Text + Icon). Tappability is\n * the caller's concern — wrap tiles, or drive `selected` from your own handler.\n *\n * <Gallery columns={2} items={[\n * {src, title:'Rewire', meta:'12 May · Newtown', topLeft:<Chip label=\"VERIFIED\" size=\"sm\" />},\n * ]} />\n */\nexport function Gallery(rawProps: IGalleryProps) {\n const props = useComponentDefaults<IGalleryProps>(\"Gallery\", rawProps).mergedProps;\n const {items, columns = 2, gap = 8, aspectRatio = 1, radius = 12, testID, ...rest} = props;\n\n const cols = Math.max(1, Math.floor(columns));\n const widthPct = `${100 / cols}%`;\n const half = gap / 2;\n\n return (\n <Box\n flexDirection=\"row\"\n flexWrap=\"wrap\"\n marginLeft={-half}\n marginRight={-half}\n {...(rest as any)}\n {...(testID ? ({testID, \"data-testid\": testID} as any) : {})}\n >\n {items.map((item, i) => (\n <Box key={i} width={widthPct} paddingLeft={half} paddingRight={half} paddingBottom={gap}>\n {/* image card */}\n <Box\n position=\"relative\"\n width=\"100%\"\n aspectRatio={aspectRatio}\n borderRadius={radius}\n overflow=\"hidden\"\n bgColor=\"gray.100\"\n borderWidth={item.selected ? 2 : 0}\n borderColor={item.selected ? \"brand\" : \"transparent\"}\n >\n {item.src != null && (\n /*\n * ⛔ `alt` WAS MISSING, SO EVERY TILE WITH AN IMAGE\n * WARNED — the same omission the Avatar carried, found\n * beside it on 5 October 2026. gluestack's image creator\n * does `if (typeof alt !== 'string') console.warn('Please\n * pass alt prop to Image component')`, so the check is on\n * the TYPE: a missing prop warns, an empty string does\n * not.\n *\n * ⛔ AND `IGalleryItem.alt` HAS EXISTED ALL ALONG,\n * published and documented as \"Accessibility label\", and\n * this component read it NOWHERE. A caller could set it,\n * tsc accepted it, and it reached nothing — so the fix is\n * to honour the contract that was already written down,\n * not to invent one.\n *\n * ⛔ `aria-hidden` IS CONDITIONAL HERE, WHERE THE\n * AVATAR'S IS UNCONDITIONAL — the two structures differ.\n * The Avatar's image sits inside a Box that already\n * carries `role=\"img\"` and `aria-label`, so it is ALWAYS\n * redundant. This tile's Box carries no role and no label\n * at all, so when a caller HAS described the image it is\n * the only thing that can say so and it must stay in the\n * tree.\n *\n * ⛔ AND WITH NO DESCRIPTION IT IS HIDDEN, NOT EXPOSED\n * EMPTY — found by Codex, 5 October 2026, and the first\n * draft of this fix had it backwards. gluestack forces\n * `role={props.role || 'img'}` whatever `alt` says, so\n * `alt=\"\"` alone leaves an EXPOSED, NAMELESS image node on\n * React Native. \"No labelled ancestor\" was the wrong\n * reason to leave it exposed: an image with no name cannot\n * speak for the tile either way.\n *\n * ⛔ AND IT DOES NOT FALL BACK TO `title`. The draft\n * did, and `title` is ALREADY rendered as a Text sibling a\n * few lines below — a screen reader would have read it\n * twice: \"image, Rewire\" then \"Rewire\". The caption\n * announces itself; the image either adds something the\n * caller wrote in `alt`, or it adds nothing and gets out\n * of the way.\n *\n * ⚠️ `alt=\"\"` STAYS IN BOTH BRANCHES because it is what\n * silences the warning: gluestack tests\n * `typeof alt !== 'string'`, so a missing prop warns and\n * an empty string does not.\n */\n <Image\n src={item.src}\n alt={item.alt ?? \"\"}\n aria-hidden={!item.alt}\n style={{width: \"100%\", height: \"100%\"} as any}\n objectFit=\"cover\"\n />\n )}\n\n {item.topLeft != null && (\n <Box position=\"absolute\" top={6} left={6}>\n {item.topLeft}\n </Box>\n )}\n {item.bottomRight != null && (\n <Box position=\"absolute\" bottom={6} right={6}>\n {item.bottomRight}\n </Box>\n )}\n {item.selected && (\n <Box\n position=\"absolute\"\n top={6}\n right={6}\n width={22}\n height={22}\n borderRadius={999}\n bgColor=\"brand\"\n alignItems=\"center\"\n justifyContent=\"center\"\n borderWidth={2}\n borderColor=\"#FFFFFF\"\n >\n <Icon name=\"check\" size={12} color=\"#FFFFFF\" />\n </Box>\n )}\n </Box>\n\n {(item.title != null || item.meta != null) && (\n <Box paddingTop={6} gap={2}>\n {item.title != null && (\n <Text fontSize={13} fontWeight=\"700\" color=\"text\">\n {item.title}\n </Text>\n )}\n {item.meta != null && (\n <Text fontSize={11} color=\"text-secondary\">\n {item.meta}\n </Text>\n )}\n </Box>\n )}\n </Box>\n ))}\n </Box>\n );\n}\n\nexport default Gallery;\n"],"names":[],"mappings":"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"}
|