@pihanga2/shadcn 0.1.0 → 0.2.0
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/AGENT.building-cards.md +413 -0
- package/AGENT.md +50 -0
- package/AGENT.using-cards.md +1242 -0
- package/cards/box/box.types.d.ts +1 -4
- package/cards/box/box.types.js.map +1 -1
- package/cards/core-index.js +6 -6
- package/cards/fileDrop/fileDrop.component.d.ts +5 -0
- package/cards/fileDrop/fileDrop.types.d.ts +43 -0
- package/cards/fileDrop/index.d.ts +2 -0
- package/cards/icons.js +7 -3
- package/cards/icons.js.map +1 -1
- package/cards/index.d.ts +1 -0
- package/cards/stack/stack.types.d.ts +19 -22
- package/cards/stack/stack.types.js +1 -1
- package/cards/stack/stack.types.js.map +1 -1
- package/cards/tabs/index.js +4 -4
- package/cards/tabs/tabs.types.d.ts +2 -0
- package/cards/tabs/tabs.types.js +2 -2
- package/cards/tabs/tabs.types.js.map +1 -1
- package/cards/types.d.ts +4 -0
- package/cards/types.js.map +1 -1
- package/package.json +9 -1
- package/pihanga-shadcn.css +1 -1
- package/mountain-snow.svg +0 -4
- package/r/badge.json +0 -35
- package/r/box.json +0 -26
- package/r/button.json +0 -39
- package/r/checkbox.json +0 -31
- package/r/conditional.json +0 -30
- package/r/dataTable.json +0 -43
- package/r/dialog.json +0 -36
- package/r/dropDownMenu.json +0 -47
- package/r/field.json +0 -35
- package/r/flexGrid.json +0 -32
- package/r/form.json +0 -37
- package/r/framework.json +0 -28
- package/r/graphin.json +0 -42
- package/r/input.json +0 -35
- package/r/jsonViewer.json +0 -40
- package/r/list.json +0 -39
- package/r/loadingOverlay.json +0 -39
- package/r/loadingSkeleton.json +0 -30
- package/r/markdownViewer.json +0 -51
- package/r/menu.json +0 -34
- package/r/modeToggle.json +0 -36
- package/r/navbarSearch.json +0 -31
- package/r/pageWithNavbar.json +0 -46
- package/r/pasteTarget.json +0 -41
- package/r/pihanga-base.json +0 -11
- package/r/pihanga-cards-icons.json +0 -16
- package/r/pihanga-cards-types.json +0 -16
- package/r/pihanga-hook-use-is-touch-device.json +0 -15
- package/r/pihanga-lib-utils.json +0 -18
- package/r/pihanga-theme-provider.json +0 -25
- package/r/pihanga-ui-extras.json +0 -68
- package/r/registry.json +0 -171
- package/r/resizable.json +0 -35
- package/r/select.json +0 -35
- package/r/stack.json +0 -27
- package/r/stepper.json +0 -39
- package/r/switch.json +0 -36
- package/r/tabs.json +0 -33
- package/r/textField.json +0 -32
- package/r/toast.json +0 -32
- package/r/toggleGroup.json +0 -35
- package/r/typography.json +0 -31
package/r/jsonViewer.json
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "jsonViewer",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga jsonViewer card component",
|
|
6
|
-
"dependencies": [
|
|
7
|
-
"lucide-react@^0.513.0",
|
|
8
|
-
"react-json-view-lite@^2.5.0"
|
|
9
|
-
],
|
|
10
|
-
"registryDependencies": [
|
|
11
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
12
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-cards-icons.json"
|
|
13
|
-
],
|
|
14
|
-
"files": [
|
|
15
|
-
{
|
|
16
|
-
"path": "cards/jsonViewer/index.ts",
|
|
17
|
-
"content": "import {registerCardComponent, actionTypesToEvents} from \"@pihanga2/core\";\n\nimport {JSON_VIEWER_CARD, JSON_VIEWER_ACTION} from \"./jsonViewer.types\";\nimport {JsonViewerComponent} from \"./jsonViewer.component\";\n\nexport * from \"./jsonViewer.types\";\n\nregisterCardComponent({\n name: JSON_VIEWER_CARD,\n component: JsonViewerComponent,\n events: actionTypesToEvents(JSON_VIEWER_ACTION),\n});\n",
|
|
18
|
-
"type": "registry:component",
|
|
19
|
-
"target": "src/cards/jsonViewer/index.ts"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"path": "cards/jsonViewer/jsonViewer.component.tsx",
|
|
23
|
-
"content": "import React, {useLayoutEffect, useRef, useState, useCallback} from \"react\";\nimport {PiCardProps} from \"@pihanga2/core\";\nimport {JsonView, allExpanded, defaultStyles} from \"react-json-view-lite\";\nimport {Copy, Check} from \"lucide-react\";\nimport \"react-json-view-lite/dist/index.css\";\nimport \"./jsonViewer.css\";\n\nimport {getIconElement} from \"@/cards/icons\";\nimport type {JsonViewerProps, JsonViewerEvents} from \"./jsonViewer.types\";\n\n/**\n * Converts the `collapsed` prop (from the react-json-view API) into a\n * `shouldExpandNode` predicate for react-json-view-lite.\n *\n * - `false` → expand everything\n * - `true` → collapse everything\n * - number N → expand nodes at depth < N (root is depth 0)\n */\nfunction toShouldExpandNode(\n collapsed: boolean | number | undefined,\n): (level: number) => boolean {\n if (collapsed === false) return allExpanded;\n if (collapsed === true) return () => false;\n // default: collapsed = 1 → only root level expanded\n const depth = collapsed ?? 1;\n return (level: number) => level < depth;\n}\n\n/**\n * Compact, code-editor style for react-json-view-lite.\n *\n * The expand/collapse icon is positioned absolutely inside a fixed left gutter\n * (via padding-left on `.jv-basic-child`), so the opening `{` and closing `}`\n * always start at the same column — fixing the original misalignment where the\n * icon pushed the open-brace further right than the close-brace.\n */\nconst compactStyles = {\n // spread defaultStyles first to satisfy required fields (ariaLables, stringifyStringValues, …)\n ...defaultStyles,\n container: \"jv-container\",\n basicChildStyle: \"jv-basic-child\",\n label: \"jv-label\",\n clickableLabel: \"jv-clickable-label\",\n nullValue: \"jv-null\",\n undefinedValue: \"jv-undefined\",\n numberValue: \"jv-number\",\n stringValue: \"jv-string\",\n booleanValue: \"jv-boolean\",\n otherValue: \"jv-other\",\n punctuation: \"jv-punctuation\",\n expandIcon: \"jv-expand-icon\",\n collapseIcon: \"jv-collapse-icon\",\n collapsedContent: \"jv-collapsed-content\",\n childFieldsContainer: \"jv-child-fields-container\",\n};\n\n/**\n * Resolve the icon element to use for the copy button.\n *\n * Resolution order:\n * 1. `iconName` looked up in the global icon registry\n * 2. Lucide `Copy` as the built-in fallback\n */\nfunction resolveCopyIcon(iconName?: string): React.ElementType {\n if (iconName) {\n const registered = getIconElement(iconName);\n if (registered) return registered;\n }\n return Copy;\n}\n\n/** Write `text` to the clipboard, preferring `ClipboardItem` for explicit MIME. */\nasync function writeToClipboard(text: string): Promise<void> {\n if (typeof ClipboardItem !== \"undefined\") {\n const blob = new Blob([text], {type: \"text/plain\"});\n await navigator.clipboard.write([new ClipboardItem({\"text/plain\": blob})]);\n } else {\n // Fallback for browsers without ClipboardItem support\n await navigator.clipboard.writeText(text);\n }\n}\n\nexport const JsonViewerComponent = (\n props: PiCardProps<JsonViewerProps, JsonViewerEvents>,\n): React.ReactNode => {\n const {\n source,\n collapsed = 1,\n modifyFn,\n style,\n className,\n cardName,\n _cls,\n copyToClipboard,\n copyIcon,\n onCopied,\n } = props;\n\n const elRef = useRef<HTMLDivElement>(null);\n const [copied, setCopied] = useState(false);\n\n useLayoutEffect(() => {\n if (modifyFn) {\n modifyFn(source, elRef.current);\n }\n }, [modifyFn, source]);\n\n const shouldExpandNode = toShouldExpandNode(collapsed);\n\n const handleCopy = useCallback(async () => {\n const text = JSON.stringify(source, null, 2);\n let success = false;\n try {\n await writeToClipboard(text);\n success = true;\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.warn(\"[JsonViewer] clipboard write failed:\", err);\n }\n onCopied?.({success});\n }, [source, onCopied]);\n\n const CopyIconEl = resolveCopyIcon(copyIcon);\n const ConfirmIconEl = Check;\n\n return (\n <div\n className={[_cls(\"root\", className), \"jv-wrapper\"].join(\" \")}\n ref={elRef}\n data-pihanga={cardName}\n style={style as React.CSSProperties | undefined}\n >\n {copyToClipboard && (\n <button\n type=\"button\"\n aria-label={copied ? \"Copied!\" : \"Copy JSON to clipboard\"}\n title={copied ? \"Copied!\" : \"Copy JSON to clipboard\"}\n className={`jv-copy-btn${copied ? \" jv-copy-btn--done\" : \"\"}`}\n onClick={handleCopy}\n >\n {copied ? (\n <ConfirmIconEl size={13} strokeWidth={2.5} />\n ) : (\n <CopyIconEl size={13} strokeWidth={2} />\n )}\n </button>\n )}\n <JsonView\n data={source as object | unknown[]}\n shouldExpandNode={shouldExpandNode}\n style={compactStyles}\n clickToExpandNode\n />\n </div>\n );\n};\n",
|
|
24
|
-
"type": "registry:component",
|
|
25
|
-
"target": "src/cards/jsonViewer/jsonViewer.component.tsx"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"path": "cards/jsonViewer/jsonViewer.css",
|
|
29
|
-
"content": "/*\n * jsonViewer.css — compact, code-editor style for react-json-view-lite\n *\n * Key design goals (matching the \"second screenshot\" style):\n * - Monospace font, light weight (no bold keys)\n * - Vertical guide lines via border-left on child containers\n * - Toggle icon is absolutely positioned so { and } align on the same column\n * - String values in green, numbers in blue, booleans in teal\n * - Adapts to light/dark via CSS variables\n */\n\n/* ── Wrapper (positions the copy button relative to the viewer) ─────────────── */\n.jv-wrapper {\n position: relative;\n}\n\n/* ── Copy-to-clipboard button ───────────────────────────────────────────────── */\n.jv-copy-btn {\n position: absolute;\n top: 4px;\n right: 4px;\n z-index: 10;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: 1px solid transparent;\n border-radius: 4px;\n background: transparent;\n color: hsl(var(--muted-foreground, 0 0% 55%));\n cursor: pointer;\n opacity: 0.35;\n transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease,\n border-color 0.15s ease;\n}\n\n/* Full opacity when the wrapper is hovered/focused-within */\n.jv-wrapper:hover .jv-copy-btn,\n.jv-wrapper:focus-within .jv-copy-btn {\n opacity: 1;\n}\n\n.jv-copy-btn:hover {\n color: hsl(var(--foreground));\n background: hsl(var(--muted, 0 0% 96%));\n border-color: hsl(var(--border, 0 0% 88%));\n}\n\n/* \"Done\" state — green check mark */\n.jv-copy-btn--done {\n opacity: 1;\n color: #2e7d32;\n}\n\n.dark .jv-copy-btn--done {\n color: #81c784;\n}\n\n/* ── Container ─────────────────────────────────────────────────────────────── */\n.jv-container {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace;\n font-size: 12.5px;\n font-weight: 400;\n line-height: 1.55;\n background: transparent;\n white-space: pre-wrap;\n word-wrap: break-word;\n color: var(--foreground);\n}\n\n/* ── Each row ───────────────────────────────────────────────────────────────── */\n/*\n * position: relative + padding-left reserves a fixed gutter on the LEFT of\n * every row (both the \"key: {\" opening row AND the bare \"}\" closing row).\n * The toggle icon is then placed absolutely inside that gutter so it never\n * displaces the brace characters — fixing the original misalignment.\n */\n.jv-basic-child {\n margin: 0;\n padding: 0 0 0 1.1em;\n position: relative;\n display: block;\n}\n\n/* ── Key labels ─────────────────────────────────────────────────────────────── */\n.jv-label {\n font-weight: 400;\n color: var(--foreground);\n margin-right: 0;\n}\n\n.jv-clickable-label {\n font-weight: 400;\n color: var(--foreground);\n cursor: pointer;\n margin-right: 0;\n}\n\n.jv-clickable-label:hover {\n text-decoration: underline;\n text-decoration-style: dotted;\n}\n\n/* ── Punctuation { } [ ] : , ──────────────────────────────────────────────── */\n.jv-punctuation {\n color: var(--foreground);\n font-weight: 400;\n}\n\n/* ── Toggle icons — positioned in the left gutter ──────────────────────────── */\n.jv-expand-icon,\n.jv-collapse-icon {\n position: absolute;\n left: 0;\n /*\n * With `transform: translateY(-50%)`, `top` sets the CENTRE of the element.\n * The visual centre of the first text line = line-height / 2 = 1.55 / 2 ≈ 0.775em.\n */\n top: 0.775em;\n transform: translateY(-50%);\n width: 1em;\n font-size: 0.65em;\n line-height: 1;\n color: hsl(var(--muted-foreground, 0 0% 55%));\n user-select: none;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.jv-expand-icon::after {\n content: \"▶\";\n}\n\n.jv-collapse-icon::after {\n content: \"▼\";\n}\n\n/* ── Collapsed placeholder ──────────────────────────────────────────────────── */\n.jv-collapsed-content {\n cursor: pointer;\n color: hsl(var(--muted-foreground, 0 0% 55%));\n}\n\n.jv-collapsed-content::after {\n content: \" …\";\n}\n\n/* ── Child-fields container — the vertical guide line lives here ────────────── */\n.jv-child-fields-container {\n margin: 0;\n padding: 0;\n padding-left: 1.25em;\n border-left: 1px solid hsl(var(--border, 0 0% 88%));\n margin-left: 0.3em;\n}\n\n/* ── Value colours ──────────────────────────────────────────────────────────── */\n.jv-null {\n color: #c0392b;\n}\n\n.jv-undefined {\n color: #c0392b;\n}\n\n.jv-string {\n color: #2e7d32;\n}\n\n/* green — matches screenshot */\n.jv-number {\n color: #1565c0;\n}\n\n/* blue */\n.jv-boolean {\n color: #00796b;\n}\n\n/* teal */\n.jv-other {\n color: var(--foreground);\n}\n\n/* ── Dark-mode overrides ───────────────────────────────────────────────────── */\n.dark .jv-string {\n color: #81c784;\n}\n\n.dark .jv-number {\n color: #64b5f6;\n}\n\n.dark .jv-boolean {\n color: #4db6ac;\n}\n\n.dark .jv-null,\n.dark .jv-undefined {\n color: #ef9a9a;\n}\n\n.dark .jv-child-fields-container {\n border-left-color: hsl(var(--border, 0 0% 25%));\n}",
|
|
30
|
-
"type": "registry:component",
|
|
31
|
-
"target": "src/cards/jsonViewer/jsonViewer.css"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"path": "cards/jsonViewer/jsonViewer.types.ts",
|
|
35
|
-
"content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const JSON_VIEWER_CARD = \"json-viewer\";\n\nexport const JsonViewer = createCardDeclaration<\n JsonViewerProps,\n JsonViewerEvents\n>(JSON_VIEWER_CARD);\n\nexport const JSON_VIEWER_ACTION = registerActions(JSON_VIEWER_CARD, [\n \"clicked\",\n \"copied\",\n]);\n\nexport const onJsonViewerClicked = createOnAction<ClickedEvent>(\n JSON_VIEWER_ACTION.CLICKED,\n);\n\nexport const onJsonViewerCopied = createOnAction<CopiedEvent>(\n JSON_VIEWER_ACTION.COPIED,\n);\n\nexport type JsonViewerProps<S = object> = {\n /** The JSON value to display. */\n source: unknown;\n /**\n * Theme name. Maps loosely to a visual style.\n * Use \"monokai\", \"solarized\", \"ocean\", etc. (visual only — actual theming\n * is done via className / global CSS since react-json-view-lite uses CSS\n * variables rather than named themes).\n */\n theme?: string;\n iconStyle?: \"circle\" | \"triangle\" | \"square\";\n /**\n * When `true`, all nodes are collapsed.\n * When `false`, all nodes are expanded.\n * When a number N, nodes at depth >= N are collapsed (root is depth 0).\n * Default: 1 (expand root only).\n */\n collapsed?: boolean | number;\n /** When `true`, a click-to-expand interaction is enabled on node labels. */\n enableClipboard?: boolean;\n /** When `true`, removes quotes from object keys in display. */\n removeQuotesOnKeys?: boolean;\n /** When `true`, objects and arrays are labelled with their size. */\n displayObjectSize?: boolean;\n /** When `true`, data-type labels prefix values. */\n displayDataTypes?: boolean;\n /**\n * Called after the JSON is rendered, allowing post-render DOM modifications.\n * Receives the raw `source` value and the container element.\n */\n modifyFn?: ModifyFn;\n\n /**\n * When `true`, shows a copy-to-clipboard icon button in the top-right\n * corner of the viewer. Clicking it copies the pretty-printed JSON to the\n * clipboard using `ClipboardItem` with `text/plain` MIME type.\n */\n copyToClipboard?: boolean;\n\n /**\n * Icon name resolved via the global icon registry (`getIconElement`).\n * If omitted or the name is not registered, falls back to the Lucide\n * `Copy` icon automatically.\n *\n * @example\n * // Register your own icon first:\n * registerIcon(\"my-copy\", MyCustomCopyIcon);\n * // Then pass the name to the card:\n * JsonViewer({ copyToClipboard: true, copyIcon: \"my-copy\", ... })\n */\n copyIcon?: string;\n\n /** Inline style for the react-json-view-lite container. */\n style?: S;\n /** Additional CSS class for the outer wrapper. */\n className?: string;\n};\n\nexport type ModifyFn = (source: unknown, el: HTMLElement | null) => void;\n\nexport type ClickedEvent = Record<string, never>;\n\nexport type CopiedEvent = {\n /** `true` when the clipboard write succeeded, `false` on error. */\n success: boolean;\n};\n\nexport type JsonViewerEvents = {\n onClicked: ClickedEvent;\n onCopied: CopiedEvent;\n};\n",
|
|
36
|
-
"type": "registry:component",
|
|
37
|
-
"target": "src/cards/jsonViewer/jsonViewer.types.ts"
|
|
38
|
-
}
|
|
39
|
-
]
|
|
40
|
-
}
|
package/r/list.json
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "list",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga list card component",
|
|
6
|
-
"dependencies": [
|
|
7
|
-
"@radix-ui/react-slot@^1.2.4",
|
|
8
|
-
"class-variance-authority@^0.7.1",
|
|
9
|
-
"lucide-react@^0.513.0",
|
|
10
|
-
"radix-ui@^1.4.3"
|
|
11
|
-
],
|
|
12
|
-
"registryDependencies": [
|
|
13
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
14
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-lib-utils.json",
|
|
15
|
-
"avatar",
|
|
16
|
-
"badge",
|
|
17
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-cards-icons.json"
|
|
18
|
-
],
|
|
19
|
-
"files": [
|
|
20
|
-
{
|
|
21
|
-
"path": "cards/list/index.ts",
|
|
22
|
-
"content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {ListComponent} from \"./list.component\";\nimport {LIST_ACTION, LIST_CARD} from \"./list.types\";\n\nexport * from \"./list.types\";\n\nregisterCardComponent({\n name: LIST_CARD,\n component: ListComponent,\n events: actionTypesToEvents(LIST_ACTION),\n});\n",
|
|
23
|
-
"type": "registry:component",
|
|
24
|
-
"target": "src/cards/list/index.ts"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"path": "cards/list/list.component.tsx",
|
|
28
|
-
"content": "import * as React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {ChevronDown} from \"lucide-react\";\nimport {cn} from \"@/lib/utils\";\nimport {Avatar, AvatarFallback, AvatarImage} from \"@/components/ui/avatar\";\nimport {Badge} from \"@/components/ui/badge\";\nimport {getIcon} from \"@/cards/icons\";\nimport type {DecoratorT, ListEvents, ListItem, ListProps} from \"./list.types\";\n\n// ---------------------------------------------------------------------------\n// Size tokens\n// ---------------------------------------------------------------------------\n\n/** Padding + font-size + gap for item buttons */\nconst ITEM_SIZE: Record<\"sm\" | \"md\" | \"lg\", string> = {\n sm: \"px-2 py-1 text-xs gap-1.5\",\n md: \"px-2 py-1.5 text-sm gap-2\",\n lg: \"px-3 py-2 text-base gap-2.5\",\n};\n\n/** Sub-title font-size (one step smaller than title) */\nconst SUB_SIZE: Record<\"sm\" | \"md\" | \"lg\", string> = {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n};\n\n/** Avatar size per density */\nconst AVATAR_SIZE: Record<\"sm\" | \"md\" | \"lg\", string> = {\n sm: \"size-5\",\n md: \"size-6\",\n lg: \"size-7\",\n};\n\n// ---------------------------------------------------------------------------\n// Decorator renderer\n// ---------------------------------------------------------------------------\n\nfunction renderDecorator(\n decorator: DecoratorT | undefined,\n size: \"sm\" | \"md\" | \"lg\",\n parentCard: string,\n): React.ReactNode {\n if (!decorator) return null;\n\n switch (decorator.type) {\n case \"icon\":\n return (\n <span\n className={cn(\n \"flex shrink-0 items-center [&>svg]:size-4\",\n decorator.className,\n )}\n >\n {getIcon(decorator.name)}\n </span>\n );\n\n case \"avatar\":\n return (\n <Avatar className={cn(AVATAR_SIZE[size], decorator.className)}>\n <AvatarImage src={decorator.src} />\n <AvatarFallback>{decorator.fallback ?? \"\"}</AvatarFallback>\n </Avatar>\n );\n\n case \"chip\":\n return (\n <Badge variant=\"secondary\" className={decorator.className}>\n {decorator.text}\n </Badge>\n );\n\n case \"card\":\n return <Card cardName={decorator.cardName} parentCard={parentCard} />;\n\n default:\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Single item row (recursive for nested)\n// ---------------------------------------------------------------------------\n\ntype ItemRowProps = {\n item: ListItem;\n size: \"sm\" | \"md\" | \"lg\";\n onItemClicked: (ev: {itemID: string | number}) => void;\n cardName: string;\n};\n\nfunction ItemRow({\n item,\n size,\n onItemClicked,\n cardName,\n}: ItemRowProps): React.ReactNode {\n const [open, setOpen] = React.useState(false);\n const hasNested = Boolean(item.nested?.length);\n\n function handleClick() {\n if (hasNested) {\n setOpen((prev) => !prev);\n } else {\n onItemClicked({itemID: item.id});\n }\n }\n\n return (\n <li>\n {/* Item button ---------------------------------------------------- */}\n <button\n type=\"button\"\n className={cn(\n \"flex w-full items-center rounded-sm outline-none transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground\",\n \"focus-visible:ring-1 focus-visible:ring-ring\",\n item.isSelected && \"bg-accent text-accent-foreground font-medium\",\n ITEM_SIZE[size],\n item.className,\n )}\n onClick={handleClick}\n aria-expanded={hasNested ? open : undefined}\n data-selected={item.isSelected || undefined}\n >\n {/* Start decorator */}\n {renderDecorator(item.startDecorator, size, cardName)}\n\n {/* Title + subtitle */}\n <span className=\"flex min-w-0 flex-1 flex-col items-start\">\n <span className=\"truncate leading-none\">{item.title}</span>\n {item.subTitle && (\n <span\n className={cn(\n \"mt-0.5 truncate text-muted-foreground\",\n SUB_SIZE[size],\n )}\n >\n {item.subTitle}\n </span>\n )}\n </span>\n\n {/* End decorator (suppress if we're showing the chevron) */}\n {!hasNested && renderDecorator(item.endDecorator, size, cardName)}\n\n {/* Expand/collapse chevron for nested items */}\n {hasNested && (\n <ChevronDown\n className={cn(\n \"ml-auto size-4 shrink-0 text-muted-foreground transition-transform duration-200\",\n open && \"rotate-180\",\n )}\n />\n )}\n </button>\n\n {/* Nested list ---------------------------------------------------- */}\n {hasNested && open && (\n <ul\n className={cn(\n \"ml-3 mt-0.5 flex flex-col gap-0.5 border-l border-border pl-2\",\n \"list-none p-0\",\n // keep the indent aligned regardless of density\n size === \"lg\" ? \"ml-4 pl-3\" : \"ml-3 pl-2\",\n )}\n >\n {item.nested!.map((child, idx) => (\n <ItemRow\n key={child.id ?? idx}\n item={child}\n size={size}\n onItemClicked={onItemClicked}\n cardName={cardName}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\n// ---------------------------------------------------------------------------\n// List card component\n// ---------------------------------------------------------------------------\n\nexport const ListComponent = (\n props: PiCardProps<ListProps, ListEvents>,\n): React.ReactNode => {\n const {\n items,\n size = \"md\",\n marker,\n className,\n style,\n onItemClicked,\n cardName,\n } = props;\n\n // When a marker is requested, expose it via CSS custom property so the\n // list-style-type cascade works without a wrapper element.\n const rootStyle: React.CSSProperties = {\n ...style,\n ...(marker && marker !== \"none\" ? {listStyleType: marker} : undefined),\n };\n\n return (\n <ul\n className={cn(\n \"flex flex-col gap-0.5 p-0\",\n marker && marker !== \"none\" ? \"list-inside\" : \"list-none\",\n className,\n )}\n style={rootStyle}\n data-pihanga={cardName}\n >\n {items.map((item, idx) => (\n <ItemRow\n key={item.id ?? idx}\n item={item}\n size={size}\n onItemClicked={onItemClicked}\n cardName={cardName}\n />\n ))}\n </ul>\n );\n};\n",
|
|
29
|
-
"type": "registry:component",
|
|
30
|
-
"target": "src/cards/list/list.component.tsx"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"path": "cards/list/list.types.ts",
|
|
34
|
-
"content": "import * as React from \"react\";\nimport {\n PiCardRef,\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const LIST_CARD = \"shad/list\";\n\nexport const List = createCardDeclaration<ListProps, ListEvents>(LIST_CARD);\n\nexport const LIST_ACTION = registerActions(LIST_CARD, [\"item_clicked\"]);\n\nexport const onListItemClicked = createOnAction<ItemClickedEvent>(\n LIST_ACTION.ITEM_CLICKED,\n);\n\n// Decorator shown before or after an item's title/subtitle block.\nexport type IconDecorator = {\n type: \"icon\";\n /** Name registered via registerIcon() in src/cards/icons.ts */\n name: string;\n className?: string;\n};\n\nexport type AvatarDecorator = {\n type: \"avatar\";\n src: string;\n fallback?: string;\n className?: string;\n};\n\nexport type ChipDecorator = {\n type: \"chip\";\n text: string;\n className?: string;\n};\n\nexport type CardDecorator = {\n type: \"card\";\n cardName: PiCardRef;\n};\n\nexport type DecoratorT =\n | IconDecorator\n | AvatarDecorator\n | ChipDecorator\n | CardDecorator;\n\nexport type ListItem = {\n /** React key — must be unique within the list. */\n id: string | number;\n isSelected?: boolean;\n title: string;\n subTitle?: string;\n /** When present the item becomes a collapsible parent. */\n nested?: ListItem[];\n startDecorator?: DecoratorT;\n endDecorator?: DecoratorT;\n /** Extra CSS classes on this item's button. */\n className?: string;\n};\n\nexport type ListProps = {\n items: ListItem[];\n /** Visual density — defaults to \"md\". */\n size?: \"sm\" | \"md\" | \"lg\";\n /**\n * CSS `list-style-type` value (e.g. \"disc\", \"decimal\", \"none\").\n * Defaults to \"none\" (plain list without markers).\n */\n marker?: string;\n /** CSS classes applied directly to the root `<ul>`. */\n className?: string;\n /** Inline styles applied directly to the root `<ul>`. */\n style?: React.CSSProperties;\n};\n\nexport type ItemClickedEvent = {\n itemID: string | number;\n};\n\nexport type ListEvents = {\n onItemClicked: ItemClickedEvent;\n};\n",
|
|
35
|
-
"type": "registry:component",
|
|
36
|
-
"target": "src/cards/list/list.types.ts"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
package/r/loadingOverlay.json
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "loadingOverlay",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga loadingOverlay card component",
|
|
6
|
-
"dependencies": [
|
|
7
|
-
"clsx@^2.1.1"
|
|
8
|
-
],
|
|
9
|
-
"registryDependencies": [
|
|
10
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
11
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-ui-extras.json"
|
|
12
|
-
],
|
|
13
|
-
"files": [
|
|
14
|
-
{
|
|
15
|
-
"path": "cards/loadingOverlay/index.ts",
|
|
16
|
-
"content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {LoadingOverlayComponent} from \"./loadingOverlay.component\";\nimport {LOADING_OVERLAY_CARD} from \"./loadingOverlay.types\";\n\nexport * from \"./loadingOverlay.types\";\n\nregisterCardComponent({\n name: LOADING_OVERLAY_CARD,\n component: LoadingOverlayComponent,\n});\n",
|
|
17
|
-
"type": "registry:component",
|
|
18
|
-
"target": "src/cards/loadingOverlay/index.ts"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"path": "cards/loadingOverlay/loadingOverlay.component.tsx",
|
|
22
|
-
"content": "import * as React from \"react\";\nimport clsx from \"clsx\";\n\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\n\nimport {Spinner} from \"@/components/ui/spinner\";\n\nimport type {LoadingOverlayProps} from \"./loadingOverlay.types\";\nimport \"./loadingOverlay.css\";\n\nexport const LoadingOverlayComponent = (\n props: PiCardProps<LoadingOverlayProps>,\n): React.ReactNode => {\n const {\n cardName,\n content,\n isLoading,\n label,\n fillParent,\n viewportCentered,\n className,\n contentClassName,\n overlayClassName,\n } = props;\n\n if (!content) return null;\n\n return (\n <div\n data-pihanga={cardName}\n className={clsx(\n \"overlay-wrapper\",\n isLoading && \"is-loading\",\n fillParent && \"fill-parent\",\n viewportCentered && \"viewport-centered\",\n className,\n )}\n >\n <div className={clsx(\"overlay-content\", contentClassName)}>\n <Card cardName={content} parentCard={cardName} />\n <div className={clsx(\"loading-overlay\", overlayClassName)}>\n <div className=\"loading-overlay__content\">\n <Spinner size=\"icon\" />\n <span>{label ?? \"Loading...\"}</span>\n </div>\n </div>\n </div>\n </div>\n );\n};\n",
|
|
23
|
-
"type": "registry:component",
|
|
24
|
-
"target": "src/cards/loadingOverlay/loadingOverlay.component.tsx"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"path": "cards/loadingOverlay/loadingOverlay.css",
|
|
28
|
-
"content": ".overlay-wrapper {\n position: relative;\n width: 100%;\n min-width: 0;\n\n display: flex;\n flex-direction: column;\n}\n\n.overlay-wrapper.is-loading.fill-parent {\n min-height: 12rem;\n}\n\n.overlay-content {\n position: relative;\n width: 100%;\n min-width: 0;\n\n display: flex;\n flex-direction: column;\n overflow-y: hidden;\n /* Prevent double scrollbars */\n flex-grow: 1;\n}\n\n.overlay-wrapper.fill-parent .overlay-content {\n min-height: inherit;\n}\n\n.loading-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: oklch(1 0 0 / 0.7);\n opacity: 0;\n pointer-events: none;\n transition: opacity 150ms ease;\n z-index: 2;\n}\n\n.overlay-wrapper.viewport-centered .loading-overlay {\n position: fixed;\n inset: 0;\n z-index: 50;\n}\n\n.dark .loading-overlay {\n background: oklch(0.145 0 0 / 0.5);\n}\n\n.overlay-wrapper.is-loading .loading-overlay {\n opacity: 1;\n pointer-events: auto;\n}\n\n.loading-overlay__content {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 0.875rem;\n color: var(--muted-foreground);\n}",
|
|
29
|
-
"type": "registry:component",
|
|
30
|
-
"target": "src/cards/loadingOverlay/loadingOverlay.css"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"path": "cards/loadingOverlay/loadingOverlay.types.ts",
|
|
34
|
-
"content": "import {createCardDeclaration, type PiCardRef} from \"@pihanga2/core\";\n\nexport const LOADING_OVERLAY_CARD = \"loading-overlay\";\n\nexport type LoadingOverlayProps = {\n content: PiCardRef;\n isLoading?: boolean;\n label?: string;\n fillParent?: boolean;\n viewportCentered?: boolean;\n className?: string;\n contentClassName?: string;\n overlayClassName?: string;\n};\n\nexport const LoadingOverlay = createCardDeclaration<LoadingOverlayProps>(\n LOADING_OVERLAY_CARD,\n);\n",
|
|
35
|
-
"type": "registry:component",
|
|
36
|
-
"target": "src/cards/loadingOverlay/loadingOverlay.types.ts"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
package/r/loadingSkeleton.json
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "loadingSkeleton",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga loadingSkeleton card component",
|
|
6
|
-
"dependencies": [],
|
|
7
|
-
"registryDependencies": [
|
|
8
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json"
|
|
9
|
-
],
|
|
10
|
-
"files": [
|
|
11
|
-
{
|
|
12
|
-
"path": "cards/loadingSkeleton/index.ts",
|
|
13
|
-
"content": "import {registerCardComponent} from \"@pihanga2/core\";\nimport {LoadingSkeletonComponent} from \"./loading-skeleton.component\";\nimport {LOADING_SKELETON_CARD} from \"./loading-skeleton.types\";\n\nexport * from \"./loading-skeleton.types\";\n\n// No events — display-only card.\nregisterCardComponent({\n name: LOADING_SKELETON_CARD,\n component: LoadingSkeletonComponent,\n});\n",
|
|
14
|
-
"type": "registry:component",
|
|
15
|
-
"target": "src/cards/loadingSkeleton/index.ts"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"path": "cards/loadingSkeleton/loading-skeleton.component.tsx",
|
|
19
|
-
"content": "import * as React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport type {\n LoadingSkeletonProps,\n SkeletonRowSize,\n SkeletonSpacing,\n} from \"./loading-skeleton.types\";\n\n// ── Preset maps ───────────────────────────────────────────────────────────────\n\n/** Height class for each named `rowSize` preset. */\nconst ROW_SIZE_CLASS: Record<SkeletonRowSize, string> = {\n xs: \"h-3\", // 12 px — tiny text / timestamp lines\n sm: \"h-5\", // 20 px — small text or menu items\n md: \"h-10\", // 40 px — standard input / row\n lg: \"h-14\", // 56 px — large card rows\n xl: \"h-20\", // 80 px — wide widget / hero section\n};\n\n/** Gap class for each named `spacing` preset. */\nconst SPACING_CLASS: Record<SkeletonSpacing, string> = {\n sm: \"gap-2\", // 8 px\n md: \"gap-3\", // 12 px\n lg: \"gap-4\", // 16 px\n};\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\n/**\n * LoadingSkeletonComponent\n *\n * While `loading` is `true`: renders `rows` animated shimmer rows inside a\n * flex column wrapper.\n *\n * **Presets** — `rowSize` and `spacing` select named height / gap values\n * without needing Tailwind knowledge.\n *\n * **Raw overrides** — `rowClassName` (overrides `rowSize`) and `className`\n * (overrides `spacing`) accept arbitrary Tailwind classes for custom layouts.\n *\n * While `loading` is `false`: transparently passes through to `content` (no\n * extra DOM wrapper) or renders nothing when `content` is omitted.\n */\nexport const LoadingSkeletonComponent = (\n props: PiCardProps<LoadingSkeletonProps>,\n): React.ReactNode => {\n const {\n cardName,\n loading,\n rows = 3,\n rowSize = \"md\",\n spacing = \"md\",\n rowClassName,\n className,\n content,\n } = props;\n\n if (loading) {\n // rawClassName takes precedence over the preset; preset is the fallback.\n const rowCls =\n rowClassName ?? `rounded-md bg-foreground/15 ${ROW_SIZE_CLASS[rowSize]}`;\n const wrapperCls =\n className ?? `flex w-full flex-col ${SPACING_CLASS[spacing]}`;\n\n return (\n <div data-pihanga={cardName} className={wrapperCls}>\n {Array.from({length: rows}).map((_, i) => (\n <div key={i} className={`animate-pulse ${rowCls}`} />\n ))}\n </div>\n );\n }\n\n if (content) {\n // Transparent pass-through: no extra wrapper element.\n return <Card cardName={content} parentCard={cardName} />;\n }\n\n // Loaded with no content card — render nothing.\n return null;\n};\n",
|
|
20
|
-
"type": "registry:component",
|
|
21
|
-
"target": "src/cards/loadingSkeleton/loading-skeleton.component.tsx"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"path": "cards/loadingSkeleton/loading-skeleton.types.ts",
|
|
25
|
-
"content": "import {createCardDeclaration} from \"@pihanga2/core\";\nimport type {PiCardRef} from \"@pihanga2/core\";\n\n// ── Card id ───────────────────────────────────────────────────────────────────\n\nexport const LOADING_SKELETON_CARD = \"shad/loading-skeleton\";\n\n// ── Card declaration factory ──────────────────────────────────────────────────\n\nexport const LoadingSkeleton = createCardDeclaration<LoadingSkeletonProps>(\n LOADING_SKELETON_CARD,\n);\n\n// ── Preset enums ──────────────────────────────────────────────────────────────\n\n/**\n * Named height presets for skeleton rows.\n *\n * | Value | Height | Typical use |\n * |-------|----------|---------------------------------|\n * | `xs` | 12 px | Tiny text / timestamp lines |\n * | `sm` | 20 px | Small text or menu items |\n * | `md` | 40 px | Standard input / row (default) |\n * | `lg` | 56 px | Large card rows |\n * | `xl` | 80 px | Wide widget / hero section |\n */\nexport type SkeletonRowSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n/**\n * Named gap presets between skeleton rows.\n *\n * | Value | Gap |\n * |-------|--------|\n * | `sm` | 8 px |\n * | `md` | 12 px |\n * | `lg` | 16 px |\n */\nexport type SkeletonSpacing = \"sm\" | \"md\" | \"lg\";\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\n/**\n * Props for the `shad/loading-skeleton` card.\n *\n * Shows animated shimmer rows while `loading` is `true`; once `loading`\n * becomes `false` it transparently passes through to `content` (or renders\n * nothing when `content` is omitted).\n *\n * **Quick setup** — use the preset props:\n * ```ts\n * registerCard(\"myApp/area\", LoadingSkeleton({\n * loading: memo((s: AppState) => s.dataLoading),\n * rows: 4,\n * rowSize: \"lg\",\n * spacing: \"lg\",\n * content: \"myApp/dataList\",\n * }));\n * ```\n *\n * **Custom Tailwind** — override with raw class strings:\n * ```ts\n * registerCard(\"myApp/area\", LoadingSkeleton({\n * loading: memo((s: AppState) => s.dataLoading),\n * rows: 4,\n * rowClassName: \"h-32 rounded-xl bg-primary/10\",\n * className: \"grid grid-cols-2 gap-4 w-full\",\n * content: \"myApp/cardGrid\",\n * }));\n * ```\n */\nexport type LoadingSkeletonProps = {\n /** When `true`, render animated skeleton rows; when `false` render\n * `content` (or nothing if `content` is omitted). */\n loading: boolean;\n\n /**\n * Number of placeholder rows to render while loading.\n * @default 3\n */\n rows?: number;\n\n // ── Preset controls (no Tailwind knowledge needed) ────────────────────────\n\n /**\n * Named height preset for each shimmer row.\n * Ignored when `rowClassName` is provided.\n * @default \"md\"\n */\n rowSize?: SkeletonRowSize;\n\n /**\n * Named gap preset between shimmer rows.\n * Ignored when `className` is provided.\n * @default \"md\"\n */\n spacing?: SkeletonSpacing;\n\n // ── Raw Tailwind overrides (power users) ──────────────────────────────────\n\n /**\n * Raw Tailwind classes for every shimmer row.\n * When set, takes precedence over `rowSize`.\n * Must include a height (`h-*`) and optionally `rounded-*` and `bg-*`.\n * Tip: use `bg-foreground/15` for good contrast in both themes.\n */\n rowClassName?: string;\n\n /**\n * Raw Tailwind classes for the outer `<div>` wrapper (loading state only).\n * When set, takes precedence over `spacing`.\n * Include `w-full` to ensure rows fill the container.\n */\n className?: string;\n\n /**\n * Card to render when `loading` is `false`.\n * When omitted the card renders nothing in the loaded state.\n */\n content?: PiCardRef;\n};\n// No events — display-only card.\n",
|
|
26
|
-
"type": "registry:component",
|
|
27
|
-
"target": "src/cards/loadingSkeleton/loading-skeleton.types.ts"
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
}
|
package/r/markdownViewer.json
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "markdownViewer",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga markdownViewer card component",
|
|
6
|
-
"dependencies": [
|
|
7
|
-
"react-markdown@UNKNOWN - add to root package.json",
|
|
8
|
-
"rehype-highlight@^7.0.2",
|
|
9
|
-
"rehype-katex@UNKNOWN - add to root package.json",
|
|
10
|
-
"mermaid@^11.15.0",
|
|
11
|
-
"remark-gfm@UNKNOWN - add to root package.json",
|
|
12
|
-
"remark-math@UNKNOWN - add to root package.json",
|
|
13
|
-
"unified@UNKNOWN - add to root package.json",
|
|
14
|
-
"highlight.js@^11.11.1"
|
|
15
|
-
],
|
|
16
|
-
"registryDependencies": [
|
|
17
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json"
|
|
18
|
-
],
|
|
19
|
-
"files": [
|
|
20
|
-
{
|
|
21
|
-
"path": "cards/markdownViewer/index.ts",
|
|
22
|
-
"content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {MarkdownViewerComponent} from \"./markdownViewer.component\";\nimport {MARKDOWN_CARD} from \"./markdownViewer.types\";\n\nexport * from \"./markdownViewer.types\";\n\nregisterCardComponent({\n name: MARKDOWN_CARD,\n component: MarkdownViewerComponent,\n});\n",
|
|
23
|
-
"type": "registry:component",
|
|
24
|
-
"target": "src/cards/markdownViewer/index.ts"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"path": "cards/markdownViewer/katex.css",
|
|
28
|
-
"content": "/* stylelint-disable font-family-no-missing-generic-family-keyword */\n@font-face {\n font-family: \"KaTeX_AMS\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_AMS-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_AMS-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_AMS-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Caligraphic\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Caligraphic\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Caligraphic-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Fraktur\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Fraktur\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Fraktur-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-BoldItalic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-BoldItalic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-BoldItalic.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Italic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Italic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Italic.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_Main\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Main-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Math\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-BoldItalic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-BoldItalic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-BoldItalic.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_Math\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-Italic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-Italic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Math-Italic.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_SansSerif\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Bold.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Bold.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Bold.ttf) format(\"truetype\");\n font-weight: bold;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_SansSerif\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Italic.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Italic.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Italic.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: italic;\n}\n\n@font-face {\n font-family: \"KaTeX_SansSerif\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_SansSerif-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Script\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Script-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Script-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Script-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size1\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size1-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size1-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size1-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size2\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size2-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size2-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size2-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size3\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size3-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size3-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size3-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Size4\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size4-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size4-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Size4-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n@font-face {\n font-family: \"KaTeX_Typewriter\";\n src: url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Typewriter-Regular.woff2) format(\"woff2\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Typewriter-Regular.woff) format(\"woff\"), url(https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/fonts/KaTeX_Typewriter-Regular.ttf) format(\"truetype\");\n font-weight: normal;\n font-style: normal;\n}\n\n.katex {\n font: normal 1.21em KaTeX_Main, Times New Roman, serif;\n line-height: 1.2;\n text-indent: 0;\n text-rendering: auto;\n}\n\n.katex * {\n -ms-high-contrast-adjust: none !important;\n}\n\n.katex * {\n border-color: currentColor;\n}\n\n.katex .katex-version::after {\n content: \"0.16.11\";\n}\n\n.katex .katex-mathml {\n /* Accessibility hack to only show to screen readers\n Found at: http://a11yproject.com/posts/how-to-hide-content/ */\n position: absolute;\n clip: rect(1px, 1px, 1px, 1px);\n padding: 0;\n border: 0;\n height: 1px;\n width: 1px;\n overflow: hidden;\n}\n\n.katex .katex-html {\n /* \\newline is an empty block at top level, between .base elements */\n}\n\n.katex .katex-html>.newline {\n display: block;\n}\n\n.katex .base {\n position: relative;\n display: inline-block;\n white-space: nowrap;\n width: -webkit-min-content;\n width: -moz-min-content;\n width: min-content;\n}\n\n.katex .strut {\n display: inline-block;\n}\n\n.katex .textbf {\n font-weight: bold;\n}\n\n.katex .textit {\n font-style: italic;\n}\n\n.katex .textrm {\n font-family: KaTeX_Main;\n}\n\n.katex .textsf {\n font-family: KaTeX_SansSerif;\n}\n\n.katex .texttt {\n font-family: KaTeX_Typewriter;\n}\n\n.katex .mathnormal {\n font-family: KaTeX_Math;\n font-style: italic;\n}\n\n.katex .mathit {\n font-family: KaTeX_Main;\n font-style: italic;\n}\n\n.katex .mathrm {\n font-style: normal;\n}\n\n.katex .mathbf {\n font-family: KaTeX_Main;\n font-weight: bold;\n}\n\n.katex .boldsymbol {\n font-family: KaTeX_Math;\n font-weight: bold;\n font-style: italic;\n}\n\n.katex .amsrm {\n font-family: KaTeX_AMS;\n}\n\n.katex .mathbb,\n.katex .textbb {\n font-family: KaTeX_AMS;\n}\n\n.katex .mathcal {\n font-family: KaTeX_Caligraphic;\n}\n\n.katex .mathfrak,\n.katex .textfrak {\n font-family: KaTeX_Fraktur;\n}\n\n.katex .mathboldfrak,\n.katex .textboldfrak {\n font-family: KaTeX_Fraktur;\n font-weight: bold;\n}\n\n.katex .mathtt {\n font-family: KaTeX_Typewriter;\n}\n\n.katex .mathscr,\n.katex .textscr {\n font-family: KaTeX_Script;\n}\n\n.katex .mathsf,\n.katex .textsf {\n font-family: KaTeX_SansSerif;\n}\n\n.katex .mathboldsf,\n.katex .textboldsf {\n font-family: KaTeX_SansSerif;\n font-weight: bold;\n}\n\n.katex .mathitsf,\n.katex .textitsf {\n font-family: KaTeX_SansSerif;\n font-style: italic;\n}\n\n.katex .mainrm {\n font-family: KaTeX_Main;\n font-style: normal;\n}\n\n.katex .vlist-t {\n display: inline-table;\n table-layout: fixed;\n border-collapse: collapse;\n}\n\n.katex .vlist-r {\n display: table-row;\n}\n\n.katex .vlist {\n display: table-cell;\n vertical-align: bottom;\n position: relative;\n}\n\n.katex .vlist>span {\n display: block;\n height: 0;\n position: relative;\n}\n\n.katex .vlist>span>span {\n display: inline-block;\n}\n\n.katex .vlist>span>.pstrut {\n overflow: hidden;\n width: 0;\n}\n\n.katex .vlist-t2 {\n margin-right: -2px;\n}\n\n.katex .vlist-s {\n display: table-cell;\n vertical-align: bottom;\n font-size: 1px;\n width: 2px;\n min-width: 2px;\n}\n\n.katex .vbox {\n display: inline-flex;\n flex-direction: column;\n align-items: baseline;\n}\n\n.katex .hbox {\n display: inline-flex;\n flex-direction: row;\n width: 100%;\n}\n\n.katex .thinbox {\n display: inline-flex;\n flex-direction: row;\n width: 0;\n max-width: 0;\n}\n\n.katex .msupsub {\n text-align: left;\n}\n\n.katex .mfrac>span>span {\n text-align: center;\n}\n\n.katex .mfrac .frac-line {\n display: inline-block;\n width: 100%;\n border-bottom-style: solid;\n}\n\n.katex .mfrac .frac-line,\n.katex .overline .overline-line,\n.katex .underline .underline-line,\n.katex .hline,\n.katex .hdashline,\n.katex .rule {\n min-height: 1px;\n}\n\n.katex .mspace {\n display: inline-block;\n}\n\n.katex .llap,\n.katex .rlap,\n.katex .clap {\n width: 0;\n position: relative;\n}\n\n.katex .llap>.inner,\n.katex .rlap>.inner,\n.katex .clap>.inner {\n position: absolute;\n}\n\n.katex .llap>.fix,\n.katex .rlap>.fix,\n.katex .clap>.fix {\n display: inline-block;\n}\n\n.katex .llap>.inner {\n right: 0;\n}\n\n.katex .rlap>.inner,\n.katex .clap>.inner {\n left: 0;\n}\n\n.katex .clap>.inner>span {\n margin-left: -50%;\n margin-right: 50%;\n}\n\n.katex .rule {\n display: inline-block;\n border: solid 0;\n position: relative;\n}\n\n.katex .overline .overline-line,\n.katex .underline .underline-line,\n.katex .hline {\n display: inline-block;\n width: 100%;\n border-bottom-style: solid;\n}\n\n.katex .hdashline {\n display: inline-block;\n width: 100%;\n border-bottom-style: dashed;\n}\n\n.katex .sqrt>.root {\n /* These values are taken from the definition of `\\r@@t`,\n `\\mkern 5mu` and `\\mkern -10mu`. */\n margin-left: 0.2777777778em;\n margin-right: -0.5555555556em;\n}\n\n.katex .sizing.reset-size1.size1,\n.katex .fontsize-ensurer.reset-size1.size1 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size1.size2,\n.katex .fontsize-ensurer.reset-size1.size2 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size1.size3,\n.katex .fontsize-ensurer.reset-size1.size3 {\n /* stylelint-disable-next-line */\n font-size: 1.4em;\n}\n\n.katex .sizing.reset-size1.size4,\n.katex .fontsize-ensurer.reset-size1.size4 {\n /* stylelint-disable-next-line */\n font-size: 1.6em;\n}\n\n.katex .sizing.reset-size1.size5,\n.katex .fontsize-ensurer.reset-size1.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.8em;\n}\n\n.katex .sizing.reset-size1.size6,\n.katex .fontsize-ensurer.reset-size1.size6 {\n /* stylelint-disable-next-line */\n font-size: 2em;\n}\n\n.katex .sizing.reset-size1.size7,\n.katex .fontsize-ensurer.reset-size1.size7 {\n /* stylelint-disable-next-line */\n font-size: 2.4em;\n}\n\n.katex .sizing.reset-size1.size8,\n.katex .fontsize-ensurer.reset-size1.size8 {\n /* stylelint-disable-next-line */\n font-size: 2.88em;\n}\n\n.katex .sizing.reset-size1.size9,\n.katex .fontsize-ensurer.reset-size1.size9 {\n /* stylelint-disable-next-line */\n font-size: 3.456em;\n}\n\n.katex .sizing.reset-size1.size10,\n.katex .fontsize-ensurer.reset-size1.size10 {\n /* stylelint-disable-next-line */\n font-size: 4.148em;\n}\n\n.katex .sizing.reset-size1.size11,\n.katex .fontsize-ensurer.reset-size1.size11 {\n /* stylelint-disable-next-line */\n font-size: 4.976em;\n}\n\n.katex .sizing.reset-size2.size1,\n.katex .fontsize-ensurer.reset-size2.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size2.size2,\n.katex .fontsize-ensurer.reset-size2.size2 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size2.size3,\n.katex .fontsize-ensurer.reset-size2.size3 {\n /* stylelint-disable-next-line */\n font-size: 1.1666666667em;\n}\n\n.katex .sizing.reset-size2.size4,\n.katex .fontsize-ensurer.reset-size2.size4 {\n /* stylelint-disable-next-line */\n font-size: 1.3333333333em;\n}\n\n.katex .sizing.reset-size2.size5,\n.katex .fontsize-ensurer.reset-size2.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.5em;\n}\n\n.katex .sizing.reset-size2.size6,\n.katex .fontsize-ensurer.reset-size2.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.6666666667em;\n}\n\n.katex .sizing.reset-size2.size7,\n.katex .fontsize-ensurer.reset-size2.size7 {\n /* stylelint-disable-next-line */\n font-size: 2em;\n}\n\n.katex .sizing.reset-size2.size8,\n.katex .fontsize-ensurer.reset-size2.size8 {\n /* stylelint-disable-next-line */\n font-size: 2.4em;\n}\n\n.katex .sizing.reset-size2.size9,\n.katex .fontsize-ensurer.reset-size2.size9 {\n /* stylelint-disable-next-line */\n font-size: 2.88em;\n}\n\n.katex .sizing.reset-size2.size10,\n.katex .fontsize-ensurer.reset-size2.size10 {\n /* stylelint-disable-next-line */\n font-size: 3.4566666667em;\n}\n\n.katex .sizing.reset-size2.size11,\n.katex .fontsize-ensurer.reset-size2.size11 {\n /* stylelint-disable-next-line */\n font-size: 4.1466666667em;\n}\n\n.katex .sizing.reset-size3.size1,\n.katex .fontsize-ensurer.reset-size3.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.7142857143em;\n}\n\n.katex .sizing.reset-size3.size2,\n.katex .fontsize-ensurer.reset-size3.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.8571428571em;\n}\n\n.katex .sizing.reset-size3.size3,\n.katex .fontsize-ensurer.reset-size3.size3 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size3.size4,\n.katex .fontsize-ensurer.reset-size3.size4 {\n /* stylelint-disable-next-line */\n font-size: 1.1428571429em;\n}\n\n.katex .sizing.reset-size3.size5,\n.katex .fontsize-ensurer.reset-size3.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.2857142857em;\n}\n\n.katex .sizing.reset-size3.size6,\n.katex .fontsize-ensurer.reset-size3.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.4285714286em;\n}\n\n.katex .sizing.reset-size3.size7,\n.katex .fontsize-ensurer.reset-size3.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.7142857143em;\n}\n\n.katex .sizing.reset-size3.size8,\n.katex .fontsize-ensurer.reset-size3.size8 {\n /* stylelint-disable-next-line */\n font-size: 2.0571428571em;\n}\n\n.katex .sizing.reset-size3.size9,\n.katex .fontsize-ensurer.reset-size3.size9 {\n /* stylelint-disable-next-line */\n font-size: 2.4685714286em;\n}\n\n.katex .sizing.reset-size3.size10,\n.katex .fontsize-ensurer.reset-size3.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.9628571429em;\n}\n\n.katex .sizing.reset-size3.size11,\n.katex .fontsize-ensurer.reset-size3.size11 {\n /* stylelint-disable-next-line */\n font-size: 3.5542857143em;\n}\n\n.katex .sizing.reset-size4.size1,\n.katex .fontsize-ensurer.reset-size4.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.625em;\n}\n\n.katex .sizing.reset-size4.size2,\n.katex .fontsize-ensurer.reset-size4.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.75em;\n}\n\n.katex .sizing.reset-size4.size3,\n.katex .fontsize-ensurer.reset-size4.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.875em;\n}\n\n.katex .sizing.reset-size4.size4,\n.katex .fontsize-ensurer.reset-size4.size4 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size4.size5,\n.katex .fontsize-ensurer.reset-size4.size5 {\n /* stylelint-disable-next-line */\n font-size: 1.125em;\n}\n\n.katex .sizing.reset-size4.size6,\n.katex .fontsize-ensurer.reset-size4.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.25em;\n}\n\n.katex .sizing.reset-size4.size7,\n.katex .fontsize-ensurer.reset-size4.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.5em;\n}\n\n.katex .sizing.reset-size4.size8,\n.katex .fontsize-ensurer.reset-size4.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.8em;\n}\n\n.katex .sizing.reset-size4.size9,\n.katex .fontsize-ensurer.reset-size4.size9 {\n /* stylelint-disable-next-line */\n font-size: 2.16em;\n}\n\n.katex .sizing.reset-size4.size10,\n.katex .fontsize-ensurer.reset-size4.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.5925em;\n}\n\n.katex .sizing.reset-size4.size11,\n.katex .fontsize-ensurer.reset-size4.size11 {\n /* stylelint-disable-next-line */\n font-size: 3.11em;\n}\n\n.katex .sizing.reset-size5.size1,\n.katex .fontsize-ensurer.reset-size5.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.5555555556em;\n}\n\n.katex .sizing.reset-size5.size2,\n.katex .fontsize-ensurer.reset-size5.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.6666666667em;\n}\n\n.katex .sizing.reset-size5.size3,\n.katex .fontsize-ensurer.reset-size5.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.7777777778em;\n}\n\n.katex .sizing.reset-size5.size4,\n.katex .fontsize-ensurer.reset-size5.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.8888888889em;\n}\n\n.katex .sizing.reset-size5.size5,\n.katex .fontsize-ensurer.reset-size5.size5 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size5.size6,\n.katex .fontsize-ensurer.reset-size5.size6 {\n /* stylelint-disable-next-line */\n font-size: 1.1111111111em;\n}\n\n.katex .sizing.reset-size5.size7,\n.katex .fontsize-ensurer.reset-size5.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.3333333333em;\n}\n\n.katex .sizing.reset-size5.size8,\n.katex .fontsize-ensurer.reset-size5.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.6em;\n}\n\n.katex .sizing.reset-size5.size9,\n.katex .fontsize-ensurer.reset-size5.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.92em;\n}\n\n.katex .sizing.reset-size5.size10,\n.katex .fontsize-ensurer.reset-size5.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.3044444444em;\n}\n\n.katex .sizing.reset-size5.size11,\n.katex .fontsize-ensurer.reset-size5.size11 {\n /* stylelint-disable-next-line */\n font-size: 2.7644444444em;\n}\n\n.katex .sizing.reset-size6.size1,\n.katex .fontsize-ensurer.reset-size6.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.5em;\n}\n\n.katex .sizing.reset-size6.size2,\n.katex .fontsize-ensurer.reset-size6.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.6em;\n}\n\n.katex .sizing.reset-size6.size3,\n.katex .fontsize-ensurer.reset-size6.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.7em;\n}\n\n.katex .sizing.reset-size6.size4,\n.katex .fontsize-ensurer.reset-size6.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.8em;\n}\n\n.katex .sizing.reset-size6.size5,\n.katex .fontsize-ensurer.reset-size6.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.9em;\n}\n\n.katex .sizing.reset-size6.size6,\n.katex .fontsize-ensurer.reset-size6.size6 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size6.size7,\n.katex .fontsize-ensurer.reset-size6.size7 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size6.size8,\n.katex .fontsize-ensurer.reset-size6.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.44em;\n}\n\n.katex .sizing.reset-size6.size9,\n.katex .fontsize-ensurer.reset-size6.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.728em;\n}\n\n.katex .sizing.reset-size6.size10,\n.katex .fontsize-ensurer.reset-size6.size10 {\n /* stylelint-disable-next-line */\n font-size: 2.074em;\n}\n\n.katex .sizing.reset-size6.size11,\n.katex .fontsize-ensurer.reset-size6.size11 {\n /* stylelint-disable-next-line */\n font-size: 2.488em;\n}\n\n.katex .sizing.reset-size7.size1,\n.katex .fontsize-ensurer.reset-size7.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.4166666667em;\n}\n\n.katex .sizing.reset-size7.size2,\n.katex .fontsize-ensurer.reset-size7.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.5em;\n}\n\n.katex .sizing.reset-size7.size3,\n.katex .fontsize-ensurer.reset-size7.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.5833333333em;\n}\n\n.katex .sizing.reset-size7.size4,\n.katex .fontsize-ensurer.reset-size7.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.6666666667em;\n}\n\n.katex .sizing.reset-size7.size5,\n.katex .fontsize-ensurer.reset-size7.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.75em;\n}\n\n.katex .sizing.reset-size7.size6,\n.katex .fontsize-ensurer.reset-size7.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size7.size7,\n.katex .fontsize-ensurer.reset-size7.size7 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size7.size8,\n.katex .fontsize-ensurer.reset-size7.size8 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size7.size9,\n.katex .fontsize-ensurer.reset-size7.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.44em;\n}\n\n.katex .sizing.reset-size7.size10,\n.katex .fontsize-ensurer.reset-size7.size10 {\n /* stylelint-disable-next-line */\n font-size: 1.7283333333em;\n}\n\n.katex .sizing.reset-size7.size11,\n.katex .fontsize-ensurer.reset-size7.size11 {\n /* stylelint-disable-next-line */\n font-size: 2.0733333333em;\n}\n\n.katex .sizing.reset-size8.size1,\n.katex .fontsize-ensurer.reset-size8.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.3472222222em;\n}\n\n.katex .sizing.reset-size8.size2,\n.katex .fontsize-ensurer.reset-size8.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.4166666667em;\n}\n\n.katex .sizing.reset-size8.size3,\n.katex .fontsize-ensurer.reset-size8.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.4861111111em;\n}\n\n.katex .sizing.reset-size8.size4,\n.katex .fontsize-ensurer.reset-size8.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.5555555556em;\n}\n\n.katex .sizing.reset-size8.size5,\n.katex .fontsize-ensurer.reset-size8.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.625em;\n}\n\n.katex .sizing.reset-size8.size6,\n.katex .fontsize-ensurer.reset-size8.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.6944444444em;\n}\n\n.katex .sizing.reset-size8.size7,\n.katex .fontsize-ensurer.reset-size8.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size8.size8,\n.katex .fontsize-ensurer.reset-size8.size8 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size8.size9,\n.katex .fontsize-ensurer.reset-size8.size9 {\n /* stylelint-disable-next-line */\n font-size: 1.2em;\n}\n\n.katex .sizing.reset-size8.size10,\n.katex .fontsize-ensurer.reset-size8.size10 {\n /* stylelint-disable-next-line */\n font-size: 1.4402777778em;\n}\n\n.katex .sizing.reset-size8.size11,\n.katex .fontsize-ensurer.reset-size8.size11 {\n /* stylelint-disable-next-line */\n font-size: 1.7277777778em;\n}\n\n.katex .sizing.reset-size9.size1,\n.katex .fontsize-ensurer.reset-size9.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.2893518519em;\n}\n\n.katex .sizing.reset-size9.size2,\n.katex .fontsize-ensurer.reset-size9.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.3472222222em;\n}\n\n.katex .sizing.reset-size9.size3,\n.katex .fontsize-ensurer.reset-size9.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.4050925926em;\n}\n\n.katex .sizing.reset-size9.size4,\n.katex .fontsize-ensurer.reset-size9.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.462962963em;\n}\n\n.katex .sizing.reset-size9.size5,\n.katex .fontsize-ensurer.reset-size9.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.5208333333em;\n}\n\n.katex .sizing.reset-size9.size6,\n.katex .fontsize-ensurer.reset-size9.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.5787037037em;\n}\n\n.katex .sizing.reset-size9.size7,\n.katex .fontsize-ensurer.reset-size9.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.6944444444em;\n}\n\n.katex .sizing.reset-size9.size8,\n.katex .fontsize-ensurer.reset-size9.size8 {\n /* stylelint-disable-next-line */\n font-size: 0.8333333333em;\n}\n\n.katex .sizing.reset-size9.size9,\n.katex .fontsize-ensurer.reset-size9.size9 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size9.size10,\n.katex .fontsize-ensurer.reset-size9.size10 {\n /* stylelint-disable-next-line */\n font-size: 1.2002314815em;\n}\n\n.katex .sizing.reset-size9.size11,\n.katex .fontsize-ensurer.reset-size9.size11 {\n /* stylelint-disable-next-line */\n font-size: 1.4398148148em;\n}\n\n.katex .sizing.reset-size10.size1,\n.katex .fontsize-ensurer.reset-size10.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.2410800386em;\n}\n\n.katex .sizing.reset-size10.size2,\n.katex .fontsize-ensurer.reset-size10.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.2892960463em;\n}\n\n.katex .sizing.reset-size10.size3,\n.katex .fontsize-ensurer.reset-size10.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.337512054em;\n}\n\n.katex .sizing.reset-size10.size4,\n.katex .fontsize-ensurer.reset-size10.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.3857280617em;\n}\n\n.katex .sizing.reset-size10.size5,\n.katex .fontsize-ensurer.reset-size10.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.4339440694em;\n}\n\n.katex .sizing.reset-size10.size6,\n.katex .fontsize-ensurer.reset-size10.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.4821600771em;\n}\n\n.katex .sizing.reset-size10.size7,\n.katex .fontsize-ensurer.reset-size10.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.5785920926em;\n}\n\n.katex .sizing.reset-size10.size8,\n.katex .fontsize-ensurer.reset-size10.size8 {\n /* stylelint-disable-next-line */\n font-size: 0.6943105111em;\n}\n\n.katex .sizing.reset-size10.size9,\n.katex .fontsize-ensurer.reset-size10.size9 {\n /* stylelint-disable-next-line */\n font-size: 0.8331726133em;\n}\n\n.katex .sizing.reset-size10.size10,\n.katex .fontsize-ensurer.reset-size10.size10 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .sizing.reset-size10.size11,\n.katex .fontsize-ensurer.reset-size10.size11 {\n /* stylelint-disable-next-line */\n font-size: 1.1996142719em;\n}\n\n.katex .sizing.reset-size11.size1,\n.katex .fontsize-ensurer.reset-size11.size1 {\n /* stylelint-disable-next-line */\n font-size: 0.2009646302em;\n}\n\n.katex .sizing.reset-size11.size2,\n.katex .fontsize-ensurer.reset-size11.size2 {\n /* stylelint-disable-next-line */\n font-size: 0.2411575563em;\n}\n\n.katex .sizing.reset-size11.size3,\n.katex .fontsize-ensurer.reset-size11.size3 {\n /* stylelint-disable-next-line */\n font-size: 0.2813504823em;\n}\n\n.katex .sizing.reset-size11.size4,\n.katex .fontsize-ensurer.reset-size11.size4 {\n /* stylelint-disable-next-line */\n font-size: 0.3215434084em;\n}\n\n.katex .sizing.reset-size11.size5,\n.katex .fontsize-ensurer.reset-size11.size5 {\n /* stylelint-disable-next-line */\n font-size: 0.3617363344em;\n}\n\n.katex .sizing.reset-size11.size6,\n.katex .fontsize-ensurer.reset-size11.size6 {\n /* stylelint-disable-next-line */\n font-size: 0.4019292605em;\n}\n\n.katex .sizing.reset-size11.size7,\n.katex .fontsize-ensurer.reset-size11.size7 {\n /* stylelint-disable-next-line */\n font-size: 0.4823151125em;\n}\n\n.katex .sizing.reset-size11.size8,\n.katex .fontsize-ensurer.reset-size11.size8 {\n /* stylelint-disable-next-line */\n font-size: 0.578778135em;\n}\n\n.katex .sizing.reset-size11.size9,\n.katex .fontsize-ensurer.reset-size11.size9 {\n /* stylelint-disable-next-line */\n font-size: 0.6945337621em;\n}\n\n.katex .sizing.reset-size11.size10,\n.katex .fontsize-ensurer.reset-size11.size10 {\n /* stylelint-disable-next-line */\n font-size: 0.8336012862em;\n}\n\n.katex .sizing.reset-size11.size11,\n.katex .fontsize-ensurer.reset-size11.size11 {\n /* stylelint-disable-next-line */\n font-size: 1em;\n}\n\n.katex .delimsizing.size1 {\n font-family: KaTeX_Size1;\n}\n\n.katex .delimsizing.size2 {\n font-family: KaTeX_Size2;\n}\n\n.katex .delimsizing.size3 {\n font-family: KaTeX_Size3;\n}\n\n.katex .delimsizing.size4 {\n font-family: KaTeX_Size4;\n}\n\n.katex .delimsizing.mult .delim-size1>span {\n font-family: KaTeX_Size1;\n}\n\n.katex .delimsizing.mult .delim-size4>span {\n font-family: KaTeX_Size4;\n}\n\n.katex .nulldelimiter {\n display: inline-block;\n width: 0.12em;\n}\n\n.katex .delimcenter {\n position: relative;\n}\n\n.katex .op-symbol {\n position: relative;\n}\n\n.katex .op-symbol.small-op {\n font-family: KaTeX_Size1;\n}\n\n.katex .op-symbol.large-op {\n font-family: KaTeX_Size2;\n}\n\n.katex .op-limits>.vlist-t {\n text-align: center;\n}\n\n.katex .accent>.vlist-t {\n text-align: center;\n}\n\n.katex .accent .accent-body {\n position: relative;\n}\n\n.katex .accent .accent-body:not(.accent-full) {\n width: 0;\n}\n\n.katex .overlay {\n display: block;\n}\n\n.katex .mtable .vertical-separator {\n display: inline-block;\n min-width: 1px;\n}\n\n.katex .mtable .arraycolsep {\n display: inline-block;\n}\n\n.katex .mtable .col-align-c>.vlist-t {\n text-align: center;\n}\n\n.katex .mtable .col-align-l>.vlist-t {\n text-align: left;\n}\n\n.katex .mtable .col-align-r>.vlist-t {\n text-align: right;\n}\n\n.katex .svg-align {\n text-align: left;\n}\n\n.katex svg {\n display: block;\n position: absolute;\n width: 100%;\n height: inherit;\n fill: currentColor;\n stroke: currentColor;\n fill-rule: nonzero;\n fill-opacity: 1;\n stroke-width: 1;\n stroke-linecap: butt;\n stroke-linejoin: miter;\n stroke-miterlimit: 4;\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n stroke-opacity: 1;\n}\n\n.katex svg path {\n stroke: none;\n}\n\n.katex img {\n border-style: none;\n min-width: 0;\n min-height: 0;\n max-width: none;\n max-height: none;\n}\n\n.katex .stretchy {\n width: 100%;\n display: block;\n position: relative;\n overflow: hidden;\n}\n\n.katex .stretchy::before,\n.katex .stretchy::after {\n content: \"\";\n}\n\n.katex .hide-tail {\n width: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.katex .halfarrow-left {\n position: absolute;\n left: 0;\n width: 50.2%;\n overflow: hidden;\n}\n\n.katex .halfarrow-right {\n position: absolute;\n right: 0;\n width: 50.2%;\n overflow: hidden;\n}\n\n.katex .brace-left {\n position: absolute;\n left: 0;\n width: 25.1%;\n overflow: hidden;\n}\n\n.katex .brace-center {\n position: absolute;\n left: 25%;\n width: 50%;\n overflow: hidden;\n}\n\n.katex .brace-right {\n position: absolute;\n right: 0;\n width: 25.1%;\n overflow: hidden;\n}\n\n.katex .x-arrow-pad {\n padding: 0 0.5em;\n}\n\n.katex .cd-arrow-pad {\n padding: 0 0.55556em 0 0.27778em;\n}\n\n.katex .x-arrow,\n.katex .mover,\n.katex .munder {\n text-align: center;\n}\n\n.katex .boxpad {\n padding: 0 0.3em;\n}\n\n.katex .fbox,\n.katex .fcolorbox {\n box-sizing: border-box;\n border: 0.04em solid;\n}\n\n.katex .cancel-pad {\n padding: 0 0.2em;\n}\n\n.katex .cancel-lap {\n margin-left: -0.2em;\n margin-right: -0.2em;\n}\n\n.katex .sout {\n border-bottom-style: solid;\n border-bottom-width: 0.08em;\n}\n\n.katex .angl {\n box-sizing: border-box;\n border-top: 0.049em solid;\n border-right: 0.049em solid;\n margin-right: 0.03889em;\n}\n\n.katex .anglpad {\n padding: 0 0.03889em;\n}\n\n.katex .eqn-num::before {\n counter-increment: katexEqnNo;\n content: \"(\" counter(katexEqnNo) \")\";\n}\n\n.katex .mml-eqn-num::before {\n counter-increment: mmlEqnNo;\n content: \"(\" counter(mmlEqnNo) \")\";\n}\n\n.katex .mtr-glue {\n width: 50%;\n}\n\n.katex .cd-vert-arrow {\n display: inline-block;\n position: relative;\n}\n\n.katex .cd-label-left {\n display: inline-block;\n position: absolute;\n right: calc(50% + 0.3em);\n text-align: left;\n}\n\n.katex .cd-label-right {\n display: inline-block;\n position: absolute;\n left: calc(50% + 0.3em);\n text-align: right;\n}\n\n.katex-display {\n display: block;\n margin: 1em 0;\n text-align: center;\n}\n\n.katex-display>.katex {\n display: block;\n text-align: center;\n white-space: nowrap;\n}\n\n.katex-display>.katex>.katex-html {\n display: block;\n position: relative;\n}\n\n.katex-display>.katex>.katex-html>.tag {\n position: absolute;\n right: 0;\n}\n\n.katex-display.leqno>.katex>.katex-html>.tag {\n left: 0;\n right: auto;\n}\n\n.katex-display.fleqn>.katex {\n text-align: left;\n padding-left: 2em;\n}\n\nbody {\n counter-reset: katexEqnNo mmlEqnNo;\n}",
|
|
29
|
-
"type": "registry:component",
|
|
30
|
-
"target": "src/cards/markdownViewer/katex.css"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"path": "cards/markdownViewer/markdownViewer.code.css",
|
|
34
|
-
"content": "/**\n * Code-block styling for the MarkdownViewer card.\n *\n * Works in concert with `highlight.js/styles/github.css` (imported in the\n * component) to give code a GitHub-like appearance:\n *\n * • Fenced code blocks → light-gray bg, rounded border, 13 px mono\n * • Inline code → muted pill, slightly smaller than body text\n *\n * These rules are intentionally scoped to elements that react-markdown\n * produces so they do not bleed into the rest of the application.\n */\n\n/* ── Fenced code blocks ────────────────────────────────────────────────────── */\n\n/* Override highlight.js's own background so the block matches GitHub's\n #f6f8fa rather than pure white, and clamp the font to a readable size. */\ncode.hljs {\n background-color: #f6f8fa;\n font-size: 0.8125rem;\n /* 13 px */\n line-height: 1.65;\n padding: 0;\n /* padding lives on <pre> below */\n border-radius: 0;\n /* radius lives on <pre> below */\n}\n\n/* The <pre> wrapper: add GitHub-style border + padding + corner radius. */\npre:has(code.hljs) {\n background-color: #f6f8fa;\n border: 1px solid #d0d7de;\n border-radius: 0.5rem;\n /* 8 px */\n padding: 1rem;\n overflow-x: auto;\n margin: 0.25rem 0;\n}\n\n/* Dark-mode overrides — keep readable against the dark surface */\n@media (prefers-color-scheme: dark) {\n code.hljs {\n background-color: #161b22;\n color: #c9d1d9;\n }\n\n pre:has(code.hljs) {\n background-color: #161b22;\n border-color: #30363d;\n }\n}\n\n/* ── Typography — headings, paragraphs, lists, etc. ────────────────────────── */\n\n/* Headings: size, weight, and spacing matching GitHub's rendered markdown. */\n.md-prose h1 {\n font-size: 2em;\n font-weight: 600;\n line-height: 1.25;\n padding-bottom: 0.3em;\n border-bottom: 1px solid #d0d7de;\n margin: 1.5rem 0 1rem;\n}\n\n.md-prose h2 {\n font-size: 1.5em;\n font-weight: 600;\n line-height: 1.25;\n padding-bottom: 0.3em;\n border-bottom: 1px solid #d0d7de;\n margin: 1.5rem 0 1rem;\n}\n\n.md-prose h3 {\n font-size: 1.25em;\n font-weight: 600;\n line-height: 1.25;\n margin: 1.25rem 0 0.75rem;\n}\n\n.md-prose h4 {\n font-size: 1em;\n font-weight: 600;\n line-height: 1.25;\n margin: 1rem 0 0.5rem;\n}\n\n.md-prose h5,\n.md-prose h6 {\n font-size: 0.875em;\n font-weight: 600;\n line-height: 1.25;\n margin: 1rem 0 0.5rem;\n}\n\n/* Paragraphs */\n.md-prose p {\n margin: 0 0 1rem;\n line-height: 1.75;\n}\n\n/* Unordered and ordered lists */\n.md-prose ul,\n.md-prose ol {\n padding-left: 2em;\n margin: 0 0 1rem;\n line-height: 1.75;\n}\n\n.md-prose ul {\n list-style-type: disc;\n}\n\n.md-prose ol {\n list-style-type: decimal;\n}\n\n.md-prose li {\n margin: 0.25em 0;\n}\n\n.md-prose li>p {\n margin: 0;\n}\n\n/* Nested lists */\n.md-prose ul ul,\n.md-prose ul ol,\n.md-prose ol ul,\n.md-prose ol ol {\n margin-bottom: 0;\n}\n\n/* Blockquotes */\n.md-prose blockquote {\n border-left: 4px solid #d0d7de;\n padding: 0 1em;\n color: #656d76;\n margin: 0 0 1rem;\n}\n\n.md-prose blockquote>p {\n margin: 0;\n}\n\n/* Horizontal rules */\n.md-prose hr {\n border: 0;\n border-top: 1px solid #d0d7de;\n margin: 1.5rem 0;\n}\n\n/* Links */\n.md-prose a {\n color: #0969da;\n text-decoration: none;\n}\n\n.md-prose a:hover {\n text-decoration: underline;\n}\n\n/* Strong / emphasis */\n.md-prose strong {\n font-weight: 600;\n}\n\n@media (prefers-color-scheme: dark) {\n\n .md-prose h1,\n .md-prose h2 {\n border-bottom-color: #30363d;\n }\n\n .md-prose hr {\n border-top-color: #30363d;\n }\n\n .md-prose blockquote {\n border-left-color: #30363d;\n color: #8b949e;\n }\n\n .md-prose a {\n color: #58a6ff;\n }\n}\n\n/* ── Tables (GitHub-like) ───────────────────────────────────────────────────── */\n\n/* Scoped to .md-prose so these rules never bleed into app-level tables. */\n.md-prose table {\n border-collapse: collapse;\n width: 100%;\n font-size: 0.875rem;\n margin: 0.75rem 0;\n overflow-x: auto;\n display: block;\n}\n\n.md-prose thead tr {\n background-color: #f6f8fa;\n}\n\n.md-prose th,\n.md-prose td {\n border: 1px solid #d0d7de;\n padding: 6px 13px;\n text-align: left;\n white-space: normal;\n}\n\n.md-prose tbody tr:nth-child(even) {\n background-color: #f6f8fa;\n}\n\n@media (prefers-color-scheme: dark) {\n\n .md-prose thead tr,\n .md-prose tbody tr:nth-child(even) {\n background-color: #161b22;\n }\n\n .md-prose th,\n .md-prose td {\n border-color: #30363d;\n }\n}\n\n/* ── Inline code ────────────────────────────────────────────────────────────── */\n\n/* Style inline `code` spans that are NOT inside a fenced block.\n Uses rgba so it stays subtle in both light and dark themes. */\n:not(pre)>code {\n font-size: 0.875em;\n font-family: ui-monospace, \"SFMono-Regular\", \"SF Mono\", Menlo, Consolas,\n \"Liberation Mono\", monospace;\n background-color: rgba(175, 184, 193, 0.2);\n border-radius: 0.25rem;\n /* 4 px */\n padding: 0.15em 0.4em;\n white-space: nowrap;\n}\n\n/* ── Mermaid diagrams ───────────────────────────────────────────────────────── */\n\n/* Reset font size so the page's typography CSS cannot make text larger than\n what Mermaid measured when it calculated node heights. */\n.mermaid-diagram svg,\n.mermaid-diagram foreignObject div,\n.mermaid-diagram foreignObject p,\n.mermaid-diagram foreignObject span {\n font-size: 14px !important;\n}\n\n/* White background on edge labels with padding so descenders (g, p, y …)\n aren't clipped. Belt-and-suspenders alongside themeVariables + inline SVG override. */\n.mermaid-diagram .edgeLabel,\n.mermaid-diagram .edgeLabel span,\n.mermaid-diagram .edgeLabel div,\n.mermaid-diagram .edgeLabel p {\n background: #ffffff !important;\n background-color: #ffffff !important;\n padding: 2px 4px !important;\n line-height: 1.4 !important;\n}\n\n.mermaid-diagram .edgeLabel rect {\n fill: #ffffff !important;\n}",
|
|
35
|
-
"type": "registry:component",
|
|
36
|
-
"target": "src/cards/markdownViewer/markdownViewer.code.css"
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"path": "cards/markdownViewer/markdownViewer.component.tsx",
|
|
40
|
-
"content": "import React, {useEffect, useRef, useState} from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport type {Components} from \"react-markdown\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport remarkMath from \"remark-math\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeKatex from \"rehype-katex\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport mermaid from \"mermaid\";\nimport \"./katex.css\";\nimport \"highlight.js/styles/github.css\";\nimport \"./markdownViewer.code.css\";\n\nimport type {MarkdownViewerProps} from \"./markdownViewer.types\";\n\n/**\n * Lazily initialise Mermaid once per page load (not per component mount).\n * Detects the current dark/light theme from the `<html>` element and picks\n * the matching Mermaid theme so edge labels and diagram colours look correct.\n */\nlet _mermaidReady = false;\nfunction ensureMermaid() {\n if (!_mermaidReady) {\n const isDark = document.documentElement.classList.contains(\"dark\");\n mermaid.initialize({\n startOnLoad: false,\n theme: isDark ? \"dark\" : \"default\",\n themeVariables: {\n // Keep edge label background white so labels don't bleed through arrows\n edgeLabelBackground: \"#ffffff\",\n // Match the font size we enforce in CSS so node sizes are calculated correctly\n fontSize: \"14px\",\n },\n });\n _mermaidReady = true;\n }\n}\n\n/**\n * Renders a single Mermaid diagram asynchronously.\n * The diagram code is passed via the `code` prop (raw text from the fenced block).\n */\nconst MermaidDiagram: React.FC<{code: string}> = ({code}) => {\n const [svg, setSvg] = useState<string>(\"\");\n // Each diagram needs a unique id for mermaid.render()\n const id = useRef(`mermaid-${Math.random().toString(36).slice(2)}`).current;\n\n useEffect(() => {\n ensureMermaid();\n let cancelled = false;\n mermaid\n .render(id, code)\n .then(({svg: rendered}) => {\n if (!cancelled) {\n // Append an override <style> inside the SVG so it takes precedence\n // over Mermaid's own injected styles (which come earlier in the SVG).\n // This removes the opaque background box on edge labels.\n // Note: avoid </x> in template literals — TSX parser misreads them.\n const svgClose = \"<\" + \"/svg>\";\n const styleClose = \"<\" + \"/style>\";\n const overrideStyle =\n \"<style>\" +\n // Reset font size so page CSS can't make text overflow node boxes\n \".mermaid-diagram svg,foreignObject div,foreignObject span,foreignObject p{\" +\n \"font-size:14px!important}\" +\n // White edge-label background with padding so descenders aren't clipped\n \".edgeLabel,.edgeLabel span,.edgeLabel div,.edgeLabel p{\" +\n \"background:#ffffff!important;\" +\n \"background-color:#ffffff!important;\" +\n \"padding:2px 4px!important;\" +\n \"line-height:1.4!important}\" +\n styleClose;\n setSvg(rendered.replace(svgClose, overrideStyle + svgClose));\n }\n })\n .catch((err) => {\n if (!cancelled) setSvg(`<pre style=\"color:red\">${String(err)}</pre>`);\n });\n return () => {\n cancelled = true;\n };\n }, [id, code]);\n\n if (!svg) return null;\n // mermaid.render() returns a sanitised SVG string — safe to inject directly.\n return (\n <div className=\"mermaid-diagram\" dangerouslySetInnerHTML={{__html: svg}} />\n );\n};\n\n/**\n * Default component overrides applied to every MarkdownViewer instance.\n *\n * Goals:\n * - Fenced ```mermaid``` blocks → rendered as Mermaid SVG diagrams\n * - Fenced code blocks → GitHub-like appearance (CSS + rehype-highlight)\n * - Inline `code` spans → subtle muted-pill style (CSS)\n *\n * The `node` prop injected by react-markdown is destructured and discarded\n * here so it doesn't propagate to native DOM elements and trigger React\n * \"unknown prop\" warnings.\n */\nconst DEFAULT_COMPONENTS: Components = {\n pre: ({node: _node, children, ...rest}) => <pre {...rest}>{children}</pre>,\n code: ({node: _node, className, children, ...rest}) => {\n const lang = /language-(\\w+)/.exec(className ?? \"\")?.[1];\n if (lang === \"mermaid\") {\n return <MermaidDiagram code={String(children).replace(/\\n$/, \"\")} />;\n }\n return (\n <code className={className} {...rest}>\n {children}\n </code>\n );\n },\n};\n\nexport const MarkdownViewerComponent = (\n props: PiCardProps<MarkdownViewerProps>,\n): React.ReactNode => {\n const {\n source,\n path,\n maxBodyLength = -1,\n remarkPlugins = [remarkMath, remarkGfm],\n rehypePlugins = [rehypeKatex, rehypeHighlight],\n remarkRehypeOptions,\n components,\n className,\n style = {},\n cardName,\n _cls,\n } = props;\n\n const [fetchedText, setFetchedText] = useState(\"\");\n const text = source || fetchedText;\n\n if (path && fetchedText === \"\") {\n fetch(path)\n .then((response) => response.text())\n .then((fetched) => setFetchedText(fetched));\n }\n\n function decode(s: string): string {\n try {\n return decodeURI(s);\n } catch {\n // may fail if actually not encoded and contains '%'\n return s;\n }\n }\n\n function shortenBody(body: string): string {\n if (body.length <= maxBodyLength) return body;\n\n const shortened = body\n .split(\" \")\n .reduce(\n (p, el) => {\n const [a, cnt] = p;\n if (cnt < maxBodyLength) {\n return [a.concat(el), cnt + el.length + 1] as [string[], number];\n } else {\n return p;\n }\n },\n [[], 0] as [string[], number],\n )[0]\n .join(\" \");\n return shortened.length < body.length ? `${shortened}...` : body;\n }\n\n const decoded = decode(text);\n const content = maxBodyLength > 0 ? shortenBody(decoded) : decoded;\n\n // Merge caller-supplied component overrides on top of our defaults.\n const mergedComponents: Components = {...DEFAULT_COMPONENTS, ...components};\n\n return (\n <div\n className={_cls(\"root\", className)}\n style={style}\n data-pihanga={cardName}\n >\n <ReactMarkdown\n remarkPlugins={remarkPlugins}\n rehypePlugins={\n rehypePlugins as Parameters<typeof ReactMarkdown>[0][\"rehypePlugins\"]\n }\n remarkRehypeOptions={remarkRehypeOptions}\n components={mergedComponents}\n className={`${_cls(\"inner\")} md-prose`}\n >\n {content}\n </ReactMarkdown>\n </div>\n );\n};\n",
|
|
41
|
-
"type": "registry:component",
|
|
42
|
-
"target": "src/cards/markdownViewer/markdownViewer.component.tsx"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"path": "cards/markdownViewer/markdownViewer.types.ts",
|
|
46
|
-
"content": "import {createCardDeclaration} from \"@pihanga2/core\";\nimport type {PluggableList} from \"unified\";\nimport type {Components} from \"react-markdown\";\n\nexport const MARKDOWN_CARD = \"markdown-viewer\";\n\nexport const MarkdownViewer =\n createCardDeclaration<MarkdownViewerProps>(MARKDOWN_CARD);\n\nexport type MarkdownViewerProps<S = object> = {\n /** Markdown source string to render directly. */\n source?: string;\n /** URL/path to a file whose text content will be fetched and rendered. */\n path?: string;\n /**\n * Truncate rendered text to approximately this many characters (word boundary).\n * A negative value (default: -1) disables truncation.\n */\n maxBodyLength?: number;\n\n remarkPlugins?: PluggableList;\n rehypePlugins?: PluggableList;\n remarkRehypeOptions?: object;\n\n /**\n * Custom element renderers passed directly to `react-markdown`.\n * Merged on top of the card's built-in defaults (code/pre styling).\n * Use this to override how individual HTML elements are rendered.\n */\n components?: Components;\n\n /** Additional CSS class to apply to the outer wrapper div. */\n className?: string;\n style?: S;\n};\n",
|
|
47
|
-
"type": "registry:component",
|
|
48
|
-
"target": "src/cards/markdownViewer/markdownViewer.types.ts"
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
}
|
package/r/menu.json
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "menu",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga menu card component",
|
|
6
|
-
"dependencies": [
|
|
7
|
-
"@radix-ui/react-menubar@^1.1.16",
|
|
8
|
-
"lucide-react@^0.513.0"
|
|
9
|
-
],
|
|
10
|
-
"registryDependencies": [
|
|
11
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
12
|
-
"menubar"
|
|
13
|
-
],
|
|
14
|
-
"files": [
|
|
15
|
-
{
|
|
16
|
-
"path": "cards/menu/index.ts",
|
|
17
|
-
"content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {MenuComponent} from \"./menu.component\";\nimport {MENU_ACTION, MENU_CARD} from \"./menu.types\";\n\nexport * from \"./menu.types\";\n\nregisterCardComponent({\n name: MENU_CARD,\n component: MenuComponent,\n events: actionTypesToEvents(MENU_ACTION),\n});\n",
|
|
18
|
-
"type": "registry:component",
|
|
19
|
-
"target": "src/cards/menu/index.ts"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"path": "cards/menu/menu.component.tsx",
|
|
23
|
-
"content": "import React from \"react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {\n Menubar,\n MenubarContent,\n MenubarItem,\n MenubarMenu,\n MenubarSeparator,\n MenubarTrigger,\n} from \"@/components/ui/menubar\";\nimport type {MenuEvents, MenuProps} from \"./menu.types\";\n\nexport const MenuComponent = (\n props: PiCardProps<MenuProps, MenuEvents>,\n): React.ReactNode => {\n const {menuButton, items, className, cardName, onClicked} = props;\n\n return (\n <Menubar className={className} data-pihanga={cardName}>\n <MenubarMenu>\n <MenubarTrigger disabled={menuButton.isDisabled}>\n {menuButton.label}\n </MenubarTrigger>\n <MenubarContent>\n {items.map((item, idx) => {\n if (item === null) {\n return <MenubarSeparator key={idx} />;\n }\n return (\n <MenubarItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => onClicked({itemID: item.id})}\n >\n {item.title ?? item.id}\n </MenubarItem>\n );\n })}\n </MenubarContent>\n </MenubarMenu>\n </Menubar>\n );\n};\n",
|
|
24
|
-
"type": "registry:component",
|
|
25
|
-
"target": "src/cards/menu/menu.component.tsx"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"path": "cards/menu/menu.types.ts",
|
|
29
|
-
"content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\nimport type {ColorT, DecoratorT, SizeT, VariantT} from \"../types\";\n\nexport const MENU_CARD = \"shad/menu\";\nexport const Menu = createCardDeclaration<MenuProps, MenuEvents>(MENU_CARD);\n\nexport const MENU_ACTION = registerActions(MENU_CARD, [\"clicked\"]);\n\nexport const onMenuClicked = createOnAction<MenuClickEvent>(\n MENU_ACTION.CLICKED,\n);\n\n// ---------------------------------------------------------------------------\n// Shared button props (re-used by trigger buttons)\n// ---------------------------------------------------------------------------\n\nexport type BaseButtonProps = {\n label: string;\n tooltip?: string;\n isDisabled?: boolean;\n isLoading?: boolean;\n loadingPosition?: \"center\" | \"start\" | \"end\";\n size?: SizeT;\n color?: ColorT;\n variant?: VariantT;\n startDecorator?: DecoratorT;\n endDecorator?: DecoratorT;\n};\n\n// ---------------------------------------------------------------------------\n// Menu item types\n// ---------------------------------------------------------------------------\n\nexport type MenuItemT = {\n id: string;\n title?: string;\n disabled?: boolean;\n color?: ColorT;\n variant?: VariantT;\n startDecorator?: DecoratorT;\n endDecorator?: DecoratorT;\n};\n\n/** A `null` entry renders as a visual separator between menu items. */\nexport type MenuSeparatorT = null;\n\n// ---------------------------------------------------------------------------\n// Placement\n// ---------------------------------------------------------------------------\n\nexport type PlacementT =\n | \"auto-end\"\n | \"auto-start\"\n | \"auto\"\n | \"bottom-end\"\n | \"bottom-start\"\n | \"bottom\"\n | \"left-end\"\n | \"left-start\"\n | \"left\"\n | \"right-end\"\n | \"right-start\"\n | \"right\"\n | \"top-end\"\n | \"top-start\"\n | \"top\";\n\n// ---------------------------------------------------------------------------\n// Card props & events\n// ---------------------------------------------------------------------------\n\nexport type MenuProps<S = unknown, T = unknown> = {\n /** Configuration for the trigger button that opens the menu. */\n menuButton: BaseButtonProps;\n /** Ordered list of menu items; `null` entries render as separators. */\n items: (MenuItemT | MenuSeparatorT)[];\n placement?: PlacementT;\n size?: SizeT;\n color?: ColorT;\n variant?: VariantT;\n className?: string;\n style?: S;\n theme?: T;\n};\n\nexport type MenuClickEvent = {\n /** The `id` of the menu item that was clicked. */\n itemID: string;\n};\n\nexport type MenuEvents = {\n onClicked: MenuClickEvent;\n};\n",
|
|
30
|
-
"type": "registry:component",
|
|
31
|
-
"target": "src/cards/menu/menu.types.ts"
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
}
|
package/r/modeToggle.json
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "modeToggle",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga modeToggle card component",
|
|
6
|
-
"dependencies": [
|
|
7
|
-
"class-variance-authority@^0.7.1",
|
|
8
|
-
"lucide-react@^0.513.0",
|
|
9
|
-
"radix-ui@^1.4.3"
|
|
10
|
-
],
|
|
11
|
-
"registryDependencies": [
|
|
12
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
13
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-ui-extras.json",
|
|
14
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-theme-provider.json"
|
|
15
|
-
],
|
|
16
|
-
"files": [
|
|
17
|
-
{
|
|
18
|
-
"path": "cards/modeToggle/index.ts",
|
|
19
|
-
"content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {ModeToggleComponent} from \"./mode-toggle.component\";\nimport {MODE_TOGGLE_ACTION, MODE_TOGGLE_CARD} from \"./mode-toggle.types\";\n\nexport * from \"./mode-toggle.types\";\n\nregisterCardComponent({\n name: MODE_TOGGLE_CARD,\n component: ModeToggleComponent,\n events: actionTypesToEvents(MODE_TOGGLE_ACTION),\n});\n",
|
|
20
|
-
"type": "registry:component",
|
|
21
|
-
"target": "src/cards/modeToggle/index.ts"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"path": "cards/modeToggle/mode-toggle.component.tsx",
|
|
25
|
-
"content": "import React from \"react\";\nimport {Moon, Sun} from \"lucide-react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {Button} from \"@/components/ui/button\";\nimport {useTheme} from \"@/components/theme-provider\";\nimport type {ModeToggleEvents, ModeToggleProps} from \"./mode-toggle.types\";\n\nexport const ModeToggleComponent = (\n props: PiCardProps<ModeToggleProps, ModeToggleEvents>,\n): React.ReactNode => {\n const {setTheme, theme} = useTheme();\n const {variant, className, onModeChanged, cardName} = props;\n\n function onClick() {\n const mode = theme === \"dark\" ? \"light\" : \"dark\";\n setTheme(mode);\n onModeChanged({mode});\n }\n\n return (\n <Button\n variant={variant ?? \"outline\"}\n size=\"icon\"\n onClick={onClick}\n className={className}\n data-pihanga={cardName}\n >\n <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n <span className=\"sr-only\">Toggle theme</span>\n </Button>\n );\n};\n",
|
|
26
|
-
"type": "registry:component",
|
|
27
|
-
"target": "src/cards/modeToggle/mode-toggle.component.tsx"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"path": "cards/modeToggle/mode-toggle.types.ts",
|
|
31
|
-
"content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\nimport type {VariantT} from \"../types\";\n\nexport const MODE_TOGGLE_CARD = \"shad/mode-toggle\";\nexport const ModeToggle = createCardDeclaration<\n ModeToggleProps,\n ModeToggleEvents\n>(MODE_TOGGLE_CARD);\n\nexport const MODE_TOGGLE_ACTION = registerActions(MODE_TOGGLE_CARD, [\n \"mode_changed\",\n]);\n\nexport const onModeToggleChanged = createOnAction<ModeChangeEvent>(\n MODE_TOGGLE_ACTION.MODE_CHANGED,\n);\n\n// ---------------------------------------------------------------------------\n// Card props & events\n// ---------------------------------------------------------------------------\n\nexport type ModeToggleProps = {\n /** Visual variant of the toggle button. Defaults to `\"outline\"`. */\n variant?: VariantT;\n className?: string;\n};\n\nexport type ModeChangeEvent = {\n mode: \"light\" | \"dark\" | \"system\";\n};\n\nexport type ModeToggleEvents = {\n onModeChanged: ModeChangeEvent;\n};\n",
|
|
32
|
-
"type": "registry:component",
|
|
33
|
-
"target": "src/cards/modeToggle/mode-toggle.types.ts"
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
}
|
package/r/navbarSearch.json
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "navbarSearch",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Pihanga navbarSearch card component",
|
|
6
|
-
"dependencies": [],
|
|
7
|
-
"registryDependencies": [
|
|
8
|
-
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
9
|
-
"input"
|
|
10
|
-
],
|
|
11
|
-
"files": [
|
|
12
|
-
{
|
|
13
|
-
"path": "cards/navbarSearch/index.ts",
|
|
14
|
-
"content": "import {\n actionTypesToEvents,\n registerCardComponent,\n} from \"@pihanga2/core\";\n\nimport {Component} from \"./navbarSearch.component\";\nimport {NAVBAR_SEARCH_ACTION, NAVBAR_SEARCH_CARD} from \"./navbarSearch.type\";\n\nexport * from \"./navbarSearch.type\";\n\nregisterCardComponent({\n name: NAVBAR_SEARCH_CARD,\n component: Component,\n events: actionTypesToEvents(NAVBAR_SEARCH_ACTION),\n});\n",
|
|
15
|
-
"type": "registry:component",
|
|
16
|
-
"target": "src/cards/navbarSearch/index.ts"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"path": "cards/navbarSearch/navbarSearch.component.tsx",
|
|
20
|
-
"content": "import {Input} from \"@/components/ui/input\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport type {\n NavbarSearchEvents,\n NavbarSearchProps,\n} from \"./navbarSearch.type\";\n\nexport const Component = (\n props: PiCardProps<NavbarSearchProps, NavbarSearchEvents>,\n): React.ReactNode => {\n const {\n searchItems,\n placeholder = \"Load Spec...\",\n onSearchSubmit,\n onSearchItems,\n onSearchFocus,\n cardName,\n } = props;\n const listId = searchItems?.length ? `${cardName}-search` : undefined;\n\n const submitSearch = (value: string, input?: HTMLInputElement | null) => {\n const search = value.trim();\n if (!search) return;\n onSearchSubmit?.({search});\n if (input) {\n input.value = \"\";\n emitSearchInput(\"\");\n }\n input?.blur();\n };\n\n const emitSearchInput = (value: string) => {\n onSearchItems?.({search: value});\n };\n\n const maybeSubmit = (value: string, input?: HTMLInputElement | null) => {\n if (searchItems?.includes(value)) {\n submitSearch(value, input);\n }\n };\n\n const openNativeSuggestions = (input: HTMLInputElement | null) => {\n if (!input) {\n return;\n }\n\n const pickerInput = input as HTMLInputElement & {\n showPicker?: () => void;\n };\n pickerInput.showPicker?.();\n };\n\n const blockManualTyping = (event: React.KeyboardEvent<HTMLInputElement>) => {\n const allowedKeys = new Set([\n \"Tab\",\n \"Enter\",\n \"Escape\",\n \"ArrowDown\",\n \"ArrowUp\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Home\",\n \"End\",\n \"Shift\",\n \"Control\",\n \"Alt\",\n \"Meta\",\n ]);\n\n if (event.ctrlKey || event.metaKey || event.altKey) {\n return;\n }\n\n if (allowedKeys.has(event.key)) {\n if (event.key === \"ArrowDown\") {\n openNativeSuggestions(event.currentTarget);\n }\n return;\n }\n\n event.preventDefault();\n };\n\n return (\n <form\n onSubmit={(el) => {\n el.preventDefault();\n const data = new FormData(el.currentTarget);\n submitSearch(\n String(data.get(\"search\") || \"\"),\n el.currentTarget.elements.namedItem(\"search\") as HTMLInputElement | null,\n );\n }}\n autoComplete=\"off\"\n className=\"ml-auto flex-1 sm:flex-initial\"\n data-pihanga={cardName}\n >\n <div className=\"relative\">\n <Input\n type=\"search\"\n placeholder={placeholder}\n name=\"search\"\n list={listId}\n autoComplete=\"off\"\n className=\"sm:w-[300px] md:w-[200px] lg:w-[300px]\"\n onFocus={() => {\n onSearchFocus?.({});\n }}\n onClick={(e) => {\n openNativeSuggestions(e.currentTarget);\n }}\n onBeforeInput={(e) => {\n e.preventDefault();\n }}\n onKeyDown={blockManualTyping}\n onInput={(e) => {\n const input = e.target as HTMLInputElement;\n const value = input.value;\n emitSearchInput(value);\n maybeSubmit(value, input);\n }}\n onChange={(e) => {\n const input = e.target as HTMLInputElement;\n const value = input.value;\n emitSearchInput(value);\n maybeSubmit(value, input);\n }}\n onBlur={(e) => {\n const input = e.target as HTMLInputElement;\n const value = input.value;\n maybeSubmit(value, input);\n }}\n />\n {listId && (\n <datalist id={listId}>\n {searchItems!.map((item) => (\n <option key={item} value={item} />\n ))}\n </datalist>\n )}\n </div>\n </form>\n );\n};\n",
|
|
21
|
-
"type": "registry:component",
|
|
22
|
-
"target": "src/cards/navbarSearch/navbarSearch.component.tsx"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"path": "cards/navbarSearch/navbarSearch.type.ts",
|
|
26
|
-
"content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const NAVBAR_SEARCH_CARD = \"navbarSearch\";\nexport const NavbarSearch = createCardDeclaration<\n NavbarSearchProps,\n NavbarSearchEvents\n>(NAVBAR_SEARCH_CARD);\n\nexport const NAVBAR_SEARCH_ACTION = registerActions(NAVBAR_SEARCH_CARD, [\n \"search_submit\",\n \"search_items\",\n \"search_focus\",\n]);\n\nexport const onNavbarSearchSubmit =\n createOnAction<NavbarSearchEvent>(NAVBAR_SEARCH_ACTION.SEARCH_SUBMIT);\nexport const onNavbarSearchItems = createOnAction<NavbarSearchInputEvent>(\n NAVBAR_SEARCH_ACTION.SEARCH_ITEMS,\n);\nexport const onNavbarSearchFocus = createOnAction<NavbarSearchFocusEvent>(\n NAVBAR_SEARCH_ACTION.SEARCH_FOCUS,\n);\n\nexport type NavbarSearchProps = {\n searchItems?: string[];\n placeholder?: string;\n};\n\nexport type NavbarSearchEvent = {search: string};\nexport type NavbarSearchInputEvent = {search: string};\nexport type NavbarSearchFocusEvent = Record<string, never>;\n\nexport type NavbarSearchEvents = {\n onSearchSubmit: NavbarSearchEvent;\n onSearchItems?: NavbarSearchInputEvent;\n onSearchFocus?: NavbarSearchFocusEvent;\n};\n",
|
|
27
|
-
"type": "registry:component",
|
|
28
|
-
"target": "src/cards/navbarSearch/navbarSearch.type.ts"
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
}
|