@aglyn/shared-ui-jsx 1.0.0-beta.292 → 1.0.0-beta.296
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 +6 -6
- package/src/lib/components/option-card-grid.component.d.ts +23 -1
- package/src/lib/components/option-card-grid.component.js +36 -3
- package/src/lib/components/option-card-grid.component.js.map +1 -1
- package/src/lib/components/site-live-notice.component.d.ts +5 -1
- package/src/lib/components/site-live-notice.component.js +7 -1
- package/src/lib/components/site-live-notice.component.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/shared-ui-jsx",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.296",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.296",
|
|
29
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.296",
|
|
30
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.296",
|
|
31
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.296",
|
|
32
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.296",
|
|
33
33
|
"@mdi/js": "^7.4.47",
|
|
34
34
|
"@swc/helpers": "0.5.23",
|
|
35
35
|
"change-case": "^5.4.4",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import { type ChipProps } from '@mui/material';
|
|
17
18
|
import { type Theme } from '@mui/material/styles';
|
|
18
19
|
/**
|
|
19
20
|
* A picture across the top of an {@link OptionCard} (AGL-3898): a real
|
|
@@ -27,6 +28,18 @@ export interface OptionCardImage {
|
|
|
27
28
|
width: number;
|
|
28
29
|
height: number;
|
|
29
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* A short fact about a choice, drawn as a small chip under its line: a price,
|
|
33
|
+
* who made it, how many pages it has. Theme colors only; the label is read
|
|
34
|
+
* with the card's name, so a chip is never the only place a fact is said.
|
|
35
|
+
*/
|
|
36
|
+
export interface OptionCardChip {
|
|
37
|
+
label: string;
|
|
38
|
+
/** A theme palette color; `default` when absent. */
|
|
39
|
+
color?: ChipProps['color'];
|
|
40
|
+
/** `outlined` when absent. */
|
|
41
|
+
variant?: 'filled' | 'outlined';
|
|
42
|
+
}
|
|
30
43
|
/** One choice in an {@link OptionCardGrid}. */
|
|
31
44
|
export interface OptionCard<T extends string = string> {
|
|
32
45
|
id: T;
|
|
@@ -36,7 +49,15 @@ export interface OptionCard<T extends string = string> {
|
|
|
36
49
|
icon?: string;
|
|
37
50
|
/** A picture of the choice, drawn above its title. */
|
|
38
51
|
image?: OptionCardImage;
|
|
52
|
+
/** Facts drawn as chips under its line, in this order. */
|
|
53
|
+
chips?: readonly OptionCardChip[];
|
|
39
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* What a card is called for assistive technology: its title, its line after a
|
|
57
|
+
* colon, and its chips in parentheses — every word the card shows, since the
|
|
58
|
+
* card is one control and its name is all a screen reader says of it.
|
|
59
|
+
*/
|
|
60
|
+
export declare function optionCardLabel(option: Pick<OptionCard, 'title' | 'description' | 'chips'>): string;
|
|
40
61
|
export interface OptionCardGridProps<T extends string = string> {
|
|
41
62
|
/** What the group is, for assistive technology. */
|
|
42
63
|
label: string;
|
|
@@ -87,7 +108,8 @@ export declare function optionCardSx(selected: boolean): (theme: Theme) => {
|
|
|
87
108
|
export declare function OptionCardCheck(): import("react").JSX.Element;
|
|
88
109
|
/**
|
|
89
110
|
* A grid of cards to pick one from (AGL-3660): a radio group drawn as cards,
|
|
90
|
-
* each with an icon or a picture (AGL-3898), a title
|
|
111
|
+
* each with an icon or a picture (AGL-3898), a title, a line under it and any
|
|
112
|
+
* facts as chips under that (AGL-4049), which its accessible name reads too. Each
|
|
91
113
|
* card is a surface with an outline that reads on a gray page, its title in
|
|
92
114
|
* the text color and its line in the secondary text color, both at AA on the
|
|
93
115
|
* card (AGL-3985). The picked card is outlined 2px in the primary color and
|
|
@@ -17,10 +17,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
17
17
|
* See the License for the specific language governing permissions and
|
|
18
18
|
* limitations under the License.
|
|
19
19
|
*/ import { mdiCheckCircle } from "@aglyn/shared-data-mdi";
|
|
20
|
-
import { Box, Card, CardActionArea, Stack, Typography } from "@mui/material";
|
|
20
|
+
import { Box, Card, CardActionArea, Chip, Stack, Typography } from "@mui/material";
|
|
21
21
|
import { alpha } from "@mui/material/styles";
|
|
22
22
|
import { useRef } from "react";
|
|
23
23
|
import { MdiIcon } from "./mdi-icon/mdi-icon.js";
|
|
24
|
+
/**
|
|
25
|
+
* What a card is called for assistive technology: its title, its line after a
|
|
26
|
+
* colon, and its chips in parentheses — every word the card shows, since the
|
|
27
|
+
* card is one control and its name is all a screen reader says of it.
|
|
28
|
+
*/ export function optionCardLabel(option) {
|
|
29
|
+
var _option_chips;
|
|
30
|
+
const named = option.description ? `${option.title}: ${option.description}` : option.title;
|
|
31
|
+
const chips = ((_option_chips = option.chips) != null ? _option_chips : []).map((chip)=>chip.label).filter(Boolean);
|
|
32
|
+
return chips.length ? `${named} (${chips.join(', ')})` : named;
|
|
33
|
+
}
|
|
24
34
|
/**
|
|
25
35
|
* How an option card sits on the page around it (AGL-3985). A grid of them is
|
|
26
36
|
* often drawn on a dialog's gray, where a transparent card with a divider
|
|
@@ -81,7 +91,8 @@ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
|
|
|
81
91
|
}
|
|
82
92
|
/**
|
|
83
93
|
* A grid of cards to pick one from (AGL-3660): a radio group drawn as cards,
|
|
84
|
-
* each with an icon or a picture (AGL-3898), a title
|
|
94
|
+
* each with an icon or a picture (AGL-3898), a title, a line under it and any
|
|
95
|
+
* facts as chips under that (AGL-4049), which its accessible name reads too. Each
|
|
85
96
|
* card is a surface with an outline that reads on a gray page, its title in
|
|
86
97
|
* the text color and its line in the secondary text color, both at AA on the
|
|
87
98
|
* card (AGL-3985). The picked card is outlined 2px in the primary color and
|
|
@@ -140,7 +151,7 @@ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
|
|
|
140
151
|
},
|
|
141
152
|
role: "radio",
|
|
142
153
|
"aria-checked": selected,
|
|
143
|
-
"aria-label": option
|
|
154
|
+
"aria-label": optionCardLabel(option),
|
|
144
155
|
tabIndex: index === focusable ? 0 : -1,
|
|
145
156
|
onClick: ()=>onChange(option.id),
|
|
146
157
|
onKeyDown: (event)=>onKeyDown(event, index),
|
|
@@ -226,6 +237,28 @@ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
|
|
|
226
237
|
mt: 0.25
|
|
227
238
|
},
|
|
228
239
|
children: option.description
|
|
240
|
+
}),
|
|
241
|
+
option.chips && option.chips.length > 0 && /*#__PURE__*/ _jsx(Stack, {
|
|
242
|
+
component: "span",
|
|
243
|
+
direction: "row",
|
|
244
|
+
useFlexGap: true,
|
|
245
|
+
"aria-hidden": true,
|
|
246
|
+
"data-option-card-chips": "",
|
|
247
|
+
sx: {
|
|
248
|
+
flexWrap: 'wrap',
|
|
249
|
+
gap: 0.5,
|
|
250
|
+
mt: 1
|
|
251
|
+
},
|
|
252
|
+
children: option.chips.map((chip, chipIndex)=>{
|
|
253
|
+
var _chip_color, _chip_variant;
|
|
254
|
+
return /*#__PURE__*/ _jsx(Chip, {
|
|
255
|
+
component: "span",
|
|
256
|
+
size: "small",
|
|
257
|
+
label: chip.label,
|
|
258
|
+
color: (_chip_color = chip.color) != null ? _chip_color : 'default',
|
|
259
|
+
variant: (_chip_variant = chip.variant) != null ? _chip_variant : 'outlined'
|
|
260
|
+
}, `${chipIndex}-${chip.label}`);
|
|
261
|
+
})
|
|
229
262
|
})
|
|
230
263
|
]
|
|
231
264
|
})
|
|
@@ -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 { 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 * How an option card sits on the page around it (AGL-3985). A grid of them is\n * often drawn on a dialog's gray, where a transparent card with a divider\n * outline melted into the page, so each card is:\n *\n * - a surface of its own, `background.paper`;\n * - outlined as strongly as an outlined field (the text color at 23%, the\n * border MUI's OutlinedInput draws), the text color on hover;\n * - once picked, outlined 2px in the primary color, from a 1px ring laid\n * outside its 1px border so the grid never shifts, with a check in a disc\n * at its top corner ({@link OptionCardCheck});\n * - ringed in the primary color while it has the keyboard's focus.\n *\n * Theme values only, so it reads the same in light and dark mode.\n */\nexport function optionCardSx(selected: boolean) {\n return (theme: Theme) => ({\n position: 'relative' as const,\n borderRadius: 2,\n bgcolor: 'background.paper',\n borderColor: selected ? theme.palette.primary.main : alpha(theme.palette.text.primary, 0.23),\n boxShadow: selected ? `0 0 0 1px ${theme.palette.primary.main}` : 'none',\n transition: theme.transitions.create(['border-color', 'box-shadow']),\n '&:hover': { borderColor: selected ? theme.palette.primary.main : theme.palette.text.primary },\n '&:has(.Mui-focusVisible)': {\n outline: `2px solid ${theme.palette.primary.main}`,\n outlineOffset: '2px',\n },\n })\n}\n\n/** The check a picked card carries in its top corner: over its picture, or beside its words. */\nexport function OptionCardCheck() {\n return (\n <Box\n aria-hidden\n data-option-card-check=\"\"\n sx={{\n position: 'absolute',\n top: 8,\n right: 8,\n zIndex: 1,\n display: 'flex',\n borderRadius: '50%',\n bgcolor: 'background.paper',\n color: 'primary.main',\n boxShadow: 2,\n pointerEvents: 'none',\n }}\n >\n <MdiIcon path={mdiCheckCircle.path} sx={{ fontSize: 24 }} />\n </Box>\n )\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. Each\n * card is a surface with an outline that reads on a gray page, its title in\n * the text color and its line in the secondary text color, both at AA on the\n * card (AGL-3985). The picked card is outlined 2px in the primary color and\n * carries a check; the arrow keys move between cards and pick, as a radio\n * group's do, only the picked card is in the tab order, and the focused card\n * is ringed.\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: 2,\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 key={option.id} variant=\"outlined\" sx={optionCardSx(selected)}>\n {selected && <OptionCardCheck />}\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 // Room for the check beside a card's words, where no picture carries it.\n pr: option.image ? 0 : 5,\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\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 )}\n <Stack\n direction=\"row\"\n spacing={1.5}\n sx={{ alignItems: 'flex-start', width: '100%', px: option.image ? 2 : 0, py: option.image ? 1.5 : 0 }}\n >\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\n variant=\"subtitle2\"\n component=\"span\"\n sx={{ display: 'block', color: 'text.primary', fontWeight: 600, lineHeight: 1.4 }}\n >\n {option.title}\n </Typography>\n {option.description && (\n <Typography variant=\"body2\" component=\"span\" sx={{ display: 'block', color: 'text.secondary', mt: 0.25 }}>\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","optionCardSx","selected","theme","position","borderRadius","bgcolor","borderColor","palette","primary","main","text","boxShadow","transition","transitions","create","outline","outlineOffset","OptionCardCheck","aria-hidden","data-option-card-check","sx","top","right","zIndex","display","color","pointerEvents","path","fontSize","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","gap","gridTemplateColumns","map","variant","ref","node","aria-checked","description","title","tabIndex","onClick","p","image","pr","height","minHeight","flexDirection","alignItems","justifyContent","component","src","alt","width","loading","decoding","aspectRatio","objectFit","objectPosition","borderBottom","direction","spacing","px","py","icon","pt","flex","minWidth","fontWeight","lineHeight","mt"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,cAAc,QAAQ,yBAAwB;AACvD,SAASC,GAAG,EAAEC,IAAI,EAAEC,cAAc,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAC5E,SAASC,KAAK,QAAoB,uBAAsB;AACxD,SAASC,MAAM,QAA4B,QAAO;AAClD,SAASC,OAAO,QAAQ,yBAAqB;AAuC7C;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,aAAaC,QAAiB;IAC5C,OAAO,CAACC,QAAkB,CAAA;YACxBC,UAAU;YACVC,cAAc;YACdC,SAAS;YACTC,aAAaL,WAAWC,MAAMK,OAAO,CAACC,OAAO,CAACC,IAAI,GAAGZ,MAAMK,MAAMK,OAAO,CAACG,IAAI,CAACF,OAAO,EAAE;YACvFG,WAAWV,WAAW,CAAC,UAAU,EAAEC,MAAMK,OAAO,CAACC,OAAO,CAACC,IAAI,EAAE,GAAG;YAClEG,YAAYV,MAAMW,WAAW,CAACC,MAAM,CAAC;gBAAC;gBAAgB;aAAa;YACnE,WAAW;gBAAER,aAAaL,WAAWC,MAAMK,OAAO,CAACC,OAAO,CAACC,IAAI,GAAGP,MAAMK,OAAO,CAACG,IAAI,CAACF,OAAO;YAAC;YAC7F,4BAA4B;gBAC1BO,SAAS,CAAC,UAAU,EAAEb,MAAMK,OAAO,CAACC,OAAO,CAACC,IAAI,EAAE;gBAClDO,eAAe;YACjB;QACF,CAAA;AACF;AAEA,8FAA8F,GAC9F,OAAO,SAASC;IACd,qBACE,KAACzB;QACC0B,aAAW;QACXC,0BAAuB;QACvBC,IAAI;YACFjB,UAAU;YACVkB,KAAK;YACLC,OAAO;YACPC,QAAQ;YACRC,SAAS;YACTpB,cAAc;YACdC,SAAS;YACToB,OAAO;YACPd,WAAW;YACXe,eAAe;QACjB;kBAEA,cAAA,KAAC3B;YAAQ4B,MAAMpC,eAAeoC,IAAI;YAAEP,IAAI;gBAAEQ,UAAU;YAAG;;;AAG7D;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASC,eAA0C,EAOjC;QAPiC,EACxDC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,QAAQ,EACRC,UAAU;QAAEC,IAAI;QAAGC,IAAI;QAAGC,IAAI;IAAE,CAAC,EAEV,OADpBC;;;;;;;QA8BkBJ;IA5BrB,MAAMK,OAAOzC,OAAwC,EAAE;IACvD,MAAM0C,gBAAgBT,QAAQU,SAAS,CAAC,CAACC,SAAWA,OAAOC,EAAE,KAAKX;IAClE,MAAMY,YAAYJ,kBAAkB,CAAC,IAAI,IAAIA;IAE7C,MAAMK,YAAY,CAACC,OAAsBC;YAUvCR;QATA,MAAMS,OACJF,MAAMG,GAAG,KAAK,gBAAgBH,MAAMG,GAAG,KAAK,cACxC,IACAH,MAAMG,GAAG,KAAK,eAAeH,MAAMG,GAAG,KAAK,YACzC,CAAC,IACD;QACR,IAAI,CAACD,MAAM;QACXF,MAAMI,cAAc;QACpB,MAAMC,OAAO,AAACJ,CAAAA,QAAQC,OAAOjB,QAAQqB,MAAM,AAAD,IAAKrB,QAAQqB,MAAM;SAC7Db,qBAAAA,KAAKc,OAAO,CAACF,KAAK,qBAAlBZ,mBAAoBe,KAAK;QACzBrB,SAASF,OAAO,CAACoB,KAAK,CAACR,EAAE;IAC3B;IAEA,MAAMY,WAAW,CAACC,QAAoBA,QAAQ,CAAC,OAAO,EAAEA,MAAM,iBAAiB,CAAC,GAAGC;IACnF,qBACE,KAACjE;QACCkE,MAAK;QACLC,cAAY7B;OACRQ;QACJlB,IAAI;YACFI,SAAS;YACToC,KAAK;YACLC,qBAAqB;gBACnB1B,IAAIoB,UAASrB,cAAAA,QAAQC,EAAE,YAAVD,cAAc;gBAC3BE,IAAImB,SAASrB,QAAQE,EAAE;gBACvBC,IAAIkB,SAASrB,QAAQG,EAAE;YACzB;QACF;kBAECN,QAAQ+B,GAAG,CAAC,CAACpB,QAAQK;YACpB,MAAM9C,WAAWyC,OAAOC,EAAE,KAAKX;YAC/B,qBACE,MAACvC;gBAAqBsE,SAAQ;gBAAW3C,IAAIpB,aAAaC;;oBACvDA,0BAAY,KAACgB;kCACd,MAACvB;wBACCsE,KAAK,CAACC;4BACJ1B,KAAKc,OAAO,CAACN,MAAM,GAAGkB;wBACxB;wBACAP,MAAK;wBACLQ,gBAAcjE;wBACd0D,cAAYjB,OAAOyB,WAAW,GAAG,GAAGzB,OAAO0B,KAAK,CAAC,EAAE,EAAE1B,OAAOyB,WAAW,EAAE,GAAGzB,OAAO0B,KAAK;wBACxFC,UAAUtB,UAAUH,YAAY,IAAI,CAAC;wBACrC0B,SAAS,IAAMrC,SAASS,OAAOC,EAAE;wBACjCE,WAAW,CAACC,QAAUD,UAAUC,OAAOC;wBACvC3B,IAAI;4BACFmD,GAAG7B,OAAO8B,KAAK,GAAG,IAAI;4BACtB,yEAAyE;4BACzEC,IAAI/B,OAAO8B,KAAK,GAAG,IAAI;4BACvBE,QAAQ;4BACRC,WAAW;4BACXnD,SAAS;4BACToD,eAAelC,OAAO8B,KAAK,GAAG,WAAW;4BACzCK,YAAYnC,OAAO8B,KAAK,GAAG,YAAY;4BACvCM,gBAAgB;wBAClB;;4BAECpC,OAAO8B,KAAK,kBACX,KAAChF;gCACCuF,WAAU;gCACVC,KAAKtC,OAAO8B,KAAK,CAACQ,GAAG;gCACrBC,KAAKvC,OAAO8B,KAAK,CAACS,GAAG;gCACrBC,OAAOxC,OAAO8B,KAAK,CAACU,KAAK;gCACzBR,QAAQhC,OAAO8B,KAAK,CAACE,MAAM;gCAC3BS,SAAQ;gCACRC,UAAS;gCACThE,IAAI;oCACFI,SAAS;oCACT0D,OAAO;oCACPR,QAAQ;oCACRW,aAAa,GAAG3C,OAAO8B,KAAK,CAACU,KAAK,CAAC,GAAG,EAAExC,OAAO8B,KAAK,CAACE,MAAM,EAAE;oCAC7DY,WAAW;oCACXC,gBAAgB;oCAChBC,cAAc;oCACdlF,aAAa;oCACbD,SAAS;gCACX;;0CAGJ,MAACV;gCACC8F,WAAU;gCACVC,SAAS;gCACTtE,IAAI;oCAAEyD,YAAY;oCAAcK,OAAO;oCAAQS,IAAIjD,OAAO8B,KAAK,GAAG,IAAI;oCAAGoB,IAAIlD,OAAO8B,KAAK,GAAG,MAAM;gCAAE;;oCAEnG9B,OAAOmD,IAAI,kBACV,KAACrG;wCAAI4B,IAAI;4CAAEK,OAAOxB,WAAW,iBAAiB;4CAAkBuB,SAAS;4CAAQsE,IAAI;wCAAK;wCAAG5E,aAAW;kDACtG,cAAA,KAACnB;4CAAQ4B,MAAMe,OAAOmD,IAAI;4CAAEzE,IAAI;gDAAEQ,UAAU;4CAAG;;;kDAGnD,MAACpC;wCAAI4B,IAAI;4CAAE2E,MAAM;4CAAGC,UAAU;wCAAE;;0DAC9B,KAACpG;gDACCmE,SAAQ;gDACRgB,WAAU;gDACV3D,IAAI;oDAAEI,SAAS;oDAASC,OAAO;oDAAgBwE,YAAY;oDAAKC,YAAY;gDAAI;0DAE/ExD,OAAO0B,KAAK;;4CAEd1B,OAAOyB,WAAW,kBACjB,KAACvE;gDAAWmE,SAAQ;gDAAQgB,WAAU;gDAAO3D,IAAI;oDAAEI,SAAS;oDAASC,OAAO;oDAAkB0E,IAAI;gDAAK;0DACpGzD,OAAOyB,WAAW;;;;;;;;;eAlEpBzB,OAAOC,EAAE;QA0ExB;;AAGN;AAEA,eAAed,eAAc"}
|
|
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, Chip, Stack, Typography, type ChipProps } 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/**\n * A short fact about a choice, drawn as a small chip under its line: a price,\n * who made it, how many pages it has. Theme colors only; the label is read\n * with the card's name, so a chip is never the only place a fact is said.\n */\nexport interface OptionCardChip {\n label: string\n /** A theme palette color; `default` when absent. */\n color?: ChipProps['color']\n /** `outlined` when absent. */\n variant?: 'filled' | 'outlined'\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 /** Facts drawn as chips under its line, in this order. */\n chips?: readonly OptionCardChip[]\n}\n\n/**\n * What a card is called for assistive technology: its title, its line after a\n * colon, and its chips in parentheses — every word the card shows, since the\n * card is one control and its name is all a screen reader says of it.\n */\nexport function optionCardLabel(option: Pick<OptionCard, 'title' | 'description' | 'chips'>): string {\n const named = option.description ? `${option.title}: ${option.description}` : option.title\n const chips = (option.chips ?? []).map((chip) => chip.label).filter(Boolean)\n return chips.length ? `${named} (${chips.join(', ')})` : named\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 * How an option card sits on the page around it (AGL-3985). A grid of them is\n * often drawn on a dialog's gray, where a transparent card with a divider\n * outline melted into the page, so each card is:\n *\n * - a surface of its own, `background.paper`;\n * - outlined as strongly as an outlined field (the text color at 23%, the\n * border MUI's OutlinedInput draws), the text color on hover;\n * - once picked, outlined 2px in the primary color, from a 1px ring laid\n * outside its 1px border so the grid never shifts, with a check in a disc\n * at its top corner ({@link OptionCardCheck});\n * - ringed in the primary color while it has the keyboard's focus.\n *\n * Theme values only, so it reads the same in light and dark mode.\n */\nexport function optionCardSx(selected: boolean) {\n return (theme: Theme) => ({\n position: 'relative' as const,\n borderRadius: 2,\n bgcolor: 'background.paper',\n borderColor: selected ? theme.palette.primary.main : alpha(theme.palette.text.primary, 0.23),\n boxShadow: selected ? `0 0 0 1px ${theme.palette.primary.main}` : 'none',\n transition: theme.transitions.create(['border-color', 'box-shadow']),\n '&:hover': { borderColor: selected ? theme.palette.primary.main : theme.palette.text.primary },\n '&:has(.Mui-focusVisible)': {\n outline: `2px solid ${theme.palette.primary.main}`,\n outlineOffset: '2px',\n },\n })\n}\n\n/** The check a picked card carries in its top corner: over its picture, or beside its words. */\nexport function OptionCardCheck() {\n return (\n <Box\n aria-hidden\n data-option-card-check=\"\"\n sx={{\n position: 'absolute',\n top: 8,\n right: 8,\n zIndex: 1,\n display: 'flex',\n borderRadius: '50%',\n bgcolor: 'background.paper',\n color: 'primary.main',\n boxShadow: 2,\n pointerEvents: 'none',\n }}\n >\n <MdiIcon path={mdiCheckCircle.path} sx={{ fontSize: 24 }} />\n </Box>\n )\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, a line under it and any\n * facts as chips under that (AGL-4049), which its accessible name reads too. Each\n * card is a surface with an outline that reads on a gray page, its title in\n * the text color and its line in the secondary text color, both at AA on the\n * card (AGL-3985). The picked card is outlined 2px in the primary color and\n * carries a check; the arrow keys move between cards and pick, as a radio\n * group's do, only the picked card is in the tab order, and the focused card\n * is ringed.\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: 2,\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 key={option.id} variant=\"outlined\" sx={optionCardSx(selected)}>\n {selected && <OptionCardCheck />}\n <CardActionArea\n ref={(node: HTMLButtonElement | null) => {\n refs.current[index] = node\n }}\n role=\"radio\"\n aria-checked={selected}\n aria-label={optionCardLabel(option)}\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 // Room for the check beside a card's words, where no picture carries it.\n pr: option.image ? 0 : 5,\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\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 )}\n <Stack\n direction=\"row\"\n spacing={1.5}\n sx={{ alignItems: 'flex-start', width: '100%', px: option.image ? 2 : 0, py: option.image ? 1.5 : 0 }}\n >\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\n variant=\"subtitle2\"\n component=\"span\"\n sx={{ display: 'block', color: 'text.primary', fontWeight: 600, lineHeight: 1.4 }}\n >\n {option.title}\n </Typography>\n {option.description && (\n <Typography variant=\"body2\" component=\"span\" sx={{ display: 'block', color: 'text.secondary', mt: 0.25 }}>\n {option.description}\n </Typography>\n )}\n {option.chips && option.chips.length > 0 && (\n <Stack\n component=\"span\"\n direction=\"row\"\n useFlexGap\n aria-hidden\n data-option-card-chips=\"\"\n sx={{ flexWrap: 'wrap', gap: 0.5, mt: 1 }}\n >\n {option.chips.map((chip, chipIndex) => (\n <Chip\n key={`${chipIndex}-${chip.label}`}\n component=\"span\"\n size=\"small\"\n label={chip.label}\n color={chip.color ?? 'default'}\n variant={chip.variant ?? 'outlined'}\n />\n ))}\n </Stack>\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","Chip","Stack","Typography","alpha","useRef","MdiIcon","optionCardLabel","option","named","description","title","chips","map","chip","label","filter","Boolean","length","join","optionCardSx","selected","theme","position","borderRadius","bgcolor","borderColor","palette","primary","main","text","boxShadow","transition","transitions","create","outline","outlineOffset","OptionCardCheck","aria-hidden","data-option-card-check","sx","top","right","zIndex","display","color","pointerEvents","path","fontSize","OptionCardGrid","options","value","onChange","columns","xs","sm","md","rest","refs","selectedIndex","findIndex","id","focusable","onKeyDown","event","index","step","key","preventDefault","next","current","focus","template","count","undefined","role","aria-label","gap","gridTemplateColumns","variant","ref","node","aria-checked","tabIndex","onClick","p","image","pr","height","minHeight","flexDirection","alignItems","justifyContent","component","src","alt","width","loading","decoding","aspectRatio","objectFit","objectPosition","borderBottom","direction","spacing","px","py","icon","pt","flex","minWidth","fontWeight","lineHeight","mt","useFlexGap","data-option-card-chips","flexWrap","chipIndex","size"],"mappings":"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"}
|
|
@@ -23,7 +23,11 @@ export interface SiteLiveNoticeProps {
|
|
|
23
23
|
pagesHref: string | null;
|
|
24
24
|
/** The heading's level, for the page or dialog it sits in. */
|
|
25
25
|
headingComponent?: 'h1' | 'h2' | 'h3';
|
|
26
|
+
/** The starter the site was made from, when it was made from one, named under the heading. */
|
|
27
|
+
starterName?: string | null;
|
|
26
28
|
}
|
|
29
|
+
/** The line that names the starter a site was made from. */
|
|
30
|
+
export declare const siteLiveNoticeStarterLine: (name: string) => string;
|
|
27
31
|
/**
|
|
28
32
|
* What a person sees the moment their site goes live: that it is live, the
|
|
29
33
|
* address visitors use, and the two things to do next — look at it, or edit
|
|
@@ -35,5 +39,5 @@ export interface SiteLiveNoticeProps {
|
|
|
35
39
|
* it is chosen, and a person who was never told read their analytics window
|
|
36
40
|
* ("14 days") as a countdown to launch.
|
|
37
41
|
*/
|
|
38
|
-
export declare function SiteLiveNotice({ liveUrl, pagesHref, headingComponent }: SiteLiveNoticeProps): import("react").JSX.Element;
|
|
42
|
+
export declare function SiteLiveNotice({ liveUrl, pagesHref, headingComponent, starterName }: SiteLiveNoticeProps): import("react").JSX.Element;
|
|
39
43
|
export default SiteLiveNotice;
|
|
@@ -22,6 +22,7 @@ import Typography from "@mui/material/Typography";
|
|
|
22
22
|
import AppLink from "./app-link.js";
|
|
23
23
|
export const SITE_LIVE_NOTICE_TITLE = 'Your site is live';
|
|
24
24
|
export const SITE_LIVE_NOTICE_BODY = 'Your pages are published, and anyone can visit your site now.';
|
|
25
|
+
/** The line that names the starter a site was made from. */ export const siteLiveNoticeStarterLine = (name)=>`Made from the starter "${name}". Change anything under Pages and Setup → Theme.`;
|
|
25
26
|
/**
|
|
26
27
|
* What a person sees the moment their site goes live: that it is live, the
|
|
27
28
|
* address visitors use, and the two things to do next — look at it, or edit
|
|
@@ -32,7 +33,7 @@ export const SITE_LIVE_NOTICE_BODY = 'Your pages are published, and anyone can v
|
|
|
32
33
|
* It exists because nothing said so. A starter site is published the moment
|
|
33
34
|
* it is chosen, and a person who was never told read their analytics window
|
|
34
35
|
* ("14 days") as a countdown to launch.
|
|
35
|
-
*/ export function SiteLiveNotice({ liveUrl, pagesHref, headingComponent = 'h2' }) {
|
|
36
|
+
*/ export function SiteLiveNotice({ liveUrl, pagesHref, headingComponent = 'h2', starterName = null }) {
|
|
36
37
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
37
38
|
spacing: 2,
|
|
38
39
|
children: [
|
|
@@ -49,6 +50,11 @@ export const SITE_LIVE_NOTICE_BODY = 'Your pages are published, and anyone can v
|
|
|
49
50
|
color: "text.secondary",
|
|
50
51
|
children: SITE_LIVE_NOTICE_BODY
|
|
51
52
|
}),
|
|
53
|
+
starterName ? /*#__PURE__*/ _jsx(Typography, {
|
|
54
|
+
variant: "body2",
|
|
55
|
+
color: "text.secondary",
|
|
56
|
+
children: siteLiveNoticeStarterLine(starterName)
|
|
57
|
+
}) : null,
|
|
52
58
|
liveUrl ? /*#__PURE__*/ _jsx(Link, {
|
|
53
59
|
href: liveUrl,
|
|
54
60
|
target: "_blank",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/site-live-notice.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\n'use client'\n\nimport Button from '@mui/material/Button'\nimport Link from '@mui/material/Link'\nimport Stack from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport AppLink from './app-link'\n\nexport const SITE_LIVE_NOTICE_TITLE = 'Your site is live'\nexport const SITE_LIVE_NOTICE_BODY = 'Your pages are published, and anyone can visit your site now.'\n\nexport interface SiteLiveNoticeProps {\n /** The site's public address; without one the notice says it is live and offers only the pages. */\n liveUrl: string | null\n /** The console's page list for the site, where the pages are edited. */\n pagesHref: string | null\n /** The heading's level, for the page or dialog it sits in. */\n headingComponent?: 'h1' | 'h2' | 'h3'\n}\n\n/**\n * What a person sees the moment their site goes live: that it is live, the\n * address visitors use, and the two things to do next — look at it, or edit\n * its pages. Shared by every path that publishes a new site (the starter, the\n * guided AI start) so a site goes live in the same words whichever way it\n * was made.\n *\n * It exists because nothing said so. A starter site is published the moment\n * it is chosen, and a person who was never told read their analytics window\n * (\"14 days\") as a countdown to launch.\n */\nexport function SiteLiveNotice({ liveUrl, pagesHref, headingComponent = 'h2' }: SiteLiveNoticeProps) {\n return (\n <Stack spacing={2}>\n <Stack spacing={0.5}>\n <Typography variant=\"h5\" component={headingComponent}>\n {SITE_LIVE_NOTICE_TITLE}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {SITE_LIVE_NOTICE_BODY}\n </Typography>\n {liveUrl ? (\n <Link href={liveUrl} target=\"_blank\" rel=\"noopener noreferrer\" variant=\"body1\" sx={{ wordBreak: 'break-all' }}>\n {liveUrl.replace(/^https?:\\/\\//, '')}\n </Link>\n ) : null}\n </Stack>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n {liveUrl ? (\n // The live site, in a tab of its own: the console stays where it is.\n <Button variant=\"contained\" size=\"large\" component=\"a\" href={liveUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n {'View your site'}\n </Button>\n ) : null}\n {pagesHref ? (\n <Button variant={liveUrl ? 'outlined' : 'contained'} size=\"large\" component={AppLink} href={pagesHref}>\n {'Edit your pages'}\n </Button>\n ) : null}\n </Stack>\n </Stack>\n )\n}\n\nexport default SiteLiveNotice\n"],"names":["Button","Link","Stack","Typography","AppLink","SITE_LIVE_NOTICE_TITLE","SITE_LIVE_NOTICE_BODY","SiteLiveNotice","liveUrl","pagesHref","headingComponent","spacing","variant","component","color","href","target","rel","sx","wordBreak","replace","direction","xs","sm","size"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,OAAOA,YAAY,uBAAsB;AACzC,OAAOC,UAAU,qBAAoB;AACrC,OAAOC,WAAW,sBAAqB;AACvC,OAAOC,gBAAgB,2BAA0B;AACjD,OAAOC,aAAa,gBAAY;AAEhC,OAAO,MAAMC,yBAAyB,oBAAmB;AACzD,OAAO,MAAMC,wBAAwB,gEAA+D;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/site-live-notice.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\n'use client'\n\nimport Button from '@mui/material/Button'\nimport Link from '@mui/material/Link'\nimport Stack from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport AppLink from './app-link'\n\nexport const SITE_LIVE_NOTICE_TITLE = 'Your site is live'\nexport const SITE_LIVE_NOTICE_BODY = 'Your pages are published, and anyone can visit your site now.'\n\nexport interface SiteLiveNoticeProps {\n /** The site's public address; without one the notice says it is live and offers only the pages. */\n liveUrl: string | null\n /** The console's page list for the site, where the pages are edited. */\n pagesHref: string | null\n /** The heading's level, for the page or dialog it sits in. */\n headingComponent?: 'h1' | 'h2' | 'h3'\n /** The starter the site was made from, when it was made from one, named under the heading. */\n starterName?: string | null\n}\n\n/** The line that names the starter a site was made from. */\nexport const siteLiveNoticeStarterLine = (name: string): string =>\n `Made from the starter \"${name}\". Change anything under Pages and Setup → Theme.`\n\n/**\n * What a person sees the moment their site goes live: that it is live, the\n * address visitors use, and the two things to do next — look at it, or edit\n * its pages. Shared by every path that publishes a new site (the starter, the\n * guided AI start) so a site goes live in the same words whichever way it\n * was made.\n *\n * It exists because nothing said so. A starter site is published the moment\n * it is chosen, and a person who was never told read their analytics window\n * (\"14 days\") as a countdown to launch.\n */\nexport function SiteLiveNotice({ liveUrl, pagesHref, headingComponent = 'h2', starterName = null }: SiteLiveNoticeProps) {\n return (\n <Stack spacing={2}>\n <Stack spacing={0.5}>\n <Typography variant=\"h5\" component={headingComponent}>\n {SITE_LIVE_NOTICE_TITLE}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {SITE_LIVE_NOTICE_BODY}\n </Typography>\n {starterName ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {siteLiveNoticeStarterLine(starterName)}\n </Typography>\n ) : null}\n {liveUrl ? (\n <Link href={liveUrl} target=\"_blank\" rel=\"noopener noreferrer\" variant=\"body1\" sx={{ wordBreak: 'break-all' }}>\n {liveUrl.replace(/^https?:\\/\\//, '')}\n </Link>\n ) : null}\n </Stack>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n {liveUrl ? (\n // The live site, in a tab of its own: the console stays where it is.\n <Button variant=\"contained\" size=\"large\" component=\"a\" href={liveUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n {'View your site'}\n </Button>\n ) : null}\n {pagesHref ? (\n <Button variant={liveUrl ? 'outlined' : 'contained'} size=\"large\" component={AppLink} href={pagesHref}>\n {'Edit your pages'}\n </Button>\n ) : null}\n </Stack>\n </Stack>\n )\n}\n\nexport default SiteLiveNotice\n"],"names":["Button","Link","Stack","Typography","AppLink","SITE_LIVE_NOTICE_TITLE","SITE_LIVE_NOTICE_BODY","siteLiveNoticeStarterLine","name","SiteLiveNotice","liveUrl","pagesHref","headingComponent","starterName","spacing","variant","component","color","href","target","rel","sx","wordBreak","replace","direction","xs","sm","size"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,OAAOA,YAAY,uBAAsB;AACzC,OAAOC,UAAU,qBAAoB;AACrC,OAAOC,WAAW,sBAAqB;AACvC,OAAOC,gBAAgB,2BAA0B;AACjD,OAAOC,aAAa,gBAAY;AAEhC,OAAO,MAAMC,yBAAyB,oBAAmB;AACzD,OAAO,MAAMC,wBAAwB,gEAA+D;AAapG,0DAA0D,GAC1D,OAAO,MAAMC,4BAA4B,CAACC,OACxC,CAAC,uBAAuB,EAAEA,KAAK,iDAAiD,CAAC,CAAA;AAEnF;;;;;;;;;;CAUC,GACD,OAAO,SAASC,eAAe,EAAEC,OAAO,EAAEC,SAAS,EAAEC,mBAAmB,IAAI,EAAEC,cAAc,IAAI,EAAuB;IACrH,qBACE,MAACX;QAAMY,SAAS;;0BACd,MAACZ;gBAAMY,SAAS;;kCACd,KAACX;wBAAWY,SAAQ;wBAAKC,WAAWJ;kCACjCP;;kCAEH,KAACF;wBAAWY,SAAQ;wBAAQE,OAAM;kCAC/BX;;oBAEFO,4BACC,KAACV;wBAAWY,SAAQ;wBAAQE,OAAM;kCAC/BV,0BAA0BM;yBAE3B;oBACHH,wBACC,KAACT;wBAAKiB,MAAMR;wBAASS,QAAO;wBAASC,KAAI;wBAAsBL,SAAQ;wBAAQM,IAAI;4BAAEC,WAAW;wBAAY;kCACzGZ,QAAQa,OAAO,CAAC,gBAAgB;yBAEjC;;;0BAEN,MAACrB;gBAAMsB,WAAW;oBAAEC,IAAI;oBAAUC,IAAI;gBAAM;gBAAGZ,SAAS;;oBACrDJ,UACC,qEAAqE;kCACrE,KAACV;wBAAOe,SAAQ;wBAAYY,MAAK;wBAAQX,WAAU;wBAAIE,MAAMR;wBAASS,QAAO;wBAASC,KAAI;kCACvF;yBAED;oBACHT,0BACC,KAACX;wBAAOe,SAASL,UAAU,aAAa;wBAAaiB,MAAK;wBAAQX,WAAWZ;wBAASc,MAAMP;kCACzF;yBAED;;;;;AAIZ;AAEA,eAAeF,eAAc"}
|