minerva-design 1.13.0 → 1.13.1
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 +8 -0
- package/dist/native/components/Flashcard/Flashcard.cjs +1 -1
- package/dist/native/components/Flashcard/Flashcard.cjs.map +1 -1
- package/dist/native/components/Flashcard/Flashcard.js +36 -36
- package/dist/native/components/Flashcard/Flashcard.js.map +1 -1
- package/dist/native/source/components/Flashcard/Flashcard.tsx +6 -1
- package/dist/taro/index.js +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# minerva-design
|
|
2
2
|
|
|
3
|
+
## 1.13.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
3
11
|
## 1.13.0
|
|
4
12
|
|
|
5
13
|
### 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`},
|
|
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
|
|
7
|
-
import { Fragment as
|
|
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
|
|
9
|
+
var ie = {
|
|
10
10
|
small: "3",
|
|
11
11
|
medium: "5",
|
|
12
12
|
large: "6"
|
|
13
|
-
},
|
|
13
|
+
}, h = {
|
|
14
14
|
small: "md",
|
|
15
15
|
medium: "lg",
|
|
16
16
|
large: "xl"
|
|
17
|
-
},
|
|
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
|
|
25
|
-
let { tokens: L, fonts: R, reducedMotion: z } = t(), { t: B } = e(), [V, H] = a(
|
|
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 ?
|
|
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[
|
|
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 =
|
|
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
|
-
...
|
|
58
|
+
...M === "outline" ? {
|
|
59
59
|
backgroundColor: L.colors["surface-color"],
|
|
60
60
|
borderWidth: 1,
|
|
61
61
|
borderColor: L.colors["border-strong-color"]
|
|
62
|
-
} :
|
|
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 =
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
127
|
-
style: { alignItems: "center" },
|
|
128
|
-
children: Y(t ?
|
|
129
|
-
...i(L,
|
|
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 &&
|
|
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(
|
|
145
|
+
}, Z = /* @__PURE__ */ m(f, { children: [X("front"), X("back")] }), Q = {
|
|
146
146
|
height: ue,
|
|
147
|
-
opacity:
|
|
147
|
+
opacity: j ? .6 : 1
|
|
148
148
|
}, $ = n("flashcard", "card", {
|
|
149
149
|
state: V ? "active" : "inactive",
|
|
150
|
-
disabled:
|
|
150
|
+
disabled: j
|
|
151
151
|
});
|
|
152
|
-
return /* @__PURE__ */ m(
|
|
152
|
+
return /* @__PURE__ */ m(d, {
|
|
153
153
|
...I,
|
|
154
154
|
...n("flashcard", "root", {
|
|
155
155
|
state: V ? "active" : "inactive",
|
|
156
|
-
disabled:
|
|
157
|
-
variant:
|
|
158
|
-
size:
|
|
156
|
+
disabled: j,
|
|
157
|
+
variant: M,
|
|
158
|
+
size: N
|
|
159
159
|
}),
|
|
160
|
-
style: [{ gap: L.space[3] },
|
|
161
|
-
children: [
|
|
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:
|
|
168
|
+
disabled: j
|
|
169
169
|
},
|
|
170
|
-
disabled:
|
|
170
|
+
disabled: j,
|
|
171
171
|
onPress: () => {
|
|
172
172
|
fe && H(!V);
|
|
173
173
|
},
|
|
174
174
|
style: Q,
|
|
175
175
|
children: Z
|
|
176
|
-
}) : /* @__PURE__ */ p(
|
|
176
|
+
}) : /* @__PURE__ */ p(d, {
|
|
177
177
|
...$,
|
|
178
178
|
accessibilityLabel: P,
|
|
179
179
|
style: Q,
|
|
180
180
|
children: Z
|
|
181
|
-
}), v(
|
|
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:
|
|
189
|
+
children: C
|
|
190
190
|
}) : null]
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
193
|
//#endregion
|
|
194
|
-
export {
|
|
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
|
-
|
|
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"],
|
package/dist/taro/index.js
CHANGED
|
@@ -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",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minerva-design",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.1",
|
|
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>",
|