@aglyn/shared-ui-jsx 1.0.0-beta.263 → 1.0.0-beta.266

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-ui-jsx",
3
- "version": "1.0.0-beta.263",
3
+ "version": "1.0.0-beta.266",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,10 +25,10 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-mdi": "1.0.0-beta.263",
29
- "@aglyn/shared-ui-theme": "1.0.0-beta.263",
30
- "@aglyn/shared-util-tools": "1.0.0-beta.263",
31
- "@aglyn/shared-util-vendor": "1.0.0-beta.263",
28
+ "@aglyn/shared-data-mdi": "1.0.0-beta.266",
29
+ "@aglyn/shared-ui-theme": "1.0.0-beta.266",
30
+ "@aglyn/shared-util-tools": "1.0.0-beta.266",
31
+ "@aglyn/shared-util-vendor": "1.0.0-beta.266",
32
32
  "@mdi/js": "^7.4.47",
33
33
  "@swc/helpers": "0.5.23",
34
34
  "change-case": "^5.4.4",
@@ -14,6 +14,18 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ /**
18
+ * A picture across the top of an {@link OptionCard} (AGL-3898): a real
19
+ * example of the choice, at a fixed size so the grid does not shift while it
20
+ * loads, and loaded lazily, since a grid of them is mostly below the fold.
21
+ */
22
+ export interface OptionCardImage {
23
+ src: string;
24
+ /** What the picture shows, for a reader who cannot see it. */
25
+ alt: string;
26
+ width: number;
27
+ height: number;
28
+ }
17
29
  /** One choice in an {@link OptionCardGrid}. */
18
30
  export interface OptionCard<T extends string = string> {
19
31
  id: T;
@@ -21,6 +33,8 @@ export interface OptionCard<T extends string = string> {
21
33
  description?: string;
22
34
  /** An SVG path, such as an MDI icon's. */
23
35
  icon?: string;
36
+ /** A picture of the choice, drawn above its title. */
37
+ image?: OptionCardImage;
24
38
  }
25
39
  export interface OptionCardGridProps<T extends string = string> {
26
40
  /** What the group is, for assistive technology. */
@@ -35,12 +49,15 @@ export interface OptionCardGridProps<T extends string = string> {
35
49
  sm?: number;
36
50
  md?: number;
37
51
  };
52
+ /** `data-*` attributes for the grid's root, such as a page's own marker for where clicks land. */
53
+ [data: `data-${string}`]: string | undefined;
38
54
  }
39
55
  /**
40
56
  * A grid of cards to pick one from (AGL-3660): a radio group drawn as cards,
41
- * each with an icon, a title and a line under it. The selected card carries
42
- * the primary color and a check; the arrow keys move between cards and pick,
43
- * as a radio group's do, and only the picked card is in the tab order.
57
+ * each with an icon or a picture (AGL-3898), a title and a line under it. The
58
+ * selected card carries the primary color, and a picture card a check over
59
+ * its picture; the arrow keys move between cards and pick, as a radio group's
60
+ * do, and only the picked card is in the tab order.
44
61
  */
45
- export declare function OptionCardGrid<T extends string = string>({ label, options, value, onChange, columns, }: OptionCardGridProps<T>): import("react").JSX.Element;
62
+ export declare function OptionCardGrid<T extends string = string>({ label, options, value, onChange, columns, ...rest }: OptionCardGridProps<T>): import("react").JSX.Element;
46
63
  export default OptionCardGrid;
@@ -1,3 +1,5 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
1
3
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
4
  /**
3
5
  * @license
@@ -14,20 +16,29 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
16
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
17
  * See the License for the specific language governing permissions and
16
18
  * limitations under the License.
17
- */ import { Box, Card, CardActionArea, Stack, Typography } from "@mui/material";
19
+ */ import { mdiCheckCircle } from "@aglyn/shared-data-mdi";
20
+ import { Box, Card, CardActionArea, Stack, Typography } from "@mui/material";
18
21
  import { alpha } from "@mui/material/styles";
19
22
  import { useRef } from "react";
20
23
  import { MdiIcon } from "./mdi-icon/mdi-icon.js";
21
24
  /**
22
25
  * A grid of cards to pick one from (AGL-3660): a radio group drawn as cards,
23
- * each with an icon, a title and a line under it. The selected card carries
24
- * the primary color and a check; the arrow keys move between cards and pick,
25
- * as a radio group's do, and only the picked card is in the tab order.
26
- */ export function OptionCardGrid({ label, options, value, onChange, columns = {
27
- xs: 1,
28
- sm: 2,
29
- md: 3
30
- } }) {
26
+ * each with an icon or a picture (AGL-3898), a title and a line under it. The
27
+ * selected card carries the primary color, and a picture card a check over
28
+ * its picture; the arrow keys move between cards and pick, as a radio group's
29
+ * do, and only the picked card is in the tab order.
30
+ */ export function OptionCardGrid(_0) {
31
+ let { label, options, value, onChange, columns = {
32
+ xs: 1,
33
+ sm: 2,
34
+ md: 3
35
+ } } = _0, rest = _object_without_properties_loose(_0, [
36
+ "label",
37
+ "options",
38
+ "value",
39
+ "onChange",
40
+ "columns"
41
+ ]);
31
42
  var _columns_xs;
32
43
  const refs = useRef([]);
33
44
  const selectedIndex = options.findIndex((option)=>option.id === value);
@@ -42,9 +53,10 @@ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
42
53
  onChange(options[next].id);
43
54
  };
44
55
  const template = (count)=>count ? `repeat(${count}, minmax(0, 1fr))` : undefined;
45
- return /*#__PURE__*/ _jsx(Box, {
56
+ return /*#__PURE__*/ _jsx(Box, _extends({
46
57
  role: "radiogroup",
47
- "aria-label": label,
58
+ "aria-label": label
59
+ }, rest, {
48
60
  sx: {
49
61
  display: 'grid',
50
62
  gap: 1.5,
@@ -63,7 +75,7 @@ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
63
75
  borderColor: selected ? 'primary.main' : 'divider',
64
76
  bgcolor: selected ? alpha(theme.palette.primary.main, 0.08) : 'transparent'
65
77
  }),
66
- children: /*#__PURE__*/ _jsx(CardActionArea, {
78
+ children: /*#__PURE__*/ _jsxs(CardActionArea, {
67
79
  ref: (node)=>{
68
80
  refs.current[index] = node;
69
81
  },
@@ -74,65 +86,117 @@ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
74
86
  onClick: ()=>onChange(option.id),
75
87
  onKeyDown: (event)=>onKeyDown(event, index),
76
88
  sx: {
77
- p: 2,
89
+ p: option.image ? 0 : 2,
78
90
  height: '100%',
79
91
  minHeight: 44,
80
92
  display: 'flex',
81
- alignItems: 'flex-start'
93
+ flexDirection: option.image ? 'column' : 'row',
94
+ alignItems: option.image ? 'stretch' : 'flex-start',
95
+ justifyContent: 'flex-start'
82
96
  },
83
- children: /*#__PURE__*/ _jsxs(Stack, {
84
- direction: "row",
85
- spacing: 1.5,
86
- sx: {
87
- alignItems: 'flex-start',
88
- width: '100%'
89
- },
90
- children: [
91
- option.icon && /*#__PURE__*/ _jsx(Box, {
92
- sx: {
93
- color: selected ? 'primary.main' : 'text.secondary',
94
- display: 'flex',
95
- pt: 0.25
96
- },
97
- "aria-hidden": true,
98
- children: /*#__PURE__*/ _jsx(MdiIcon, {
99
- path: option.icon,
97
+ children: [
98
+ option.image && /*#__PURE__*/ _jsxs(Box, {
99
+ sx: {
100
+ position: 'relative',
101
+ width: '100%'
102
+ },
103
+ children: [
104
+ /*#__PURE__*/ _jsx(Box, {
105
+ component: "img",
106
+ src: option.image.src,
107
+ alt: option.image.alt,
108
+ width: option.image.width,
109
+ height: option.image.height,
110
+ loading: "lazy",
111
+ decoding: "async",
100
112
  sx: {
101
- fontSize: 22
113
+ display: 'block',
114
+ width: '100%',
115
+ height: 'auto',
116
+ aspectRatio: `${option.image.width} / ${option.image.height}`,
117
+ objectFit: 'cover',
118
+ objectPosition: 'top',
119
+ borderBottom: 1,
120
+ borderColor: 'divider',
121
+ bgcolor: 'action.hover'
102
122
  }
103
- })
104
- }),
105
- /*#__PURE__*/ _jsxs(Box, {
106
- sx: {
107
- flex: 1,
108
- minWidth: 0
109
- },
110
- children: [
111
- /*#__PURE__*/ _jsx(Typography, {
112
- variant: "subtitle2",
113
- component: "span",
123
+ }),
124
+ selected && /*#__PURE__*/ _jsx(Box, {
125
+ "aria-hidden": true,
126
+ sx: {
127
+ position: 'absolute',
128
+ top: 8,
129
+ right: 8,
130
+ display: 'flex',
131
+ borderRadius: '50%',
132
+ bgcolor: 'background.paper',
133
+ color: 'primary.main',
134
+ boxShadow: 2
135
+ },
136
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
137
+ path: mdiCheckCircle.path,
114
138
  sx: {
115
- display: 'block'
116
- },
117
- children: option.title
118
- }),
119
- option.description && /*#__PURE__*/ _jsx(Typography, {
120
- variant: "body2",
121
- color: "text.secondary",
122
- component: "span",
139
+ fontSize: 24
140
+ }
141
+ })
142
+ })
143
+ ]
144
+ }),
145
+ /*#__PURE__*/ _jsxs(Stack, {
146
+ direction: "row",
147
+ spacing: 1.5,
148
+ sx: {
149
+ alignItems: 'flex-start',
150
+ width: '100%',
151
+ p: option.image ? 1.5 : 0
152
+ },
153
+ children: [
154
+ option.icon && /*#__PURE__*/ _jsx(Box, {
155
+ sx: {
156
+ color: selected ? 'primary.main' : 'text.secondary',
157
+ display: 'flex',
158
+ pt: 0.25
159
+ },
160
+ "aria-hidden": true,
161
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
162
+ path: option.icon,
123
163
  sx: {
124
- display: 'block'
125
- },
126
- children: option.description
164
+ fontSize: 22
165
+ }
127
166
  })
128
- ]
129
- })
130
- ]
131
- })
167
+ }),
168
+ /*#__PURE__*/ _jsxs(Box, {
169
+ sx: {
170
+ flex: 1,
171
+ minWidth: 0
172
+ },
173
+ children: [
174
+ /*#__PURE__*/ _jsx(Typography, {
175
+ variant: "subtitle2",
176
+ component: "span",
177
+ sx: {
178
+ display: 'block'
179
+ },
180
+ children: option.title
181
+ }),
182
+ option.description && /*#__PURE__*/ _jsx(Typography, {
183
+ variant: "body2",
184
+ color: "text.secondary",
185
+ component: "span",
186
+ sx: {
187
+ display: 'block'
188
+ },
189
+ children: option.description
190
+ })
191
+ ]
192
+ })
193
+ ]
194
+ })
195
+ ]
132
196
  })
133
197
  }, option.id);
134
198
  })
135
- });
199
+ }));
136
200
  }
137
201
  export default OptionCardGrid;
138
202
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/option-card-grid.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Box, Card, CardActionArea, Stack, Typography } from '@mui/material'\nimport { alpha, type Theme } from '@mui/material/styles'\nimport { useRef, type KeyboardEvent } from 'react'\nimport { MdiIcon } from './mdi-icon/mdi-icon'\n\n/** One choice in an {@link OptionCardGrid}. */\nexport interface OptionCard<T extends string = string> {\n id: T\n title: string\n description?: string\n /** An SVG path, such as an MDI icon's. */\n icon?: string\n}\n\nexport interface OptionCardGridProps<T extends string = string> {\n /** What the group is, for assistive technology. */\n label: string\n options: readonly OptionCard<T>[]\n /** The selected option, or `null` for none. */\n value: T | null\n onChange: (value: T) => void\n /** Columns at each breakpoint. */\n columns?: { xs?: number; sm?: number; md?: number }\n}\n\n/**\n * A grid of cards to pick one from (AGL-3660): a radio group drawn as cards,\n * each with an icon, a title and a line under it. The selected card carries\n * the primary color and a check; the arrow keys move between cards and pick,\n * as a radio group's do, and only the picked card is in the tab order.\n */\nexport function OptionCardGrid<T extends string = string>({\n label,\n options,\n value,\n onChange,\n columns = { xs: 1, sm: 2, md: 3 },\n}: OptionCardGridProps<T>) {\n const refs = useRef<Array<HTMLButtonElement | null>>([])\n const selectedIndex = options.findIndex((option) => option.id === value)\n const focusable = selectedIndex === -1 ? 0 : selectedIndex\n\n const onKeyDown = (event: KeyboardEvent, index: number) => {\n const step =\n event.key === 'ArrowRight' || event.key === 'ArrowDown'\n ? 1\n : event.key === 'ArrowLeft' || event.key === 'ArrowUp'\n ? -1\n : 0\n if (!step) return\n event.preventDefault()\n const next = (index + step + options.length) % options.length\n refs.current[next]?.focus()\n onChange(options[next].id)\n }\n\n const template = (count?: number) => (count ? `repeat(${count}, minmax(0, 1fr))` : undefined)\n return (\n <Box\n role=\"radiogroup\"\n aria-label={label}\n sx={{\n display: 'grid',\n gap: 1.5,\n gridTemplateColumns: {\n xs: template(columns.xs ?? 1),\n sm: template(columns.sm),\n md: template(columns.md),\n },\n }}\n >\n {options.map((option, index) => {\n const selected = option.id === value\n return (\n <Card\n key={option.id}\n variant=\"outlined\"\n sx={(theme: Theme) => ({\n borderRadius: 2,\n borderColor: selected ? 'primary.main' : 'divider',\n bgcolor: selected ? alpha(theme.palette.primary.main, 0.08) : 'transparent',\n })}\n >\n <CardActionArea\n ref={(node: HTMLButtonElement | null) => {\n refs.current[index] = node\n }}\n role=\"radio\"\n aria-checked={selected}\n aria-label={option.description ? `${option.title}: ${option.description}` : option.title}\n tabIndex={index === focusable ? 0 : -1}\n onClick={() => onChange(option.id)}\n onKeyDown={(event) => onKeyDown(event, index)}\n sx={{ p: 2, height: '100%', minHeight: 44, display: 'flex', alignItems: 'flex-start' }}\n >\n <Stack direction=\"row\" spacing={1.5} sx={{ alignItems: 'flex-start', width: '100%' }}>\n {option.icon && (\n <Box sx={{ color: selected ? 'primary.main' : 'text.secondary', display: 'flex', pt: 0.25 }} aria-hidden>\n <MdiIcon path={option.icon} sx={{ fontSize: 22 }} />\n </Box>\n )}\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"subtitle2\" component=\"span\" sx={{ display: 'block' }}>\n {option.title}\n </Typography>\n {option.description && (\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"span\" sx={{ display: 'block' }}>\n {option.description}\n </Typography>\n )}\n </Box>\n </Stack>\n </CardActionArea>\n </Card>\n )\n })}\n </Box>\n )\n}\n\nexport default OptionCardGrid\n"],"names":["Box","Card","CardActionArea","Stack","Typography","alpha","useRef","MdiIcon","OptionCardGrid","label","options","value","onChange","columns","xs","sm","md","refs","selectedIndex","findIndex","option","id","focusable","onKeyDown","event","index","step","key","preventDefault","next","length","current","focus","template","count","undefined","role","aria-label","sx","display","gap","gridTemplateColumns","map","selected","variant","theme","borderRadius","borderColor","bgcolor","palette","primary","main","ref","node","aria-checked","description","title","tabIndex","onClick","p","height","minHeight","alignItems","direction","spacing","width","icon","color","pt","aria-hidden","path","fontSize","flex","minWidth","component"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/option-card-grid.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { mdiCheckCircle } from '@aglyn/shared-data-mdi'\nimport { Box, Card, CardActionArea, Stack, Typography } from '@mui/material'\nimport { alpha, type Theme } from '@mui/material/styles'\nimport { useRef, type KeyboardEvent } from 'react'\nimport { MdiIcon } from './mdi-icon/mdi-icon'\n\n/**\n * A picture across the top of an {@link OptionCard} (AGL-3898): a real\n * example of the choice, at a fixed size so the grid does not shift while it\n * loads, and loaded lazily, since a grid of them is mostly below the fold.\n */\nexport interface OptionCardImage {\n src: string\n /** What the picture shows, for a reader who cannot see it. */\n alt: string\n width: number\n height: number\n}\n\n/** One choice in an {@link OptionCardGrid}. */\nexport interface OptionCard<T extends string = string> {\n id: T\n title: string\n description?: string\n /** An SVG path, such as an MDI icon's. */\n icon?: string\n /** A picture of the choice, drawn above its title. */\n image?: OptionCardImage\n}\n\nexport interface OptionCardGridProps<T extends string = string> {\n /** What the group is, for assistive technology. */\n label: string\n options: readonly OptionCard<T>[]\n /** The selected option, or `null` for none. */\n value: T | null\n onChange: (value: T) => void\n /** Columns at each breakpoint. */\n columns?: { xs?: number; sm?: number; md?: number }\n /** `data-*` attributes for the grid's root, such as a page's own marker for where clicks land. */\n [data: `data-${string}`]: string | undefined\n}\n\n/**\n * A grid of cards to pick one from (AGL-3660): a radio group drawn as cards,\n * each with an icon or a picture (AGL-3898), a title and a line under it. The\n * selected card carries the primary color, and a picture card a check over\n * its picture; the arrow keys move between cards and pick, as a radio group's\n * do, and only the picked card is in the tab order.\n */\nexport function OptionCardGrid<T extends string = string>({\n label,\n options,\n value,\n onChange,\n columns = { xs: 1, sm: 2, md: 3 },\n ...rest\n}: OptionCardGridProps<T>) {\n const refs = useRef<Array<HTMLButtonElement | null>>([])\n const selectedIndex = options.findIndex((option) => option.id === value)\n const focusable = selectedIndex === -1 ? 0 : selectedIndex\n\n const onKeyDown = (event: KeyboardEvent, index: number) => {\n const step =\n event.key === 'ArrowRight' || event.key === 'ArrowDown'\n ? 1\n : event.key === 'ArrowLeft' || event.key === 'ArrowUp'\n ? -1\n : 0\n if (!step) return\n event.preventDefault()\n const next = (index + step + options.length) % options.length\n refs.current[next]?.focus()\n onChange(options[next].id)\n }\n\n const template = (count?: number) => (count ? `repeat(${count}, minmax(0, 1fr))` : undefined)\n return (\n <Box\n role=\"radiogroup\"\n aria-label={label}\n {...rest}\n sx={{\n display: 'grid',\n gap: 1.5,\n gridTemplateColumns: {\n xs: template(columns.xs ?? 1),\n sm: template(columns.sm),\n md: template(columns.md),\n },\n }}\n >\n {options.map((option, index) => {\n const selected = option.id === value\n return (\n <Card\n key={option.id}\n variant=\"outlined\"\n sx={(theme: Theme) => ({\n borderRadius: 2,\n borderColor: selected ? 'primary.main' : 'divider',\n bgcolor: selected ? alpha(theme.palette.primary.main, 0.08) : 'transparent',\n })}\n >\n <CardActionArea\n ref={(node: HTMLButtonElement | null) => {\n refs.current[index] = node\n }}\n role=\"radio\"\n aria-checked={selected}\n aria-label={option.description ? `${option.title}: ${option.description}` : option.title}\n tabIndex={index === focusable ? 0 : -1}\n onClick={() => onChange(option.id)}\n onKeyDown={(event) => onKeyDown(event, index)}\n sx={{\n p: option.image ? 0 : 2,\n height: '100%',\n minHeight: 44,\n display: 'flex',\n flexDirection: option.image ? 'column' : 'row',\n alignItems: option.image ? 'stretch' : 'flex-start',\n justifyContent: 'flex-start',\n }}\n >\n {option.image && (\n <Box sx={{ position: 'relative', width: '100%' }}>\n <Box\n component=\"img\"\n src={option.image.src}\n alt={option.image.alt}\n width={option.image.width}\n height={option.image.height}\n loading=\"lazy\"\n decoding=\"async\"\n sx={{\n display: 'block',\n width: '100%',\n height: 'auto',\n aspectRatio: `${option.image.width} / ${option.image.height}`,\n objectFit: 'cover',\n objectPosition: 'top',\n borderBottom: 1,\n borderColor: 'divider',\n bgcolor: 'action.hover',\n }}\n />\n {selected && (\n <Box\n aria-hidden\n sx={{\n position: 'absolute',\n top: 8,\n right: 8,\n display: 'flex',\n borderRadius: '50%',\n bgcolor: 'background.paper',\n color: 'primary.main',\n boxShadow: 2,\n }}\n >\n <MdiIcon path={mdiCheckCircle.path} sx={{ fontSize: 24 }} />\n </Box>\n )}\n </Box>\n )}\n <Stack direction=\"row\" spacing={1.5} sx={{ alignItems: 'flex-start', width: '100%', p: option.image ? 1.5 : 0 }}>\n {option.icon && (\n <Box sx={{ color: selected ? 'primary.main' : 'text.secondary', display: 'flex', pt: 0.25 }} aria-hidden>\n <MdiIcon path={option.icon} sx={{ fontSize: 22 }} />\n </Box>\n )}\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"subtitle2\" component=\"span\" sx={{ display: 'block' }}>\n {option.title}\n </Typography>\n {option.description && (\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"span\" sx={{ display: 'block' }}>\n {option.description}\n </Typography>\n )}\n </Box>\n </Stack>\n </CardActionArea>\n </Card>\n )\n })}\n </Box>\n )\n}\n\nexport default OptionCardGrid\n"],"names":["mdiCheckCircle","Box","Card","CardActionArea","Stack","Typography","alpha","useRef","MdiIcon","OptionCardGrid","label","options","value","onChange","columns","xs","sm","md","rest","refs","selectedIndex","findIndex","option","id","focusable","onKeyDown","event","index","step","key","preventDefault","next","length","current","focus","template","count","undefined","role","aria-label","sx","display","gap","gridTemplateColumns","map","selected","variant","theme","borderRadius","borderColor","bgcolor","palette","primary","main","ref","node","aria-checked","description","title","tabIndex","onClick","p","image","height","minHeight","flexDirection","alignItems","justifyContent","position","width","component","src","alt","loading","decoding","aspectRatio","objectFit","objectPosition","borderBottom","aria-hidden","top","right","color","boxShadow","path","fontSize","direction","spacing","icon","pt","flex","minWidth"],"mappings":"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"}