minerva-design 1.13.0 → 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # minerva-design
2
2
 
3
+ ## 1.13.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 73e21ad: Taro: page sections keep their bottom-only 24px spacing now that they also match the shared `.mn-page-section` rule (no extra top margin).
8
+
9
+ ## 1.13.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 5799c7d: Taro: `PageHeader` and `PageSection` are now their own blocks (`mn-page-header` / `mn-page-section`, `data-minerva="page-header"` / `"page-section"`, matching React) and no longer carry the `mn-page` root class, so app rules on `.mn-page` (full-height, page padding) stop leaking onto headers and sections. The `mn-page__header` / `mn-page__section` classes are kept.
14
+
15
+ React Native: Flashcard keeps plain-text faces centred but stretches rich content to the full face width, so it lines up with the face label instead of shrinking and drifting off-centre.
16
+
3
17
  ## 1.13.0
4
18
 
5
19
  ### Minor Changes
@@ -1,2 +1,2 @@
1
- const e=require("../../theme/MinervaProvider.cjs"),t=require("../../internal/parts.cjs"),n=require("../../internal/styles.cjs"),r=require("../../internal/useControllable.cjs");let i=require("react"),a=require("react-native"),o=require("react/jsx-runtime");var s={small:`3`,medium:`5`,large:`6`},c={small:`md`,medium:`lg`,large:`xl`},ee=450,l=200,u=e=>e!=null&&e!==!1&&e!==``,te=e=>typeof e==`string`||typeof e==`number`,d=e=>{if(typeof e==`number`&&Number.isFinite(e))return e;let t=/^\s*(\d+(?:\.\d+)?)\s*(px|rem)?\s*$/.exec(String(e??``));if(!t)return 192;let n=Number(t[1]);return t[2]===`rem`?n*16:n};function f({front:f,back:p,footer:m,frontLabel:h,backLabel:g,hint:_,showHint:v=!1,flipped:y,defaultFlipped:ne=!1,onFlippedChange:b,flipOnClick:x=!0,disabled:S=!1,axis:C=`y`,variant:w=`elevated`,size:T=`medium`,minHeight:E=192,style:D,accessibilityLabel:O,accessibilityState:re,...ie}){let{tokens:k,fonts:ae,reducedMotion:A}=e.useTheme(),{t:j}=e.useI18n(),[M,N]=r.useControllable(y,ne,b),[P]=(0,i.useState)(()=>new a.Animated.Value(+!!M)),F=(0,i.useRef)(!0);(0,i.useEffect)(()=>{let e=+!!M;if(F.current){F.current=!1,P.setValue(e);return}let t=a.Animated.timing(P,{toValue:e,duration:A?l:ee,easing:a.Easing.inOut(a.Easing.cubic),useNativeDriver:!1});return t.start(),()=>t.stop()},[M,A,P]);let[I,L]=(0,i.useState)({front:0,back:0}),R=k.space[s[T]],z=Math.max(d(E),I.front+R*2,I.back+R*2),B=e=>t=>{let n=Math.ceil(t.nativeEvent.layout.height);L(t=>t[e]===n?t:{...t,[e]:n})},V=ae.sans,H=x&&!S,U=h===void 0?j(`flashcard.frontLabel`):h,W=g===void 0?j(`flashcard.backLabel`):g,G=_===void 0?j(`flashcard.hint`):_,K={borderRadius:k.radius.xl,padding:R,justifyContent:`center`,backfaceVisibility:`hidden`,...w===`outline`?{backgroundColor:k.colors[`surface-color`],borderWidth:1,borderColor:k.colors[`border-strong-color`]}:w===`filled`?{backgroundColor:k.colors[`surface-muted-color`]}:{backgroundColor:k.colors[`surface-elevated-color`],...n.shadowStyle(k.shadows.md)}},q=C===`x`?`rotateX`:`rotateY`,J=e=>{let t=e===`back`;return A?{opacity:P.interpolate({inputRange:[0,1],outputRange:t?[0,1]:[1,0]})}:{opacity:P.interpolate({inputRange:[0,.5,.5001,1],outputRange:t?[0,0,1,1]:[1,1,0,0]}),transform:[{perspective:1200},{[q]:P.interpolate({inputRange:[0,1],outputRange:t?[`180deg`,`360deg`]:[`0deg`,`180deg`]})}]}},Y=(e,t)=>te(e)?(0,o.jsx)(a.Text,{style:t,children:e}):e,X=e=>{let r=e===`back`,i=r?!M:M,s=r?W:U;return(0,o.jsx)(a.Animated.View,{...t.part(`flashcard`,e),pointerEvents:i?`none`:`auto`,accessibilityElementsHidden:i,importantForAccessibility:i?`no-hide-descendants`:`auto`,accessibilityLiveRegion:r?`polite`:void 0,style:[a.StyleSheet.absoluteFill,K,J(e)],children:(0,o.jsxs)(a.View,{onLayout:B(e),style:{gap:k.space[2]},children:[u(s)?(0,o.jsx)(a.View,{...t.part(`flashcard`,`label`),children:Y(s,{...n.textStyle(k,`xs`,V),fontWeight:n.weight(k,`semibold`),color:k.colors[`text-muted-color`],letterSpacing:.5,textTransform:`uppercase`})}):null,(0,o.jsx)(a.View,{style:{alignItems:`center`},children:Y(r?p:f,{...n.textStyle(k,c[T],V),color:k.colors[`text-color`],textAlign:`center`})}),!r&&v&&x&&u(G)?(0,o.jsx)(a.View,{...t.part(`flashcard`,`hint`),children:Y(G,{...n.textStyle(k,`xs`,V),color:k.colors[`text-muted-color`],textAlign:`center`})}):null]})})},Z=(0,o.jsxs)(o.Fragment,{children:[X(`front`),X(`back`)]}),Q={height:z,opacity:S?.6:1},$=t.part(`flashcard`,`card`,{state:M?`active`:`inactive`,disabled:S});return(0,o.jsxs)(a.View,{...ie,...t.part(`flashcard`,`root`,{state:M?`active`:`inactive`,disabled:S,variant:w,size:T}),style:[{gap:k.space[3]},D],children:[x?(0,o.jsx)(a.Pressable,{...$,accessibilityRole:`button`,accessibilityLabel:O,accessibilityState:{...re,selected:M,disabled:S},disabled:S,onPress:()=>{H&&N(!M)},style:Q,children:Z}):(0,o.jsx)(a.View,{...$,accessibilityLabel:O,style:Q,children:Z}),u(m)?(0,o.jsx)(a.View,{...t.part(`flashcard`,`footer`),style:{flexDirection:`row`,flexWrap:`wrap`,justifyContent:`center`,gap:k.space[2]},children:m}):null]})}exports.Flashcard=f,exports.flashcardMinHeight=d;
1
+ const e=require("../../theme/MinervaProvider.cjs"),t=require("../../internal/parts.cjs"),n=require("../../internal/styles.cjs"),r=require("../../internal/useControllable.cjs");let i=require("react"),a=require("react-native"),o=require("react/jsx-runtime");var s={small:`3`,medium:`5`,large:`6`},ee={small:`md`,medium:`lg`,large:`xl`},te=450,c=200,l=e=>e!=null&&e!==!1&&e!==``,u=e=>typeof e==`string`||typeof e==`number`,d=e=>{if(typeof e==`number`&&Number.isFinite(e))return e;let t=/^\s*(\d+(?:\.\d+)?)\s*(px|rem)?\s*$/.exec(String(e??``));if(!t)return 192;let n=Number(t[1]);return t[2]===`rem`?n*16:n};function f({front:f,back:p,footer:m,frontLabel:h,backLabel:g,hint:_,showHint:v=!1,flipped:y,defaultFlipped:b=!1,onFlippedChange:ne,flipOnClick:x=!0,disabled:S=!1,axis:C=`y`,variant:w=`elevated`,size:T=`medium`,minHeight:E=192,style:D,accessibilityLabel:O,accessibilityState:k,...re}){let{tokens:A,fonts:ie,reducedMotion:j}=e.useTheme(),{t:M}=e.useI18n(),[N,P]=r.useControllable(y,b,ne),[F]=(0,i.useState)(()=>new a.Animated.Value(+!!N)),I=(0,i.useRef)(!0);(0,i.useEffect)(()=>{let e=+!!N;if(I.current){I.current=!1,F.setValue(e);return}let t=a.Animated.timing(F,{toValue:e,duration:j?c:te,easing:a.Easing.inOut(a.Easing.cubic),useNativeDriver:!1});return t.start(),()=>t.stop()},[N,j,F]);let[L,R]=(0,i.useState)({front:0,back:0}),z=A.space[s[T]],B=Math.max(d(E),L.front+z*2,L.back+z*2),V=e=>t=>{let n=Math.ceil(t.nativeEvent.layout.height);R(t=>t[e]===n?t:{...t,[e]:n})},H=ie.sans,U=x&&!S,W=h===void 0?M(`flashcard.frontLabel`):h,G=g===void 0?M(`flashcard.backLabel`):g,K=_===void 0?M(`flashcard.hint`):_,q={borderRadius:A.radius.xl,padding:z,justifyContent:`center`,backfaceVisibility:`hidden`,...w===`outline`?{backgroundColor:A.colors[`surface-color`],borderWidth:1,borderColor:A.colors[`border-strong-color`]}:w===`filled`?{backgroundColor:A.colors[`surface-muted-color`]}:{backgroundColor:A.colors[`surface-elevated-color`],...n.shadowStyle(A.shadows.md)}},J=C===`x`?`rotateX`:`rotateY`,ae=e=>{let t=e===`back`;return j?{opacity:F.interpolate({inputRange:[0,1],outputRange:t?[0,1]:[1,0]})}:{opacity:F.interpolate({inputRange:[0,.5,.5001,1],outputRange:t?[0,0,1,1]:[1,1,0,0]}),transform:[{perspective:1200},{[J]:F.interpolate({inputRange:[0,1],outputRange:t?[`180deg`,`360deg`]:[`0deg`,`180deg`]})}]}},Y=(e,t)=>u(e)?(0,o.jsx)(a.Text,{style:t,children:e}):e,X=e=>{let r=e===`back`,i=r?!N:N,s=r?G:W;return(0,o.jsx)(a.Animated.View,{...t.part(`flashcard`,e),pointerEvents:i?`none`:`auto`,accessibilityElementsHidden:i,importantForAccessibility:i?`no-hide-descendants`:`auto`,accessibilityLiveRegion:r?`polite`:void 0,style:[a.StyleSheet.absoluteFill,q,ae(e)],children:(0,o.jsxs)(a.View,{onLayout:V(e),style:{gap:A.space[2]},children:[l(s)?(0,o.jsx)(a.View,{...t.part(`flashcard`,`label`),children:Y(s,{...n.textStyle(A,`xs`,H),fontWeight:n.weight(A,`semibold`),color:A.colors[`text-muted-color`],letterSpacing:.5,textTransform:`uppercase`})}):null,(0,o.jsx)(a.View,{style:{alignItems:u(r?p:f)?`center`:`stretch`},children:Y(r?p:f,{...n.textStyle(A,ee[T],H),color:A.colors[`text-color`],textAlign:`center`})}),!r&&v&&x&&l(K)?(0,o.jsx)(a.View,{...t.part(`flashcard`,`hint`),children:Y(K,{...n.textStyle(A,`xs`,H),color:A.colors[`text-muted-color`],textAlign:`center`})}):null]})})},Z=(0,o.jsxs)(o.Fragment,{children:[X(`front`),X(`back`)]}),Q={height:B,opacity:S?.6:1},$=t.part(`flashcard`,`card`,{state:N?`active`:`inactive`,disabled:S});return(0,o.jsxs)(a.View,{...re,...t.part(`flashcard`,`root`,{state:N?`active`:`inactive`,disabled:S,variant:w,size:T}),style:[{gap:A.space[3]},D],children:[x?(0,o.jsx)(a.Pressable,{...$,accessibilityRole:`button`,accessibilityLabel:O,accessibilityState:{...k,selected:N,disabled:S},disabled:S,onPress:()=>{U&&P(!N)},style:Q,children:Z}):(0,o.jsx)(a.View,{...$,accessibilityLabel:O,style:Q,children:Z}),l(m)?(0,o.jsx)(a.View,{...t.part(`flashcard`,`footer`),style:{flexDirection:`row`,flexWrap:`wrap`,justifyContent:`center`,gap:A.space[2]},children:m}):null]})}exports.Flashcard=f,exports.flashcardMinHeight=d;
2
2
  //# sourceMappingURL=Flashcard.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Flashcard.cjs","names":["useTheme","useI18n","useControllable","useState","Animated","useRef","Easing","shadowStyle","Text","part","StyleSheet","View","textStyle","weight","_Fragment","Pressable","require_MinervaProvider","require_parts","require_styles","require_useControllable","react","react_native","react_jsx_runtime","PAD","FONT","FLIP_MS","FADE_MS","shown","node","isText","flashcardMinHeight","value","match","amount","Flashcard","flippedProp","rest","t","translate","flipped","setFlipped","defaultFlipped","onFlippedChange","progress","first","target","animation","reducedMotion","heights","setHeights","pad","size","height","minHeight","measure","event","prev","side","font","fonts","interactive","flipOnClick","disabled","frontCaption","frontLabel","backCaption","backLabel","hintText","hint","surface","variant","rotate","axis","faceMotion","isBack","textOf","renderFace","hidden","caption","back","front","showHint","faces","cardStyle","cardHooks","style","accessibilityState","footer"],"sources":["../../../../../native/src/components/Flashcard/Flashcard.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n Animated,\n Easing,\n Pressable,\n StyleSheet,\n Text,\n View,\n type LayoutChangeEvent,\n type StyleProp,\n type ViewProps,\n type ViewStyle,\n} from \"react-native\";\nimport { useI18n, useTheme } from \"../../theme/MinervaProvider\";\nimport { part } from \"../../internal/parts\";\nimport { shadowStyle, textStyle, weight } from \"../../internal/styles\";\nimport { useControllable } from \"../../internal/useControllable\";\n\n/** Rotation axis of a Flashcard flip */\nexport type FlashcardAxis = \"y\" | \"x\";\n\n/** Surface style of a Flashcard (matches Card) */\nexport type FlashcardVariant = \"elevated\" | \"outline\" | \"filled\";\n\n/** Padding and typography scale of a Flashcard */\nexport type FlashcardSize = \"small\" | \"medium\" | \"large\";\n\nexport interface FlashcardProps extends Omit<ViewProps, \"style\" | \"children\"> {\n /** Content of the front face (the prompt) */\n front?: ReactNode;\n /** Content of the back face (the answer) */\n back?: ReactNode;\n /** Content below the card, not flipped (e.g. rating buttons) */\n footer?: ReactNode;\n /**\n * Small caption of the front face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.frontLabel\" message (\"Question\")\n */\n frontLabel?: ReactNode;\n /**\n * Small caption of the back face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.backLabel\" message (\"Answer\")\n */\n backLabel?: ReactNode;\n /**\n * Hint on the front face (shown with showHint while the card flips on press)\n * @default the \"flashcard.hint\" message\n */\n hint?: ReactNode;\n /**\n * Shows the hint on the front face\n * @default false\n */\n showHint?: boolean;\n /** Whether the back is shown (controlled; pair with onFlippedChange) */\n flipped?: boolean;\n /**\n * Whether the back is initially shown (uncontrolled)\n * @default false\n */\n defaultFlipped?: boolean;\n /** Called with the next flipped state */\n onFlippedChange?: (flipped: boolean) => void;\n /**\n * Pressing the card flips it. When false the card is not a button and\n * only `flipped` flips it.\n * @default true\n */\n flipOnClick?: boolean;\n /**\n * Blocks flipping and dims the card\n * @default false\n */\n disabled?: boolean;\n /**\n * Rotation axis: \"y\" flips horizontally, \"x\" vertically\n * @default \"y\"\n */\n axis?: FlashcardAxis;\n /**\n * Surface style\n * @default \"elevated\"\n */\n variant?: FlashcardVariant;\n /**\n * Padding and typography scale\n * @default \"medium\"\n */\n size?: FlashcardSize;\n /**\n * Minimum height of the card in dp (or a \"px\" / \"rem\" string, 1rem = 16);\n * the card grows to fit the taller face\n * @default 192\n */\n minHeight?: number | string;\n /** Style of the outer view (card and footer) */\n style?: StyleProp<ViewStyle>;\n}\n\nconst PAD = { small: \"3\", medium: \"5\", large: \"6\" } as const;\nconst FONT = { small: \"md\", medium: \"lg\", large: \"xl\" } as const;\nconst FLIP_MS = 450;\nconst FADE_MS = 200;\n\nconst shown = (node: ReactNode) =>\n node !== null && node !== undefined && node !== false && node !== \"\";\nconst isText = (node: ReactNode) =>\n typeof node === \"string\" || typeof node === \"number\";\n\n/** `minHeight` in dp: numbers as is, \"px\" / \"rem\" strings converted */\nexport const flashcardMinHeight = (value: number | string | undefined) => {\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(px|rem)?\\s*$/.exec(String(value ?? \"\"));\n if (!match) return 192;\n const amount = Number(match[1]);\n return match[2] === \"rem\" ? amount * 16 : amount;\n};\n\n/**\n * Flashcard: a two-sided study card that flips between a front (the prompt)\n * and a back (the answer) with an animated 3D rotation (`rotateY`, or\n * `rotateX` with axis=\"x\"; a crossfade with reduced motion). Both faces are\n * stacked and the card is as tall as the taller face. The card is a button\n * (`selected` while the back shows); the hidden face is hidden from\n * accessibility and the back is a polite live region. The footer (e.g.\n * rating buttons) sits below the card and does not flip.\n */\nexport function Flashcard({\n front,\n back,\n footer,\n frontLabel,\n backLabel,\n hint,\n showHint = false,\n flipped: flippedProp,\n defaultFlipped = false,\n onFlippedChange,\n flipOnClick = true,\n disabled = false,\n axis = \"y\",\n variant = \"elevated\",\n size = \"medium\",\n minHeight = 192,\n style,\n accessibilityLabel,\n accessibilityState,\n ...rest\n}: FlashcardProps) {\n const { tokens: t, fonts, reducedMotion } = useTheme();\n const { t: translate } = useI18n();\n const [flipped, setFlipped] = useControllable(\n flippedProp,\n defaultFlipped,\n onFlippedChange,\n );\n const [progress] = useState(() => new Animated.Value(flipped ? 1 : 0));\n const first = useRef(true);\n useEffect(() => {\n const target = flipped ? 1 : 0;\n if (first.current) {\n first.current = false;\n progress.setValue(target);\n return;\n }\n const animation = Animated.timing(progress, {\n toValue: target,\n duration: reducedMotion ? FADE_MS : FLIP_MS,\n easing: Easing.inOut(Easing.cubic),\n useNativeDriver: false,\n });\n animation.start();\n return () => animation.stop();\n }, [flipped, reducedMotion, progress]);\n\n // Both faces are absolutely stacked; the card takes the taller one.\n const [heights, setHeights] = useState({ front: 0, back: 0 });\n const pad = t.space[PAD[size]];\n const height = Math.max(\n flashcardMinHeight(minHeight),\n heights.front + pad * 2,\n heights.back + pad * 2,\n );\n const measure = (side: \"front\" | \"back\") => (event: LayoutChangeEvent) => {\n const value = Math.ceil(event.nativeEvent.layout.height);\n setHeights((prev) =>\n prev[side] === value ? prev : { ...prev, [side]: value },\n );\n };\n\n const font = fonts.sans;\n const interactive = flipOnClick && !disabled;\n const frontCaption =\n frontLabel === undefined ? translate(\"flashcard.frontLabel\") : frontLabel;\n const backCaption =\n backLabel === undefined ? translate(\"flashcard.backLabel\") : backLabel;\n const hintText = hint === undefined ? translate(\"flashcard.hint\") : hint;\n\n const surface: ViewStyle = {\n borderRadius: t.radius.xl,\n padding: pad,\n justifyContent: \"center\",\n backfaceVisibility: \"hidden\",\n ...(variant === \"outline\"\n ? {\n backgroundColor: t.colors[\"surface-color\"],\n borderWidth: 1,\n borderColor: t.colors[\"border-strong-color\"],\n }\n : variant === \"filled\"\n ? { backgroundColor: t.colors[\"surface-muted-color\"] }\n : {\n backgroundColor: t.colors[\"surface-elevated-color\"],\n ...shadowStyle(t.shadows.md),\n }),\n };\n\n const rotate = axis === \"x\" ? \"rotateX\" : \"rotateY\";\n const faceMotion = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n if (reducedMotion) {\n return {\n opacity: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [0, 1] : [1, 0],\n }),\n };\n }\n // The opacity switch at half way backs up backfaceVisibility (not\n // honored everywhere on Android).\n return {\n opacity: progress.interpolate({\n inputRange: [0, 0.5, 0.5001, 1],\n outputRange: isBack ? [0, 0, 1, 1] : [1, 1, 0, 0],\n }),\n transform: [\n { perspective: 1200 },\n {\n [rotate]: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [\"180deg\", \"360deg\"] : [\"0deg\", \"180deg\"],\n }),\n },\n ] as never,\n };\n };\n\n const textOf = (node: ReactNode, style: object) =>\n isText(node) ? <Text style={style}>{node}</Text> : node;\n\n const renderFace = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n const hidden = isBack ? !flipped : flipped;\n const caption = isBack ? backCaption : frontCaption;\n return (\n <Animated.View\n {...part(\"flashcard\", side)}\n pointerEvents={hidden ? \"none\" : \"auto\"}\n accessibilityElementsHidden={hidden}\n importantForAccessibility={hidden ? \"no-hide-descendants\" : \"auto\"}\n accessibilityLiveRegion={isBack ? \"polite\" : undefined}\n style={[StyleSheet.absoluteFill, surface, faceMotion(side)]}\n >\n <View onLayout={measure(side)} style={{ gap: t.space[\"2\"] }}>\n {shown(caption) ? (\n <View {...part(\"flashcard\", \"label\")}>\n {textOf(caption, {\n ...textStyle(t, \"xs\", font),\n fontWeight: weight(t, \"semibold\"),\n color: t.colors[\"text-muted-color\"],\n letterSpacing: 0.5,\n textTransform: \"uppercase\",\n })}\n </View>\n ) : null}\n <View style={{ alignItems: \"center\" }}>\n {textOf(isBack ? back : front, {\n ...textStyle(t, FONT[size], font),\n color: t.colors[\"text-color\"],\n textAlign: \"center\",\n })}\n </View>\n {!isBack && showHint && flipOnClick && shown(hintText) ? (\n <View {...part(\"flashcard\", \"hint\")}>\n {textOf(hintText, {\n ...textStyle(t, \"xs\", font),\n color: t.colors[\"text-muted-color\"],\n textAlign: \"center\",\n })}\n </View>\n ) : null}\n </View>\n </Animated.View>\n );\n };\n\n const faces = (\n <>\n {renderFace(\"front\")}\n {renderFace(\"back\")}\n </>\n );\n const cardStyle: ViewStyle = { height, opacity: disabled ? 0.6 : 1 };\n const cardHooks = part(\"flashcard\", \"card\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n });\n\n return (\n <View\n {...rest}\n {...part(\"flashcard\", \"root\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n variant,\n size,\n })}\n style={[{ gap: t.space[\"3\"] }, style]}\n >\n {flipOnClick ? (\n <Pressable\n {...cardHooks}\n accessibilityRole=\"button\"\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{\n ...accessibilityState,\n selected: flipped,\n disabled,\n }}\n disabled={disabled}\n onPress={() => {\n if (interactive) setFlipped(!flipped);\n }}\n style={cardStyle}\n >\n {faces}\n </Pressable>\n ) : (\n <View\n {...cardHooks}\n accessibilityLabel={accessibilityLabel}\n style={cardStyle}\n >\n {faces}\n </View>\n )}\n {shown(footer) ? (\n <View\n {...part(\"flashcard\", \"footer\")}\n style={{\n flexDirection: \"row\",\n flexWrap: \"wrap\",\n justifyContent: \"center\",\n gap: t.space[\"2\"],\n }}\n >\n {footer}\n </View>\n ) : null}\n </View>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Flashcard.cjs","names":["useTheme","useI18n","useControllable","useState","Animated","useRef","Easing","shadowStyle","Text","part","StyleSheet","View","textStyle","weight","_Fragment","Pressable","require_MinervaProvider","require_parts","require_styles","require_useControllable","react","react_native","react_jsx_runtime","PAD","FONT","FLIP_MS","FADE_MS","shown","node","isText","flashcardMinHeight","value","match","amount","Flashcard","flippedProp","rest","t","translate","flipped","setFlipped","defaultFlipped","onFlippedChange","progress","first","target","animation","reducedMotion","heights","setHeights","pad","size","height","minHeight","measure","event","prev","side","font","fonts","interactive","flipOnClick","disabled","frontCaption","frontLabel","backCaption","backLabel","hintText","hint","surface","variant","rotate","axis","faceMotion","isBack","textOf","renderFace","hidden","caption","back","front","showHint","faces","cardStyle","cardHooks","style","accessibilityState","footer"],"sources":["../../../../../native/src/components/Flashcard/Flashcard.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n Animated,\n Easing,\n Pressable,\n StyleSheet,\n Text,\n View,\n type LayoutChangeEvent,\n type StyleProp,\n type ViewProps,\n type ViewStyle,\n} from \"react-native\";\nimport { useI18n, useTheme } from \"../../theme/MinervaProvider\";\nimport { part } from \"../../internal/parts\";\nimport { shadowStyle, textStyle, weight } from \"../../internal/styles\";\nimport { useControllable } from \"../../internal/useControllable\";\n\n/** Rotation axis of a Flashcard flip */\nexport type FlashcardAxis = \"y\" | \"x\";\n\n/** Surface style of a Flashcard (matches Card) */\nexport type FlashcardVariant = \"elevated\" | \"outline\" | \"filled\";\n\n/** Padding and typography scale of a Flashcard */\nexport type FlashcardSize = \"small\" | \"medium\" | \"large\";\n\nexport interface FlashcardProps extends Omit<ViewProps, \"style\" | \"children\"> {\n /** Content of the front face (the prompt) */\n front?: ReactNode;\n /** Content of the back face (the answer) */\n back?: ReactNode;\n /** Content below the card, not flipped (e.g. rating buttons) */\n footer?: ReactNode;\n /**\n * Small caption of the front face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.frontLabel\" message (\"Question\")\n */\n frontLabel?: ReactNode;\n /**\n * Small caption of the back face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.backLabel\" message (\"Answer\")\n */\n backLabel?: ReactNode;\n /**\n * Hint on the front face (shown with showHint while the card flips on press)\n * @default the \"flashcard.hint\" message\n */\n hint?: ReactNode;\n /**\n * Shows the hint on the front face\n * @default false\n */\n showHint?: boolean;\n /** Whether the back is shown (controlled; pair with onFlippedChange) */\n flipped?: boolean;\n /**\n * Whether the back is initially shown (uncontrolled)\n * @default false\n */\n defaultFlipped?: boolean;\n /** Called with the next flipped state */\n onFlippedChange?: (flipped: boolean) => void;\n /**\n * Pressing the card flips it. When false the card is not a button and\n * only `flipped` flips it.\n * @default true\n */\n flipOnClick?: boolean;\n /**\n * Blocks flipping and dims the card\n * @default false\n */\n disabled?: boolean;\n /**\n * Rotation axis: \"y\" flips horizontally, \"x\" vertically\n * @default \"y\"\n */\n axis?: FlashcardAxis;\n /**\n * Surface style\n * @default \"elevated\"\n */\n variant?: FlashcardVariant;\n /**\n * Padding and typography scale\n * @default \"medium\"\n */\n size?: FlashcardSize;\n /**\n * Minimum height of the card in dp (or a \"px\" / \"rem\" string, 1rem = 16);\n * the card grows to fit the taller face\n * @default 192\n */\n minHeight?: number | string;\n /** Style of the outer view (card and footer) */\n style?: StyleProp<ViewStyle>;\n}\n\nconst PAD = { small: \"3\", medium: \"5\", large: \"6\" } as const;\nconst FONT = { small: \"md\", medium: \"lg\", large: \"xl\" } as const;\nconst FLIP_MS = 450;\nconst FADE_MS = 200;\n\nconst shown = (node: ReactNode) =>\n node !== null && node !== undefined && node !== false && node !== \"\";\nconst isText = (node: ReactNode) =>\n typeof node === \"string\" || typeof node === \"number\";\n\n/** `minHeight` in dp: numbers as is, \"px\" / \"rem\" strings converted */\nexport const flashcardMinHeight = (value: number | string | undefined) => {\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(px|rem)?\\s*$/.exec(String(value ?? \"\"));\n if (!match) return 192;\n const amount = Number(match[1]);\n return match[2] === \"rem\" ? amount * 16 : amount;\n};\n\n/**\n * Flashcard: a two-sided study card that flips between a front (the prompt)\n * and a back (the answer) with an animated 3D rotation (`rotateY`, or\n * `rotateX` with axis=\"x\"; a crossfade with reduced motion). Both faces are\n * stacked and the card is as tall as the taller face. The card is a button\n * (`selected` while the back shows); the hidden face is hidden from\n * accessibility and the back is a polite live region. The footer (e.g.\n * rating buttons) sits below the card and does not flip.\n */\nexport function Flashcard({\n front,\n back,\n footer,\n frontLabel,\n backLabel,\n hint,\n showHint = false,\n flipped: flippedProp,\n defaultFlipped = false,\n onFlippedChange,\n flipOnClick = true,\n disabled = false,\n axis = \"y\",\n variant = \"elevated\",\n size = \"medium\",\n minHeight = 192,\n style,\n accessibilityLabel,\n accessibilityState,\n ...rest\n}: FlashcardProps) {\n const { tokens: t, fonts, reducedMotion } = useTheme();\n const { t: translate } = useI18n();\n const [flipped, setFlipped] = useControllable(\n flippedProp,\n defaultFlipped,\n onFlippedChange,\n );\n const [progress] = useState(() => new Animated.Value(flipped ? 1 : 0));\n const first = useRef(true);\n useEffect(() => {\n const target = flipped ? 1 : 0;\n if (first.current) {\n first.current = false;\n progress.setValue(target);\n return;\n }\n const animation = Animated.timing(progress, {\n toValue: target,\n duration: reducedMotion ? FADE_MS : FLIP_MS,\n easing: Easing.inOut(Easing.cubic),\n useNativeDriver: false,\n });\n animation.start();\n return () => animation.stop();\n }, [flipped, reducedMotion, progress]);\n\n // Both faces are absolutely stacked; the card takes the taller one.\n const [heights, setHeights] = useState({ front: 0, back: 0 });\n const pad = t.space[PAD[size]];\n const height = Math.max(\n flashcardMinHeight(minHeight),\n heights.front + pad * 2,\n heights.back + pad * 2,\n );\n const measure = (side: \"front\" | \"back\") => (event: LayoutChangeEvent) => {\n const value = Math.ceil(event.nativeEvent.layout.height);\n setHeights((prev) =>\n prev[side] === value ? prev : { ...prev, [side]: value },\n );\n };\n\n const font = fonts.sans;\n const interactive = flipOnClick && !disabled;\n const frontCaption =\n frontLabel === undefined ? translate(\"flashcard.frontLabel\") : frontLabel;\n const backCaption =\n backLabel === undefined ? translate(\"flashcard.backLabel\") : backLabel;\n const hintText = hint === undefined ? translate(\"flashcard.hint\") : hint;\n\n const surface: ViewStyle = {\n borderRadius: t.radius.xl,\n padding: pad,\n justifyContent: \"center\",\n backfaceVisibility: \"hidden\",\n ...(variant === \"outline\"\n ? {\n backgroundColor: t.colors[\"surface-color\"],\n borderWidth: 1,\n borderColor: t.colors[\"border-strong-color\"],\n }\n : variant === \"filled\"\n ? { backgroundColor: t.colors[\"surface-muted-color\"] }\n : {\n backgroundColor: t.colors[\"surface-elevated-color\"],\n ...shadowStyle(t.shadows.md),\n }),\n };\n\n const rotate = axis === \"x\" ? \"rotateX\" : \"rotateY\";\n const faceMotion = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n if (reducedMotion) {\n return {\n opacity: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [0, 1] : [1, 0],\n }),\n };\n }\n // The opacity switch at half way backs up backfaceVisibility (not\n // honored everywhere on Android).\n return {\n opacity: progress.interpolate({\n inputRange: [0, 0.5, 0.5001, 1],\n outputRange: isBack ? [0, 0, 1, 1] : [1, 1, 0, 0],\n }),\n transform: [\n { perspective: 1200 },\n {\n [rotate]: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [\"180deg\", \"360deg\"] : [\"0deg\", \"180deg\"],\n }),\n },\n ] as never,\n };\n };\n\n const textOf = (node: ReactNode, style: object) =>\n isText(node) ? <Text style={style}>{node}</Text> : node;\n\n const renderFace = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n const hidden = isBack ? !flipped : flipped;\n const caption = isBack ? backCaption : frontCaption;\n return (\n <Animated.View\n {...part(\"flashcard\", side)}\n pointerEvents={hidden ? \"none\" : \"auto\"}\n accessibilityElementsHidden={hidden}\n importantForAccessibility={hidden ? \"no-hide-descendants\" : \"auto\"}\n accessibilityLiveRegion={isBack ? \"polite\" : undefined}\n style={[StyleSheet.absoluteFill, surface, faceMotion(side)]}\n >\n <View onLayout={measure(side)} style={{ gap: t.space[\"2\"] }}>\n {shown(caption) ? (\n <View {...part(\"flashcard\", \"label\")}>\n {textOf(caption, {\n ...textStyle(t, \"xs\", font),\n fontWeight: weight(t, \"semibold\"),\n color: t.colors[\"text-muted-color\"],\n letterSpacing: 0.5,\n textTransform: \"uppercase\",\n })}\n </View>\n ) : null}\n {/* Plain text is centred; rich content stretches so it lines up with the label. */}\n <View\n style={{\n alignItems: isText(isBack ? back : front) ? \"center\" : \"stretch\",\n }}\n >\n {textOf(isBack ? back : front, {\n ...textStyle(t, FONT[size], font),\n color: t.colors[\"text-color\"],\n textAlign: \"center\",\n })}\n </View>\n {!isBack && showHint && flipOnClick && shown(hintText) ? (\n <View {...part(\"flashcard\", \"hint\")}>\n {textOf(hintText, {\n ...textStyle(t, \"xs\", font),\n color: t.colors[\"text-muted-color\"],\n textAlign: \"center\",\n })}\n </View>\n ) : null}\n </View>\n </Animated.View>\n );\n };\n\n const faces = (\n <>\n {renderFace(\"front\")}\n {renderFace(\"back\")}\n </>\n );\n const cardStyle: ViewStyle = { height, opacity: disabled ? 0.6 : 1 };\n const cardHooks = part(\"flashcard\", \"card\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n });\n\n return (\n <View\n {...rest}\n {...part(\"flashcard\", \"root\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n variant,\n size,\n })}\n style={[{ gap: t.space[\"3\"] }, style]}\n >\n {flipOnClick ? (\n <Pressable\n {...cardHooks}\n accessibilityRole=\"button\"\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{\n ...accessibilityState,\n selected: flipped,\n disabled,\n }}\n disabled={disabled}\n onPress={() => {\n if (interactive) setFlipped(!flipped);\n }}\n style={cardStyle}\n >\n {faces}\n </Pressable>\n ) : (\n <View\n {...cardHooks}\n accessibilityLabel={accessibilityLabel}\n style={cardStyle}\n >\n {faces}\n </View>\n )}\n {shown(footer) ? (\n <View\n {...part(\"flashcard\", \"footer\")}\n style={{\n flexDirection: \"row\",\n flexWrap: \"wrap\",\n justifyContent: \"center\",\n gap: t.space[\"2\"],\n }}\n >\n {footer}\n </View>\n ) : null}\n </View>\n );\n}\n"],"mappings":"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"}
@@ -3,26 +3,26 @@ import { part as n } from "../../internal/parts.js";
3
3
  import { shadowStyle as r, textStyle as i, weight as ee } from "../../internal/styles.js";
4
4
  import { useControllable as a } from "../../internal/useControllable.js";
5
5
  import { useEffect as te, useRef as o, useState as s } from "react";
6
- import { Animated as c, Easing as l, Pressable as u, StyleSheet as ne, Text as d, View as f } from "react-native";
7
- import { Fragment as re, jsx as p, jsxs as m } from "react/jsx-runtime";
6
+ import { Animated as c, Easing as l, Pressable as u, StyleSheet as ne, Text as re, View as d } from "react-native";
7
+ import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
8
8
  //#region src/components/Flashcard/Flashcard.tsx
9
- var h = {
9
+ var ie = {
10
10
  small: "3",
11
11
  medium: "5",
12
12
  large: "6"
13
- }, g = {
13
+ }, h = {
14
14
  small: "md",
15
15
  medium: "lg",
16
16
  large: "xl"
17
- }, _ = 450, ie = 200, v = (e) => e != null && e !== !1 && e !== "", ae = (e) => typeof e == "string" || typeof e == "number", y = (e) => {
17
+ }, g = 450, _ = 200, v = (e) => e != null && e !== !1 && e !== "", y = (e) => typeof e == "string" || typeof e == "number", b = (e) => {
18
18
  if (typeof e == "number" && Number.isFinite(e)) return e;
19
19
  let t = /^\s*(\d+(?:\.\d+)?)\s*(px|rem)?\s*$/.exec(String(e ?? ""));
20
20
  if (!t) return 192;
21
21
  let n = Number(t[1]);
22
22
  return t[2] === "rem" ? n * 16 : n;
23
23
  };
24
- function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T, showHint: E = !1, flipped: D, defaultFlipped: oe = !1, onFlippedChange: se, flipOnClick: O = !0, disabled: k = !1, axis: ce = "y", variant: A = "elevated", size: j = "medium", minHeight: M = 192, style: N, accessibilityLabel: P, accessibilityState: F, ...I }) {
25
- let { tokens: L, fonts: R, reducedMotion: z } = t(), { t: B } = e(), [V, H] = a(D, oe, se), [U] = s(() => new c.Value(+!!V)), W = o(!0);
24
+ function x({ front: x, back: S, footer: C, frontLabel: w, backLabel: T, hint: E, showHint: D = !1, flipped: O, defaultFlipped: k = !1, onFlippedChange: ae, flipOnClick: A = !0, disabled: j = !1, axis: oe = "y", variant: M = "elevated", size: N = "medium", minHeight: se = 192, style: ce, accessibilityLabel: P, accessibilityState: F, ...I }) {
25
+ let { tokens: L, fonts: R, reducedMotion: z } = t(), { t: B } = e(), [V, H] = a(O, k, ae), [U] = s(() => new c.Value(+!!V)), W = o(!0);
26
26
  te(() => {
27
27
  let e = +!!V;
28
28
  if (W.current) {
@@ -31,7 +31,7 @@ function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T,
31
31
  }
32
32
  let t = c.timing(U, {
33
33
  toValue: e,
34
- duration: z ? ie : _,
34
+ duration: z ? _ : g,
35
35
  easing: l.inOut(l.cubic),
36
36
  useNativeDriver: !1
37
37
  });
@@ -44,26 +44,26 @@ function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T,
44
44
  let [G, le] = s({
45
45
  front: 0,
46
46
  back: 0
47
- }), K = L.space[h[j]], ue = Math.max(y(M), G.front + K * 2, G.back + K * 2), de = (e) => (t) => {
47
+ }), K = L.space[ie[N]], ue = Math.max(b(se), G.front + K * 2, G.back + K * 2), de = (e) => (t) => {
48
48
  let n = Math.ceil(t.nativeEvent.layout.height);
49
49
  le((t) => t[e] === n ? t : {
50
50
  ...t,
51
51
  [e]: n
52
52
  });
53
- }, q = R.sans, fe = O && !k, pe = C === void 0 ? B("flashcard.frontLabel") : C, me = w === void 0 ? B("flashcard.backLabel") : w, J = T === void 0 ? B("flashcard.hint") : T, he = {
53
+ }, q = R.sans, fe = A && !j, pe = w === void 0 ? B("flashcard.frontLabel") : w, me = T === void 0 ? B("flashcard.backLabel") : T, J = E === void 0 ? B("flashcard.hint") : E, he = {
54
54
  borderRadius: L.radius.xl,
55
55
  padding: K,
56
56
  justifyContent: "center",
57
57
  backfaceVisibility: "hidden",
58
- ...A === "outline" ? {
58
+ ...M === "outline" ? {
59
59
  backgroundColor: L.colors["surface-color"],
60
60
  borderWidth: 1,
61
61
  borderColor: L.colors["border-strong-color"]
62
- } : A === "filled" ? { backgroundColor: L.colors["surface-muted-color"] } : {
62
+ } : M === "filled" ? { backgroundColor: L.colors["surface-muted-color"] } : {
63
63
  backgroundColor: L.colors["surface-elevated-color"],
64
64
  ...r(L.shadows.md)
65
65
  }
66
- }, ge = ce === "x" ? "rotateX" : "rotateY", _e = (e) => {
66
+ }, ge = oe === "x" ? "rotateX" : "rotateY", _e = (e) => {
67
67
  let t = e === "back";
68
68
  return z ? { opacity: U.interpolate({
69
69
  inputRange: [0, 1],
@@ -93,7 +93,7 @@ function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T,
93
93
  outputRange: t ? ["180deg", "360deg"] : ["0deg", "180deg"]
94
94
  }) }]
95
95
  };
96
- }, Y = (e, t) => ae(e) ? /* @__PURE__ */ p(d, {
96
+ }, Y = (e, t) => y(e) ? /* @__PURE__ */ p(re, {
97
97
  style: t,
98
98
  children: e
99
99
  }) : e, X = (e) => {
@@ -109,11 +109,11 @@ function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T,
109
109
  he,
110
110
  _e(e)
111
111
  ],
112
- children: /* @__PURE__ */ m(f, {
112
+ children: /* @__PURE__ */ m(d, {
113
113
  onLayout: de(e),
114
114
  style: { gap: L.space[2] },
115
115
  children: [
116
- v(a) ? /* @__PURE__ */ p(f, {
116
+ v(a) ? /* @__PURE__ */ p(d, {
117
117
  ...n("flashcard", "label"),
118
118
  children: Y(a, {
119
119
  ...i(L, "xs", q),
@@ -123,15 +123,15 @@ function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T,
123
123
  textTransform: "uppercase"
124
124
  })
125
125
  }) : null,
126
- /* @__PURE__ */ p(f, {
127
- style: { alignItems: "center" },
128
- children: Y(t ? x : b, {
129
- ...i(L, g[j], q),
126
+ /* @__PURE__ */ p(d, {
127
+ style: { alignItems: y(t ? S : x) ? "center" : "stretch" },
128
+ children: Y(t ? S : x, {
129
+ ...i(L, h[N], q),
130
130
  color: L.colors["text-color"],
131
131
  textAlign: "center"
132
132
  })
133
133
  }),
134
- !t && E && O && v(J) ? /* @__PURE__ */ p(f, {
134
+ !t && D && A && v(J) ? /* @__PURE__ */ p(d, {
135
135
  ...n("flashcard", "hint"),
136
136
  children: Y(J, {
137
137
  ...i(L, "xs", q),
@@ -142,43 +142,43 @@ function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T,
142
142
  ]
143
143
  })
144
144
  });
145
- }, Z = /* @__PURE__ */ m(re, { children: [X("front"), X("back")] }), Q = {
145
+ }, Z = /* @__PURE__ */ m(f, { children: [X("front"), X("back")] }), Q = {
146
146
  height: ue,
147
- opacity: k ? .6 : 1
147
+ opacity: j ? .6 : 1
148
148
  }, $ = n("flashcard", "card", {
149
149
  state: V ? "active" : "inactive",
150
- disabled: k
150
+ disabled: j
151
151
  });
152
- return /* @__PURE__ */ m(f, {
152
+ return /* @__PURE__ */ m(d, {
153
153
  ...I,
154
154
  ...n("flashcard", "root", {
155
155
  state: V ? "active" : "inactive",
156
- disabled: k,
157
- variant: A,
158
- size: j
156
+ disabled: j,
157
+ variant: M,
158
+ size: N
159
159
  }),
160
- style: [{ gap: L.space[3] }, N],
161
- children: [O ? /* @__PURE__ */ p(u, {
160
+ style: [{ gap: L.space[3] }, ce],
161
+ children: [A ? /* @__PURE__ */ p(u, {
162
162
  ...$,
163
163
  accessibilityRole: "button",
164
164
  accessibilityLabel: P,
165
165
  accessibilityState: {
166
166
  ...F,
167
167
  selected: V,
168
- disabled: k
168
+ disabled: j
169
169
  },
170
- disabled: k,
170
+ disabled: j,
171
171
  onPress: () => {
172
172
  fe && H(!V);
173
173
  },
174
174
  style: Q,
175
175
  children: Z
176
- }) : /* @__PURE__ */ p(f, {
176
+ }) : /* @__PURE__ */ p(d, {
177
177
  ...$,
178
178
  accessibilityLabel: P,
179
179
  style: Q,
180
180
  children: Z
181
- }), v(S) ? /* @__PURE__ */ p(f, {
181
+ }), v(C) ? /* @__PURE__ */ p(d, {
182
182
  ...n("flashcard", "footer"),
183
183
  style: {
184
184
  flexDirection: "row",
@@ -186,11 +186,11 @@ function b({ front: b, back: x, footer: S, frontLabel: C, backLabel: w, hint: T,
186
186
  justifyContent: "center",
187
187
  gap: L.space[2]
188
188
  },
189
- children: S
189
+ children: C
190
190
  }) : null]
191
191
  });
192
192
  }
193
193
  //#endregion
194
- export { b as Flashcard, y as flashcardMinHeight };
194
+ export { x as Flashcard, b as flashcardMinHeight };
195
195
 
196
196
  //# sourceMappingURL=Flashcard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Flashcard.js","names":["_Fragment","Fragment$1","PAD","FONT","FLIP_MS","FADE_MS","shown","node","isText","flashcardMinHeight","value","match","amount","Flashcard","flippedProp","rest","t","useTheme","translate","useI18n","flipped","setFlipped","useControllable","defaultFlipped","onFlippedChange","progress","useState","Animated","first","useRef","target","animation","reducedMotion","Easing","heights","setHeights","pad","size","height","minHeight","measure","event","prev","side","font","fonts","interactive","flipOnClick","disabled","frontCaption","frontLabel","backCaption","backLabel","hintText","hint","surface","variant","shadowStyle","rotate","axis","faceMotion","isBack","textOf","Text","renderFace","hidden","caption","part","StyleSheet","View","textStyle","weight","back","front","showHint","faces","cardStyle","cardHooks","style","Pressable","accessibilityState","footer"],"sources":["../../../../../native/src/components/Flashcard/Flashcard.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n Animated,\n Easing,\n Pressable,\n StyleSheet,\n Text,\n View,\n type LayoutChangeEvent,\n type StyleProp,\n type ViewProps,\n type ViewStyle,\n} from \"react-native\";\nimport { useI18n, useTheme } from \"../../theme/MinervaProvider\";\nimport { part } from \"../../internal/parts\";\nimport { shadowStyle, textStyle, weight } from \"../../internal/styles\";\nimport { useControllable } from \"../../internal/useControllable\";\n\n/** Rotation axis of a Flashcard flip */\nexport type FlashcardAxis = \"y\" | \"x\";\n\n/** Surface style of a Flashcard (matches Card) */\nexport type FlashcardVariant = \"elevated\" | \"outline\" | \"filled\";\n\n/** Padding and typography scale of a Flashcard */\nexport type FlashcardSize = \"small\" | \"medium\" | \"large\";\n\nexport interface FlashcardProps extends Omit<ViewProps, \"style\" | \"children\"> {\n /** Content of the front face (the prompt) */\n front?: ReactNode;\n /** Content of the back face (the answer) */\n back?: ReactNode;\n /** Content below the card, not flipped (e.g. rating buttons) */\n footer?: ReactNode;\n /**\n * Small caption of the front face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.frontLabel\" message (\"Question\")\n */\n frontLabel?: ReactNode;\n /**\n * Small caption of the back face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.backLabel\" message (\"Answer\")\n */\n backLabel?: ReactNode;\n /**\n * Hint on the front face (shown with showHint while the card flips on press)\n * @default the \"flashcard.hint\" message\n */\n hint?: ReactNode;\n /**\n * Shows the hint on the front face\n * @default false\n */\n showHint?: boolean;\n /** Whether the back is shown (controlled; pair with onFlippedChange) */\n flipped?: boolean;\n /**\n * Whether the back is initially shown (uncontrolled)\n * @default false\n */\n defaultFlipped?: boolean;\n /** Called with the next flipped state */\n onFlippedChange?: (flipped: boolean) => void;\n /**\n * Pressing the card flips it. When false the card is not a button and\n * only `flipped` flips it.\n * @default true\n */\n flipOnClick?: boolean;\n /**\n * Blocks flipping and dims the card\n * @default false\n */\n disabled?: boolean;\n /**\n * Rotation axis: \"y\" flips horizontally, \"x\" vertically\n * @default \"y\"\n */\n axis?: FlashcardAxis;\n /**\n * Surface style\n * @default \"elevated\"\n */\n variant?: FlashcardVariant;\n /**\n * Padding and typography scale\n * @default \"medium\"\n */\n size?: FlashcardSize;\n /**\n * Minimum height of the card in dp (or a \"px\" / \"rem\" string, 1rem = 16);\n * the card grows to fit the taller face\n * @default 192\n */\n minHeight?: number | string;\n /** Style of the outer view (card and footer) */\n style?: StyleProp<ViewStyle>;\n}\n\nconst PAD = { small: \"3\", medium: \"5\", large: \"6\" } as const;\nconst FONT = { small: \"md\", medium: \"lg\", large: \"xl\" } as const;\nconst FLIP_MS = 450;\nconst FADE_MS = 200;\n\nconst shown = (node: ReactNode) =>\n node !== null && node !== undefined && node !== false && node !== \"\";\nconst isText = (node: ReactNode) =>\n typeof node === \"string\" || typeof node === \"number\";\n\n/** `minHeight` in dp: numbers as is, \"px\" / \"rem\" strings converted */\nexport const flashcardMinHeight = (value: number | string | undefined) => {\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(px|rem)?\\s*$/.exec(String(value ?? \"\"));\n if (!match) return 192;\n const amount = Number(match[1]);\n return match[2] === \"rem\" ? amount * 16 : amount;\n};\n\n/**\n * Flashcard: a two-sided study card that flips between a front (the prompt)\n * and a back (the answer) with an animated 3D rotation (`rotateY`, or\n * `rotateX` with axis=\"x\"; a crossfade with reduced motion). Both faces are\n * stacked and the card is as tall as the taller face. The card is a button\n * (`selected` while the back shows); the hidden face is hidden from\n * accessibility and the back is a polite live region. The footer (e.g.\n * rating buttons) sits below the card and does not flip.\n */\nexport function Flashcard({\n front,\n back,\n footer,\n frontLabel,\n backLabel,\n hint,\n showHint = false,\n flipped: flippedProp,\n defaultFlipped = false,\n onFlippedChange,\n flipOnClick = true,\n disabled = false,\n axis = \"y\",\n variant = \"elevated\",\n size = \"medium\",\n minHeight = 192,\n style,\n accessibilityLabel,\n accessibilityState,\n ...rest\n}: FlashcardProps) {\n const { tokens: t, fonts, reducedMotion } = useTheme();\n const { t: translate } = useI18n();\n const [flipped, setFlipped] = useControllable(\n flippedProp,\n defaultFlipped,\n onFlippedChange,\n );\n const [progress] = useState(() => new Animated.Value(flipped ? 1 : 0));\n const first = useRef(true);\n useEffect(() => {\n const target = flipped ? 1 : 0;\n if (first.current) {\n first.current = false;\n progress.setValue(target);\n return;\n }\n const animation = Animated.timing(progress, {\n toValue: target,\n duration: reducedMotion ? FADE_MS : FLIP_MS,\n easing: Easing.inOut(Easing.cubic),\n useNativeDriver: false,\n });\n animation.start();\n return () => animation.stop();\n }, [flipped, reducedMotion, progress]);\n\n // Both faces are absolutely stacked; the card takes the taller one.\n const [heights, setHeights] = useState({ front: 0, back: 0 });\n const pad = t.space[PAD[size]];\n const height = Math.max(\n flashcardMinHeight(minHeight),\n heights.front + pad * 2,\n heights.back + pad * 2,\n );\n const measure = (side: \"front\" | \"back\") => (event: LayoutChangeEvent) => {\n const value = Math.ceil(event.nativeEvent.layout.height);\n setHeights((prev) =>\n prev[side] === value ? prev : { ...prev, [side]: value },\n );\n };\n\n const font = fonts.sans;\n const interactive = flipOnClick && !disabled;\n const frontCaption =\n frontLabel === undefined ? translate(\"flashcard.frontLabel\") : frontLabel;\n const backCaption =\n backLabel === undefined ? translate(\"flashcard.backLabel\") : backLabel;\n const hintText = hint === undefined ? translate(\"flashcard.hint\") : hint;\n\n const surface: ViewStyle = {\n borderRadius: t.radius.xl,\n padding: pad,\n justifyContent: \"center\",\n backfaceVisibility: \"hidden\",\n ...(variant === \"outline\"\n ? {\n backgroundColor: t.colors[\"surface-color\"],\n borderWidth: 1,\n borderColor: t.colors[\"border-strong-color\"],\n }\n : variant === \"filled\"\n ? { backgroundColor: t.colors[\"surface-muted-color\"] }\n : {\n backgroundColor: t.colors[\"surface-elevated-color\"],\n ...shadowStyle(t.shadows.md),\n }),\n };\n\n const rotate = axis === \"x\" ? \"rotateX\" : \"rotateY\";\n const faceMotion = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n if (reducedMotion) {\n return {\n opacity: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [0, 1] : [1, 0],\n }),\n };\n }\n // The opacity switch at half way backs up backfaceVisibility (not\n // honored everywhere on Android).\n return {\n opacity: progress.interpolate({\n inputRange: [0, 0.5, 0.5001, 1],\n outputRange: isBack ? [0, 0, 1, 1] : [1, 1, 0, 0],\n }),\n transform: [\n { perspective: 1200 },\n {\n [rotate]: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [\"180deg\", \"360deg\"] : [\"0deg\", \"180deg\"],\n }),\n },\n ] as never,\n };\n };\n\n const textOf = (node: ReactNode, style: object) =>\n isText(node) ? <Text style={style}>{node}</Text> : node;\n\n const renderFace = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n const hidden = isBack ? !flipped : flipped;\n const caption = isBack ? backCaption : frontCaption;\n return (\n <Animated.View\n {...part(\"flashcard\", side)}\n pointerEvents={hidden ? \"none\" : \"auto\"}\n accessibilityElementsHidden={hidden}\n importantForAccessibility={hidden ? \"no-hide-descendants\" : \"auto\"}\n accessibilityLiveRegion={isBack ? \"polite\" : undefined}\n style={[StyleSheet.absoluteFill, surface, faceMotion(side)]}\n >\n <View onLayout={measure(side)} style={{ gap: t.space[\"2\"] }}>\n {shown(caption) ? (\n <View {...part(\"flashcard\", \"label\")}>\n {textOf(caption, {\n ...textStyle(t, \"xs\", font),\n fontWeight: weight(t, \"semibold\"),\n color: t.colors[\"text-muted-color\"],\n letterSpacing: 0.5,\n textTransform: \"uppercase\",\n })}\n </View>\n ) : null}\n <View style={{ alignItems: \"center\" }}>\n {textOf(isBack ? back : front, {\n ...textStyle(t, FONT[size], font),\n color: t.colors[\"text-color\"],\n textAlign: \"center\",\n })}\n </View>\n {!isBack && showHint && flipOnClick && shown(hintText) ? (\n <View {...part(\"flashcard\", \"hint\")}>\n {textOf(hintText, {\n ...textStyle(t, \"xs\", font),\n color: t.colors[\"text-muted-color\"],\n textAlign: \"center\",\n })}\n </View>\n ) : null}\n </View>\n </Animated.View>\n );\n };\n\n const faces = (\n <>\n {renderFace(\"front\")}\n {renderFace(\"back\")}\n </>\n );\n const cardStyle: ViewStyle = { height, opacity: disabled ? 0.6 : 1 };\n const cardHooks = part(\"flashcard\", \"card\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n });\n\n return (\n <View\n {...rest}\n {...part(\"flashcard\", \"root\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n variant,\n size,\n })}\n style={[{ gap: t.space[\"3\"] }, style]}\n >\n {flipOnClick ? (\n <Pressable\n {...cardHooks}\n accessibilityRole=\"button\"\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{\n ...accessibilityState,\n selected: flipped,\n disabled,\n }}\n disabled={disabled}\n onPress={() => {\n if (interactive) setFlipped(!flipped);\n }}\n style={cardStyle}\n >\n {faces}\n </Pressable>\n ) : (\n <View\n {...cardHooks}\n accessibilityLabel={accessibilityLabel}\n style={cardStyle}\n >\n {faces}\n </View>\n )}\n {shown(footer) ? (\n <View\n {...part(\"flashcard\", \"footer\")}\n style={{\n flexDirection: \"row\",\n flexWrap: \"wrap\",\n justifyContent: \"center\",\n gap: t.space[\"2\"],\n }}\n >\n {footer}\n </View>\n ) : null}\n </View>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Flashcard.js","names":["_Fragment","Fragment$1","PAD","FONT","FLIP_MS","FADE_MS","shown","node","isText","flashcardMinHeight","value","match","amount","Flashcard","flippedProp","rest","t","useTheme","translate","useI18n","flipped","setFlipped","useControllable","defaultFlipped","onFlippedChange","progress","useState","Animated","first","useRef","target","animation","reducedMotion","Easing","heights","setHeights","pad","size","height","minHeight","measure","event","prev","side","font","fonts","interactive","flipOnClick","disabled","frontCaption","frontLabel","backCaption","backLabel","hintText","hint","surface","variant","shadowStyle","rotate","axis","faceMotion","isBack","textOf","Text","renderFace","hidden","caption","part","StyleSheet","View","textStyle","weight","back","front","showHint","faces","cardStyle","cardHooks","style","Pressable","accessibilityState","footer"],"sources":["../../../../../native/src/components/Flashcard/Flashcard.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n Animated,\n Easing,\n Pressable,\n StyleSheet,\n Text,\n View,\n type LayoutChangeEvent,\n type StyleProp,\n type ViewProps,\n type ViewStyle,\n} from \"react-native\";\nimport { useI18n, useTheme } from \"../../theme/MinervaProvider\";\nimport { part } from \"../../internal/parts\";\nimport { shadowStyle, textStyle, weight } from \"../../internal/styles\";\nimport { useControllable } from \"../../internal/useControllable\";\n\n/** Rotation axis of a Flashcard flip */\nexport type FlashcardAxis = \"y\" | \"x\";\n\n/** Surface style of a Flashcard (matches Card) */\nexport type FlashcardVariant = \"elevated\" | \"outline\" | \"filled\";\n\n/** Padding and typography scale of a Flashcard */\nexport type FlashcardSize = \"small\" | \"medium\" | \"large\";\n\nexport interface FlashcardProps extends Omit<ViewProps, \"style\" | \"children\"> {\n /** Content of the front face (the prompt) */\n front?: ReactNode;\n /** Content of the back face (the answer) */\n back?: ReactNode;\n /** Content below the card, not flipped (e.g. rating buttons) */\n footer?: ReactNode;\n /**\n * Small caption of the front face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.frontLabel\" message (\"Question\")\n */\n frontLabel?: ReactNode;\n /**\n * Small caption of the back face; `null`, `false` or `\"\"` hides it\n * @default the \"flashcard.backLabel\" message (\"Answer\")\n */\n backLabel?: ReactNode;\n /**\n * Hint on the front face (shown with showHint while the card flips on press)\n * @default the \"flashcard.hint\" message\n */\n hint?: ReactNode;\n /**\n * Shows the hint on the front face\n * @default false\n */\n showHint?: boolean;\n /** Whether the back is shown (controlled; pair with onFlippedChange) */\n flipped?: boolean;\n /**\n * Whether the back is initially shown (uncontrolled)\n * @default false\n */\n defaultFlipped?: boolean;\n /** Called with the next flipped state */\n onFlippedChange?: (flipped: boolean) => void;\n /**\n * Pressing the card flips it. When false the card is not a button and\n * only `flipped` flips it.\n * @default true\n */\n flipOnClick?: boolean;\n /**\n * Blocks flipping and dims the card\n * @default false\n */\n disabled?: boolean;\n /**\n * Rotation axis: \"y\" flips horizontally, \"x\" vertically\n * @default \"y\"\n */\n axis?: FlashcardAxis;\n /**\n * Surface style\n * @default \"elevated\"\n */\n variant?: FlashcardVariant;\n /**\n * Padding and typography scale\n * @default \"medium\"\n */\n size?: FlashcardSize;\n /**\n * Minimum height of the card in dp (or a \"px\" / \"rem\" string, 1rem = 16);\n * the card grows to fit the taller face\n * @default 192\n */\n minHeight?: number | string;\n /** Style of the outer view (card and footer) */\n style?: StyleProp<ViewStyle>;\n}\n\nconst PAD = { small: \"3\", medium: \"5\", large: \"6\" } as const;\nconst FONT = { small: \"md\", medium: \"lg\", large: \"xl\" } as const;\nconst FLIP_MS = 450;\nconst FADE_MS = 200;\n\nconst shown = (node: ReactNode) =>\n node !== null && node !== undefined && node !== false && node !== \"\";\nconst isText = (node: ReactNode) =>\n typeof node === \"string\" || typeof node === \"number\";\n\n/** `minHeight` in dp: numbers as is, \"px\" / \"rem\" strings converted */\nexport const flashcardMinHeight = (value: number | string | undefined) => {\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(px|rem)?\\s*$/.exec(String(value ?? \"\"));\n if (!match) return 192;\n const amount = Number(match[1]);\n return match[2] === \"rem\" ? amount * 16 : amount;\n};\n\n/**\n * Flashcard: a two-sided study card that flips between a front (the prompt)\n * and a back (the answer) with an animated 3D rotation (`rotateY`, or\n * `rotateX` with axis=\"x\"; a crossfade with reduced motion). Both faces are\n * stacked and the card is as tall as the taller face. The card is a button\n * (`selected` while the back shows); the hidden face is hidden from\n * accessibility and the back is a polite live region. The footer (e.g.\n * rating buttons) sits below the card and does not flip.\n */\nexport function Flashcard({\n front,\n back,\n footer,\n frontLabel,\n backLabel,\n hint,\n showHint = false,\n flipped: flippedProp,\n defaultFlipped = false,\n onFlippedChange,\n flipOnClick = true,\n disabled = false,\n axis = \"y\",\n variant = \"elevated\",\n size = \"medium\",\n minHeight = 192,\n style,\n accessibilityLabel,\n accessibilityState,\n ...rest\n}: FlashcardProps) {\n const { tokens: t, fonts, reducedMotion } = useTheme();\n const { t: translate } = useI18n();\n const [flipped, setFlipped] = useControllable(\n flippedProp,\n defaultFlipped,\n onFlippedChange,\n );\n const [progress] = useState(() => new Animated.Value(flipped ? 1 : 0));\n const first = useRef(true);\n useEffect(() => {\n const target = flipped ? 1 : 0;\n if (first.current) {\n first.current = false;\n progress.setValue(target);\n return;\n }\n const animation = Animated.timing(progress, {\n toValue: target,\n duration: reducedMotion ? FADE_MS : FLIP_MS,\n easing: Easing.inOut(Easing.cubic),\n useNativeDriver: false,\n });\n animation.start();\n return () => animation.stop();\n }, [flipped, reducedMotion, progress]);\n\n // Both faces are absolutely stacked; the card takes the taller one.\n const [heights, setHeights] = useState({ front: 0, back: 0 });\n const pad = t.space[PAD[size]];\n const height = Math.max(\n flashcardMinHeight(minHeight),\n heights.front + pad * 2,\n heights.back + pad * 2,\n );\n const measure = (side: \"front\" | \"back\") => (event: LayoutChangeEvent) => {\n const value = Math.ceil(event.nativeEvent.layout.height);\n setHeights((prev) =>\n prev[side] === value ? prev : { ...prev, [side]: value },\n );\n };\n\n const font = fonts.sans;\n const interactive = flipOnClick && !disabled;\n const frontCaption =\n frontLabel === undefined ? translate(\"flashcard.frontLabel\") : frontLabel;\n const backCaption =\n backLabel === undefined ? translate(\"flashcard.backLabel\") : backLabel;\n const hintText = hint === undefined ? translate(\"flashcard.hint\") : hint;\n\n const surface: ViewStyle = {\n borderRadius: t.radius.xl,\n padding: pad,\n justifyContent: \"center\",\n backfaceVisibility: \"hidden\",\n ...(variant === \"outline\"\n ? {\n backgroundColor: t.colors[\"surface-color\"],\n borderWidth: 1,\n borderColor: t.colors[\"border-strong-color\"],\n }\n : variant === \"filled\"\n ? { backgroundColor: t.colors[\"surface-muted-color\"] }\n : {\n backgroundColor: t.colors[\"surface-elevated-color\"],\n ...shadowStyle(t.shadows.md),\n }),\n };\n\n const rotate = axis === \"x\" ? \"rotateX\" : \"rotateY\";\n const faceMotion = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n if (reducedMotion) {\n return {\n opacity: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [0, 1] : [1, 0],\n }),\n };\n }\n // The opacity switch at half way backs up backfaceVisibility (not\n // honored everywhere on Android).\n return {\n opacity: progress.interpolate({\n inputRange: [0, 0.5, 0.5001, 1],\n outputRange: isBack ? [0, 0, 1, 1] : [1, 1, 0, 0],\n }),\n transform: [\n { perspective: 1200 },\n {\n [rotate]: progress.interpolate({\n inputRange: [0, 1],\n outputRange: isBack ? [\"180deg\", \"360deg\"] : [\"0deg\", \"180deg\"],\n }),\n },\n ] as never,\n };\n };\n\n const textOf = (node: ReactNode, style: object) =>\n isText(node) ? <Text style={style}>{node}</Text> : node;\n\n const renderFace = (side: \"front\" | \"back\") => {\n const isBack = side === \"back\";\n const hidden = isBack ? !flipped : flipped;\n const caption = isBack ? backCaption : frontCaption;\n return (\n <Animated.View\n {...part(\"flashcard\", side)}\n pointerEvents={hidden ? \"none\" : \"auto\"}\n accessibilityElementsHidden={hidden}\n importantForAccessibility={hidden ? \"no-hide-descendants\" : \"auto\"}\n accessibilityLiveRegion={isBack ? \"polite\" : undefined}\n style={[StyleSheet.absoluteFill, surface, faceMotion(side)]}\n >\n <View onLayout={measure(side)} style={{ gap: t.space[\"2\"] }}>\n {shown(caption) ? (\n <View {...part(\"flashcard\", \"label\")}>\n {textOf(caption, {\n ...textStyle(t, \"xs\", font),\n fontWeight: weight(t, \"semibold\"),\n color: t.colors[\"text-muted-color\"],\n letterSpacing: 0.5,\n textTransform: \"uppercase\",\n })}\n </View>\n ) : null}\n {/* Plain text is centred; rich content stretches so it lines up with the label. */}\n <View\n style={{\n alignItems: isText(isBack ? back : front) ? \"center\" : \"stretch\",\n }}\n >\n {textOf(isBack ? back : front, {\n ...textStyle(t, FONT[size], font),\n color: t.colors[\"text-color\"],\n textAlign: \"center\",\n })}\n </View>\n {!isBack && showHint && flipOnClick && shown(hintText) ? (\n <View {...part(\"flashcard\", \"hint\")}>\n {textOf(hintText, {\n ...textStyle(t, \"xs\", font),\n color: t.colors[\"text-muted-color\"],\n textAlign: \"center\",\n })}\n </View>\n ) : null}\n </View>\n </Animated.View>\n );\n };\n\n const faces = (\n <>\n {renderFace(\"front\")}\n {renderFace(\"back\")}\n </>\n );\n const cardStyle: ViewStyle = { height, opacity: disabled ? 0.6 : 1 };\n const cardHooks = part(\"flashcard\", \"card\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n });\n\n return (\n <View\n {...rest}\n {...part(\"flashcard\", \"root\", {\n state: flipped ? \"active\" : \"inactive\",\n disabled,\n variant,\n size,\n })}\n style={[{ gap: t.space[\"3\"] }, style]}\n >\n {flipOnClick ? (\n <Pressable\n {...cardHooks}\n accessibilityRole=\"button\"\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{\n ...accessibilityState,\n selected: flipped,\n disabled,\n }}\n disabled={disabled}\n onPress={() => {\n if (interactive) setFlipped(!flipped);\n }}\n style={cardStyle}\n >\n {faces}\n </Pressable>\n ) : (\n <View\n {...cardHooks}\n accessibilityLabel={accessibilityLabel}\n style={cardStyle}\n >\n {faces}\n </View>\n )}\n {shown(footer) ? (\n <View\n {...part(\"flashcard\", \"footer\")}\n style={{\n flexDirection: \"row\",\n flexWrap: \"wrap\",\n justifyContent: \"center\",\n gap: t.space[\"2\"],\n }}\n >\n {footer}\n </View>\n ) : null}\n </View>\n );\n}\n"],"mappings":"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"}
@@ -273,7 +273,12 @@ export function Flashcard({
273
273
  })}
274
274
  </View>
275
275
  ) : null}
276
- <View style={{ alignItems: "center" }}>
276
+ {/* Plain text is centred; rich content stretches so it lines up with the label. */}
277
+ <View
278
+ style={{
279
+ alignItems: isText(isBack ? back : front) ? "center" : "stretch",
280
+ }}
281
+ >
277
282
  {textOf(isBack ? back : front, {
278
283
  ...textStyle(t, FONT[size], font),
279
284
  color: t.colors["text-color"],
@@ -3208,9 +3208,9 @@ function Xi({ maxWidth: e = 1200, ...n }) {
3208
3208
  }
3209
3209
  function Zi({ title: e, description: n, actions: r, children: i, ...a }) {
3210
3210
  return /* @__PURE__ */ X(t, {
3211
- name: "page",
3212
- part: "header",
3211
+ name: "page-header",
3213
3212
  ...a,
3213
+ className: J("mn-page__header", a.className),
3214
3214
  children: [/* @__PURE__ */ X(q, { children: [
3215
3215
  /* @__PURE__ */ Y(K, {
3216
3216
  className: "mn-page-title",
@@ -3226,9 +3226,9 @@ function Zi({ title: e, description: n, actions: r, children: i, ...a }) {
3226
3226
  }
3227
3227
  function Qi({ title: e, description: n, actions: r, icon: i, children: a, ...o }) {
3228
3228
  return /* @__PURE__ */ X(t, {
3229
- name: "page",
3230
- part: "section",
3229
+ name: "page-section",
3231
3230
  ...o,
3231
+ className: J("mn-page__section", o.className),
3232
3232
  children: [
3233
3233
  /* @__PURE__ */ X(q, {
3234
3234
  className: "mn-page-section-heading",
@@ -2998,6 +2998,8 @@
2998
2998
  font-weight: 600;
2999
2999
  }
3000
3000
  .mn-page__section {
3001
+ /* Taro sections also carry .mn-page-section; keep the bottom-only spacing. */
3002
+ margin-top: 0;
3001
3003
  margin-bottom: 24px;
3002
3004
  }
3003
3005
  .mn-page-section-heading {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minerva-design",
3
- "version": "1.13.0",
3
+ "version": "1.13.2",
4
4
  "description": "Minerva Design: React, Vue, Angular, Web Components and native platform components with shared design tokens and contracts",
5
5
  "license": "MIT",
6
6
  "author": "fwx5618177 <fwx5618177@gmail.com>",