@pihanga2/shadcn 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/AGENT.building-cards.md +413 -0
  2. package/AGENT.md +57 -0
  3. package/AGENT.using-cards.md +1051 -0
  4. package/cards/box/box.component.js +2 -2
  5. package/cards/box/box.component.js.map +1 -1
  6. package/cards/box/box.types.d.ts +1 -4
  7. package/cards/box/box.types.js.map +1 -1
  8. package/cards/button/button.types.d.ts +3 -0
  9. package/cards/button/button.types.js +2 -2
  10. package/cards/button/button.types.js.map +1 -1
  11. package/cards/button/index.js +5 -5
  12. package/cards/chartGraph/chartGraph.component.d.ts +4 -0
  13. package/cards/chartGraph/chartGraph.types.d.ts +102 -0
  14. package/cards/chartGraph/index.d.ts +1 -0
  15. package/cards/conditional/conditional.component.d.ts +14 -3
  16. package/cards/conditional/conditional.component.js +17 -9
  17. package/cards/conditional/conditional.component.js.map +1 -1
  18. package/cards/conditional/conditional.types.d.ts +100 -8
  19. package/cards/conditional/conditional.types.js.map +1 -1
  20. package/cards/core-index.js +31 -31
  21. package/cards/fileDrop/fileDrop.component.d.ts +5 -0
  22. package/cards/fileDrop/fileDrop.types.d.ts +43 -0
  23. package/cards/fileDrop/index.d.ts +2 -0
  24. package/cards/graphin/contextMenu.component.d.ts +28 -0
  25. package/cards/graphin/eventDispatcher.component.d.ts +8 -0
  26. package/cards/graphin/graphin.component.d.ts +2 -2
  27. package/cards/graphin/graphin.playground-cards.d.ts +1 -0
  28. package/cards/graphin/graphin.types.d.ts +444 -4
  29. package/cards/graphin/graphinOpHandler.component.d.ts +8 -0
  30. package/cards/graphin/index.d.ts +1 -0
  31. package/cards/graphin/tooltip.component.d.ts +13 -1
  32. package/cards/icons.js +7 -3
  33. package/cards/icons.js.map +1 -1
  34. package/cards/index.d.ts +1 -0
  35. package/cards/slider/index.d.ts +1 -0
  36. package/cards/slider/slider.component.d.ts +4 -0
  37. package/cards/slider/slider.types.d.ts +102 -0
  38. package/cards/sliderValue/index.d.ts +1 -0
  39. package/cards/sliderValue/sliderValue.component.d.ts +15 -0
  40. package/cards/sliderValue/sliderValue.types.d.ts +21 -0
  41. package/cards/stack/stack.types.d.ts +19 -22
  42. package/cards/stack/stack.types.js +1 -1
  43. package/cards/stack/stack.types.js.map +1 -1
  44. package/cards/tabs/index.js +4 -4
  45. package/cards/tabs/tabs.component.js +51 -25
  46. package/cards/tabs/tabs.component.js.map +1 -1
  47. package/cards/tabs/tabs.types.d.ts +12 -0
  48. package/cards/tabs/tabs.types.js +2 -2
  49. package/cards/tabs/tabs.types.js.map +1 -1
  50. package/cards/types.d.ts +4 -0
  51. package/cards/types.js.map +1 -1
  52. package/components/hooks/use-breakpoint.d.ts +49 -0
  53. package/components/hooks/use-breakpoint.js +73 -0
  54. package/components/hooks/use-breakpoint.js.map +1 -0
  55. package/package.json +9 -1
  56. package/pihanga-shadcn.css +1 -1
  57. package/mountain-snow.svg +0 -4
  58. package/r/badge.json +0 -35
  59. package/r/box.json +0 -26
  60. package/r/button.json +0 -39
  61. package/r/checkbox.json +0 -31
  62. package/r/conditional.json +0 -30
  63. package/r/dataTable.json +0 -43
  64. package/r/dialog.json +0 -36
  65. package/r/dropDownMenu.json +0 -47
  66. package/r/field.json +0 -35
  67. package/r/flexGrid.json +0 -32
  68. package/r/form.json +0 -37
  69. package/r/framework.json +0 -28
  70. package/r/graphin.json +0 -42
  71. package/r/input.json +0 -35
  72. package/r/jsonViewer.json +0 -40
  73. package/r/list.json +0 -39
  74. package/r/loadingOverlay.json +0 -39
  75. package/r/loadingSkeleton.json +0 -30
  76. package/r/markdownViewer.json +0 -51
  77. package/r/menu.json +0 -34
  78. package/r/modeToggle.json +0 -36
  79. package/r/navbarSearch.json +0 -31
  80. package/r/pageWithNavbar.json +0 -46
  81. package/r/pasteTarget.json +0 -41
  82. package/r/pihanga-base.json +0 -11
  83. package/r/pihanga-cards-icons.json +0 -16
  84. package/r/pihanga-cards-types.json +0 -16
  85. package/r/pihanga-hook-use-is-touch-device.json +0 -15
  86. package/r/pihanga-lib-utils.json +0 -18
  87. package/r/pihanga-theme-provider.json +0 -25
  88. package/r/pihanga-ui-extras.json +0 -68
  89. package/r/registry.json +0 -171
  90. package/r/resizable.json +0 -35
  91. package/r/select.json +0 -35
  92. package/r/stack.json +0 -27
  93. package/r/stepper.json +0 -39
  94. package/r/switch.json +0 -36
  95. package/r/tabs.json +0 -33
  96. package/r/textField.json +0 -32
  97. package/r/toast.json +0 -32
  98. package/r/toggleGroup.json +0 -35
  99. package/r/typography.json +0 -31
package/r/flexGrid.json DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "flexGrid",
4
- "type": "registry:component",
5
- "description": "Pihanga flexGrid 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
- ],
12
- "files": [
13
- {
14
- "path": "cards/flexGrid/flexGrid.component.tsx",
15
- "content": "import React from \"react\";\nimport {Card, PiCardProps, PiCardRef} from \"@pihanga2/core\";\nimport {FlexGridProps} from \"./flexGrid.types\";\nimport clsx from \"clsx\";\n\nexport const FlexGridComponent = (\n props: PiCardProps<FlexGridProps>,\n): React.ReactNode => {\n const {\n cardName,\n cards = {},\n template,\n height = \"auto\", //'100vh',\n margin = 0,\n overflow = \"hidden\", // 'scroll',\n style,\n className,\n _cls,\n } = props;\n\n // console.log(\"AREA\", area)\n const _style = {\n display: \"grid\",\n gridGap: template.gap || \"10px\",\n height,\n margin,\n width: \"100%\",\n ...style?.root,\n };\n\n if (template.area) {\n const areaRows = template.area.map((rn) => `\"${rn.join(\" \")}\"`);\n _style.gridTemplateAreas = areaRows.join(\" \");\n }\n if (template.rows) {\n _style.gridTemplateRows = template.rows.join(\" \");\n }\n if (template.columns) {\n _style.gridTemplateColumns = template.columns.join(\" \");\n }\n\n function renderGridCard(v: [string, PiCardRef]): React.ReactElement {\n const [name, gridCard] = v;\n const _style = {\n overflow,\n ...style?.item,\n };\n if (template.area) {\n _style.gridArea = name;\n }\n return (\n <div style={_style} data-pihanga-grid={name} key={name}>\n <Card cardName={gridCard} parentCard={cardName} />\n </div>\n );\n }\n\n return (\n <div\n style={_style}\n className={clsx(_cls(\"root\"), className)}\n data-pihanga={cardName}\n >\n {Object.entries(cards).map(renderGridCard)}\n </div>\n );\n};\n",
16
- "type": "registry:component",
17
- "target": "src/cards/flexGrid/flexGrid.component.tsx"
18
- },
19
- {
20
- "path": "cards/flexGrid/flexGrid.types.ts",
21
- "content": "import { createCardDeclaration, PiCardRef } from \"@pihanga2/core\"\n\nexport const FLEX_GRID_CARD = \"flex_grid\"\nexport const FlexGrid = createCardDeclaration<FlexGridProps>(FLEX_GRID_CARD)\n\nexport type FlexGridProps = {\n cards: { [name: string]: PiCardRef }\n template: TemplateT\n height?: string\n margin?: string\n overflow?: string\n\n style?: {\n root?: React.CSSProperties\n item?: React.CSSProperties\n }\n className?: string\n}\n\n// body {\n// display: grid;\n// grid-template-areas:\n// \"header header header\"\n// \"nav article ads\"\n// \"footer footer footer\";\n// grid-template-rows: 60px 1fr 60px;\n// grid-template-columns: 20% 1fr 15%;\n// grid-gap: 10px;\n// height: 100vh;\n// margin: 0;\n// }\n\n// https://css-tricks.com/snippets/css/complete-guide-grid/\nexport type TemplateT = {\n area?: string[][] // name of card dict\n rows?: string[] // grid-template-rows (e.g [\"min-content\", \"1fr\", \"min-content\"])\n columns?: string[] // grid-template-cols (e.g. [\"1fr\", \"50px\", \"1fr\", \"1fr\"])\n gap?: string\n}\n",
22
- "type": "registry:component",
23
- "target": "src/cards/flexGrid/flexGrid.types.ts"
24
- },
25
- {
26
- "path": "cards/flexGrid/index.ts",
27
- "content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {FlexGridComponent} from \"./flexGrid.component\";\nimport {FLEX_GRID_CARD} from \"./flexGrid.types\";\n\nexport * from \"./flexGrid.types\";\n\nregisterCardComponent({\n name: FLEX_GRID_CARD,\n component: FlexGridComponent,\n});\n",
28
- "type": "registry:component",
29
- "target": "src/cards/flexGrid/index.ts"
30
- }
31
- ]
32
- }
package/r/form.json DELETED
@@ -1,37 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "form",
4
- "type": "registry:component",
5
- "description": "Pihanga form card component",
6
- "dependencies": [],
7
- "registryDependencies": [
8
- "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
9
- "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-lib-utils.json"
10
- ],
11
- "files": [
12
- {
13
- "path": "cards/form/form.component.tsx",
14
- "content": "import React, {useState, useCallback} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {cn} from \"@/lib/utils\";\nimport {FormContext, type FormContextValue} from \"./form.context\";\nimport type {PiFormEvents, PiFormProps} from \"./form.types\";\n\nexport const FormComponent = (\n props: PiCardProps<PiFormProps, PiFormEvents>,\n): React.ReactNode => {\n const {\n id,\n content = [],\n initialValues = {},\n submitLabel = \"Submit\",\n className,\n cardName,\n onSubmitted,\n } = props;\n\n const [formData, setFormData] = useState<Record<string, unknown>>(() => ({\n ...initialValues,\n }));\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const handleChange = useCallback((fieldName: string, value: unknown) => {\n setFormData((prev) => ({...prev, [fieldName]: value}));\n // Clear validation error when the user modifies the field\n setErrors((prev) => {\n if (!prev[fieldName]) return prev;\n const next = {...prev};\n delete next[fieldName];\n return next;\n });\n }, []);\n\n const setError = useCallback((fieldName: string, error: string | null) => {\n setErrors((prev) => {\n if (error === null) {\n const next = {...prev};\n delete next[fieldName];\n return next;\n }\n return {...prev, [fieldName]: error};\n });\n }, []);\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n onSubmitted({id, formData});\n },\n [id, formData, onSubmitted],\n );\n\n const contextValue: FormContextValue = {\n formData,\n errors,\n handleChange,\n setError,\n isInForm: true,\n };\n\n return (\n <FormContext.Provider value={contextValue}>\n <form\n onSubmit={handleSubmit}\n className={cn(\"flex flex-col gap-4\", className)}\n data-pihanga={cardName}\n >\n {content.map((cref, idx) => (\n <Card key={idx} cardName={cref} parentCard={cardName} />\n ))}\n <button\n type=\"submit\"\n className=\"inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\"\n >\n {submitLabel}\n </button>\n </form>\n </FormContext.Provider>\n );\n};\n",
15
- "type": "registry:component",
16
- "target": "src/cards/form/form.component.tsx"
17
- },
18
- {
19
- "path": "cards/form/form.context.tsx",
20
- "content": "import React, {useContext} from \"react\";\n\n/**\n * Shape of the data provided by FormContext.\n * When inside a <Form> card, isInForm is true and all fields are populated.\n * When outside (fallback), isInForm is false.\n */\nexport interface FormContextValue {\n formData: Record<string, unknown>;\n errors: Record<string, string>;\n handleChange: (fieldName: string, value: unknown) => void;\n setError: (fieldName: string, error: string | null) => void;\n isInForm: boolean;\n}\n\nconst FALLBACK_CONTEXT: FormContextValue = {\n formData: {},\n errors: {},\n handleChange: () => {},\n setError: () => {},\n isInForm: false,\n};\n\nexport const FormContext = React.createContext<FormContextValue | null>(null);\n\n/**\n * Hook that reads the nearest FormContext.\n * Returns a fallback (isInForm=false) when called outside a <Form> card.\n *\n * Usage in field components:\n * const form = useFormContext();\n * const value = form.isInForm && name ? form.formData[name] ?? '' : propValue;\n */\nexport const useFormContext = (): FormContextValue => {\n const ctx = useContext(FormContext);\n return ctx ?? FALLBACK_CONTEXT;\n};\n",
21
- "type": "registry:component",
22
- "target": "src/cards/form/form.context.tsx"
23
- },
24
- {
25
- "path": "cards/form/form.types.ts",
26
- "content": "import {\n createCardDeclaration,\n createOnAction,\n type PiCardRef,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_FORM_CARD = \"pi/form\";\n\nexport const Form = createCardDeclaration<PiFormProps, PiFormEvents>(\n PI_FORM_CARD,\n);\n\nexport const PI_FORM_ACTION = registerActions(PI_FORM_CARD, [\"submitted\"]);\n\nexport const onPiFormSubmitted = createOnAction<PiFormSubmittedEvent>(\n PI_FORM_ACTION.SUBMITTED,\n);\n\nexport type PiFormProps = {\n /**\n * Optional id passed through to events for identification.\n */\n id?: string;\n\n /**\n * Ordered list of field card refs to render inside the form.\n * Each card (e.g. TextField, Checkbox, FormSelect) will be rendered\n * in order and wrapped by a FormContext.Provider so they can read\n * and write shared form state.\n */\n content?: PiCardRef[];\n\n /**\n * Initial values for the form fields, keyed by field name.\n */\n initialValues?: Record<string, unknown>;\n\n /**\n * Label for the submit button.\n * @default \"Submit\"\n */\n submitLabel?: string;\n\n /**\n * Additional CSS classes for the <form> element.\n */\n className?: string;\n};\n\nexport type PiFormSubmittedEvent = {\n id?: string;\n /** Snapshot of form state at the time of submission. */\n formData: Record<string, unknown>;\n};\n\nexport type PiFormEvents = {\n onSubmitted: PiFormSubmittedEvent;\n};\n",
27
- "type": "registry:component",
28
- "target": "src/cards/form/form.types.ts"
29
- },
30
- {
31
- "path": "cards/form/index.ts",
32
- "content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {FormComponent} from \"./form.component\";\nimport {PI_FORM_ACTION, PI_FORM_CARD} from \"./form.types\";\n\nexport * from \"./form.types\";\nexport * from \"./form.context\";\n\nregisterCardComponent({\n name: PI_FORM_CARD,\n component: FormComponent,\n events: actionTypesToEvents(PI_FORM_ACTION),\n});\n",
33
- "type": "registry:component",
34
- "target": "src/cards/form/index.ts"
35
- }
36
- ]
37
- }
package/r/framework.json DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "framework",
4
- "type": "registry:component",
5
- "description": "Pihanga framework card component",
6
- "dependencies": [
7
- "@radix-ui/react-tooltip@^1.2.8"
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-theme-provider.json",
12
- "tooltip"
13
- ],
14
- "files": [
15
- {
16
- "path": "cards/framework/framework.component.tsx",
17
- "content": "import * as React from \"react\";\nimport {StrictMode} from \"react\";\nimport {ThemeProvider} from \"@/components/theme-provider/theme-provider.component\";\n\nimport {Card, type PiCardProps, type WindowProps} from \"@pihanga2/core\";\nimport {TooltipProvider} from \"@/components/ui/tooltip\";\n\nexport const Component = (props: PiCardProps<WindowProps>): React.ReactNode => {\n const {page, theme = \"dark\", cardName} = props;\n\n return (\n <StrictMode>\n <ThemeProvider defaultTheme={theme} storageKey=\"shadcn-ui-theme\">\n <TooltipProvider>\n <Card cardName={page} parentCard={cardName} />\n </TooltipProvider>\n </ThemeProvider>\n </StrictMode>\n );\n};\n",
18
- "type": "registry:component",
19
- "target": "src/cards/framework/framework.component.tsx"
20
- },
21
- {
22
- "path": "cards/framework/index.ts",
23
- "content": "import {\n type WindowProps,\n createCardDeclaration,\n registerCardComponent,\n} from \"@pihanga2/core\";\n\nimport {Component} from \"./framework.component\";\n\nconst CARD_TYPE = \"shad/framework\";\nexport const SdFramework = createCardDeclaration<WindowProps>(CARD_TYPE);\n\nregisterCardComponent({\n name: CARD_TYPE,\n component: Component,\n});\n",
24
- "type": "registry:component",
25
- "target": "src/cards/framework/index.ts"
26
- }
27
- ]
28
- }
package/r/graphin.json DELETED
@@ -1,42 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "graphin",
4
- "type": "registry:component",
5
- "description": "Pihanga graphin card component",
6
- "dependencies": [
7
- "@antv/g@^6.3.1",
8
- "@antv/g6@^5.1.1",
9
- "@antv/graphin@^3.0.5",
10
- "clsx@^2.1.1",
11
- "lodash@^4.18.1"
12
- ],
13
- "registryDependencies": [
14
- "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json"
15
- ],
16
- "files": [
17
- {
18
- "path": "cards/graphin/graphin.component.tsx",
19
- "content": "import React from \"react\";\nimport {PiCardProps} from \"@pihanga2/core\";\nimport {Graphin} from \"@antv/graphin\";\n\nimport {\n NodeEvent,\n type GraphOptions,\n type Graph,\n} from \"@antv/g6\";\n\nimport {GraphinProps} from \"./graphin.types\";\nimport clsx from \"clsx\";\nimport {cloneDeep, merge} from \"lodash\";\nimport {TooltipComponent} from \"./tooltip.component\";\n\nexport const GraphinComponent = (\n props: PiCardProps<GraphinProps>,\n): React.ReactNode => {\n const {cardName, data, options, tooltip, style, className, _cls} = props;\n\n const handleReady = (graph: Graph) => {\n console.log(\"Graph ready:\", graph);\n\n // Add event handlers\n graph.on(NodeEvent.CLICK, (evt) => {\n console.log(\"Node clicked:\", evt);\n });\n\n graph.on(\"edge:click\", (evt) => {\n console.log(\"Edge clicked:\", evt);\n });\n };\n\n // console.log(\"AREA\", area)\n const _style = {\n // display: \"grid\",\n // gridGap: template.gap || \"10px\",\n // height,\n // margin,\n // width: \"100%\",\n\n display: \"flex\",\n width: \"100%\",\n ...style?.root,\n };\n\n const defOptions: GraphOptions = {\n data,\n autoResize: true,\n node: {\n style: {\n labelText: (d) => {\n return (d.data?.displayName || d.id) as string;\n },\n lod: {\n 0: {labelFontSize: 10}, // Zoomed out: smaller font\n 1: {labelFontSize: 12}, // Normal: standard font\n 2: {labelFontSize: 6}, // Zoomed in: reduce font size so it doesn't overwhelm\n },\n // labelFontSize: 12, // This is your \"Base\" size\n // labelWordWrap: true,\n // labelWordWrapWidth: 100, // Limits the horizontal growth\n },\n // palette: {\n // type: \"group\",\n // field: \"cluster\",\n // },\n },\n // layout: {\n // type: \"force\", // The layout engine type\n // preventOverlap: true, // Parameters specific to the 'force' engine\n // nodeSize: 30,\n // linkDistance: 100,\n // },\n layout: {\n type: \"force-atlas2\",\n preventOverlap: true,\n kr: 20,\n // center: [250, 250],\n },\n behaviors: [\n \"drag-canvas\",\n \"drag-element\",\n // \"zoom-canvas\",\n {\n type: \"auto-adapt-label\",\n enableAnimation: true,\n throttle: 100,\n padding: 0,\n },\n {\n type: \"zoom-canvas\",\n id: \"zoom-canvas-1\",\n // trigger: {\n // zoomIn: [\"Control\", \"+\"], // Zoom in shortcut\n // zoomOut: [\"Control\", \"-\"], // Zoom out shortcut\n // reset: [\"Control\", \"0\"], // Reset zoom ratio shortcut\n // },\n // trigger: [\"Control\"],\n fixSelectedItems: {fixLabel: true},\n },\n ],\n animation: true,\n };\n\n const mergedOptions = merge(cloneDeep(defOptions), options);\n return (\n <div\n style={_style}\n className={clsx(_cls(\"root\"), className)}\n data-pihanga={cardName}\n >\n <Graphin\n options={mergedOptions}\n onReady={handleReady}\n style={{width: \"inherit\"}}\n >\n {tooltip && (\n <TooltipComponent contentCards={tooltip} parentCard={cardName} />\n )}\n </Graphin>\n </div>\n );\n};\n",
20
- "type": "registry:component",
21
- "target": "src/cards/graphin/graphin.component.tsx"
22
- },
23
- {
24
- "path": "cards/graphin/graphin.types.ts",
25
- "content": "import {createCardDeclaration, PiCardDef} from \"@pihanga2/core\";\nimport {GraphData, GraphOptions} from \"@antv/g6\";\n\nexport const GRAPHIN_CARD = \"graphin\";\nexport const Graphin = createCardDeclaration<GraphinProps>(GRAPHIN_CARD);\n\nexport type GraphinProps = {\n data: GraphData;\n options?: Partial<Omit<GraphOptions, \"data\">>;\n tooltip?: GraphinTooltip;\n style?: {\n root?: React.CSSProperties;\n item?: React.CSSProperties;\n };\n className?: string;\n};\n\nexport type GraphinTooltip = {\n node?: PiCardDef;\n edge?: PiCardDef;\n};\n",
26
- "type": "registry:component",
27
- "target": "src/cards/graphin/graphin.types.ts"
28
- },
29
- {
30
- "path": "cards/graphin/index.ts",
31
- "content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {GraphinComponent} from \"./graphin.component\";\nimport {GRAPHIN_CARD} from \"./graphin.types\";\n\nexport * from \"./graphin.types\";\nexport type {TooltipContext} from \"./tooltip.component\";\n\nregisterCardComponent({\n name: GRAPHIN_CARD,\n component: GraphinComponent,\n});\n",
32
- "type": "registry:component",
33
- "target": "src/cards/graphin/index.ts"
34
- },
35
- {
36
- "path": "cards/graphin/tooltip.component.tsx",
37
- "content": "import {useState, useEffect} from \"react\";\nimport {useGraphin} from \"@antv/graphin\";\nimport type {IPointerEvent} from \"@antv/g6\";\nimport type {DisplayObject} from \"@antv/g\";\nimport {Target} from \"@antv/g6/lib/types\";\nimport {GraphinTooltip} from \"@/cards/graphin/graphin.types\";\nimport {Card} from \"@pihanga2/core\";\n\nexport type TooltipContext<T = Record<string, unknown>> = {\n isEdge?: boolean;\n elementID?: string;\n elementData?: T;\n};\n\ntype tooltipState = TooltipContext & {\n visible: boolean;\n x: number;\n y: number;\n};\n\nexport function TooltipComponent(props: {\n contentCards: GraphinTooltip;\n parentCard: string;\n}): React.ReactNode {\n const {contentCards, parentCard} = props;\n const {graph, isReady} = useGraphin();\n const [tooltip, setTooltip] = useState<tooltipState>({\n visible: false,\n x: 0,\n y: 0,\n });\n\n useEffect(() => {\n if (!isReady || !graph) return;\n\n // Try different event name formats\n const handleNodePointerEnter = (\n evt: IPointerEvent<DisplayObject & Target>,\n ) => {\n const nodeId = evt.target.id;\n const nodeData = graph.getNodeData(nodeId);\n\n setTooltip({\n visible: true,\n x: evt.canvas.x || evt.x,\n y: evt.canvas.y || evt.y,\n isEdge: true,\n elementID: nodeId,\n elementData: nodeData.data,\n });\n };\n\n const handleNodePointerLeave = () => {\n console.log(\"Pointer leave event\");\n setTooltip({\n visible: false,\n x: 0,\n y: 0,\n isEdge: undefined,\n elementID: undefined,\n elementData: undefined,\n });\n };\n\n // Listen to G6 events\n console.log(\">>> tooltip effect\", graph);\n graph.on(\"node:pointerenter\", handleNodePointerEnter);\n graph.on(\"node:pointerleave\", handleNodePointerLeave);\n\n return () => {\n graph.off(\"node:pointerenter\", handleNodePointerEnter);\n graph.off(\"node:pointerleave\", handleNodePointerLeave);\n };\n }, [graph, isReady]);\n\n if (!contentCards.node) return null;\n if (!tooltip.visible) return null;\n\n return (\n <div\n style={{\n position: \"absolute\",\n left: tooltip.x + 10,\n top: tooltip.y + 10,\n background: \"white\",\n padding: \"12px\",\n borderRadius: \"4px\",\n boxShadow: \"0 2px 8px rgba(0,0,0,0.15)\",\n pointerEvents: \"none\",\n zIndex: 1000,\n }}\n >\n {/* <h4 style={{margin: \"0 0 8px 0\"}}>{tooltip.elementID}</h4> */}\n <Card cardName={contentCards.node} parentCard={parentCard} {...tooltip} />\n </div>\n );\n}\n",
38
- "type": "registry:component",
39
- "target": "src/cards/graphin/tooltip.component.tsx"
40
- }
41
- ]
42
- }
package/r/input.json DELETED
@@ -1,35 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
- "name": "input",
4
- "type": "registry:component",
5
- "description": "Pihanga input card component",
6
- "dependencies": [
7
- "radix-ui@^1.4.3"
8
- ],
9
- "registryDependencies": [
10
- "https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
11
- "input",
12
- "label",
13
- "https://ivcap-works.github.io/pihanga-shadcn/r/form.json"
14
- ],
15
- "files": [
16
- {
17
- "path": "cards/input/index.ts",
18
- "content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {InputComponent} from \"./input.component\";\nimport {PI_INPUT_ACTION, PI_INPUT_CARD} from \"./input.types\";\n\nexport * from \"./input.types\";\n\nregisterCardComponent({\n name: PI_INPUT_CARD,\n component: InputComponent,\n events: actionTypesToEvents(PI_INPUT_ACTION),\n});\n",
19
- "type": "registry:component",
20
- "target": "src/cards/input/index.ts"
21
- },
22
- {
23
- "path": "cards/input/input.component.tsx",
24
- "content": "import React from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {Input} from \"@/components/ui/input\";\nimport {Label} from \"@/components/ui/label\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {PiInputEvents, PiInputProps} from \"./input.types\";\n\nexport const InputComponent = (\n props: PiCardProps<PiInputProps, PiInputEvents>,\n): React.ReactNode => {\n const {\n name,\n value: propValue = \"\",\n type = \"text\",\n placeholder,\n disabled,\n label,\n description,\n className,\n cardName,\n onChanged,\n onCommitted,\n } = props;\n\n // `id` and `invalid` may be injected by a parent pi/field card via\n // Pihanga's extra-prop forwarding. They are not in the official type so\n // we read them through an escape hatch.\n const injectedId = (props as {id?: string}).id;\n const invalid = Boolean((props as {invalid?: boolean}).invalid);\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // Local display state — lets the user type freely without triggering Redux\n // on every keystroke. Only `onCommitted` (blur / Enter) writes back to\n // Redux, so callers that use `onCommitted` instead of `onChanged` avoid\n // per-keystroke panel rebuilds.\n const [localValue, setLocalValue] = React.useState<string>(\n type === \"file\" ? \"\" : propValue,\n );\n\n // Sync local value when the prop changes from outside (e.g. when a different\n // card is selected in the playground, resetting the controls panel).\n React.useEffect(() => {\n if (!useFormData && type !== \"file\") {\n setLocalValue(propValue);\n }\n }, [propValue, useFormData, type]);\n\n // Derive the effective value for the controlled <Input>:\n // - inside a form → read from form.formData[name]\n // - standalone → use localValue (updated on every keystroke locally)\n // - file inputs → uncontrolled (no value binding)\n const inputValue =\n type === \"file\"\n ? undefined\n : useFormData\n ? ((form.formData[name!] as string | undefined) ?? \"\")\n : localValue;\n\n function handleChange(e: React.ChangeEvent<HTMLInputElement>) {\n const newValue =\n type === \"file\" ? (e.target.files?.[0]?.name ?? \"\") : e.target.value;\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n setLocalValue(newValue);\n onChanged({name, value: newValue});\n }\n }\n\n // Fire onCommitted when the user leaves the field or presses Enter.\n function handleBlur() {\n if (!useFormData && type !== \"file\") {\n onCommitted({name, value: localValue});\n }\n }\n\n function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {\n if (e.key === \"Enter\" && !useFormData && type !== \"file\") {\n onCommitted({name, value: localValue});\n }\n }\n\n // Use the id injected by pi/field (for label↔control linking); fall back\n // to a locally-generated id when used standalone.\n const selfId = `${cardName}-${name ?? \"input\"}`;\n const fieldId = injectedId ?? selfId;\n\n return (\n <div data-pihanga={cardName} className=\"grid w-full gap-1.5\">\n {label && <Label htmlFor={fieldId}>{label}</Label>}\n <Input\n id={fieldId}\n type={type}\n value={inputValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className={className}\n aria-invalid={invalid || undefined}\n aria-describedby={description ? `${fieldId}-desc` : undefined}\n />\n {description && (\n <p id={`${fieldId}-desc`} className=\"text-sm text-muted-foreground\">\n {description}\n </p>\n )}\n </div>\n );\n};\n",
25
- "type": "registry:component",
26
- "target": "src/cards/input/input.component.tsx"
27
- },
28
- {
29
- "path": "cards/input/input.types.ts",
30
- "content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_INPUT_CARD = \"pi/input\";\n\nexport const PiInput = createCardDeclaration<PiInputProps, PiInputEvents>(\n PI_INPUT_CARD,\n);\n\nexport const PI_INPUT_ACTION = registerActions(PI_INPUT_CARD, [\n \"changed\",\n \"committed\",\n]);\n\nexport const onPiInputChanged = createOnAction<PiInputChangedEvent>(\n PI_INPUT_ACTION.CHANGED,\n);\n\n/**\n * Subscribe to \"committed\" events — fired when the user finishes editing\n * (blur or Enter key). Unlike `onChanged` (which fires on every keystroke),\n * `onCommitted` only fires once per editing session, making it the preferred\n * handler for controlled inputs that trigger expensive downstream work.\n */\nexport const onPiInputCommitted = createOnAction<PiInputCommittedEvent>(\n PI_INPUT_ACTION.COMMITTED,\n);\n\n// ---------------------------------------------------------------------------\n// Props & Events\n// ---------------------------------------------------------------------------\n\nexport type PiInputProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its value from form state and writes\n * back via form.handleChange.\n */\n name?: string;\n\n /**\n * Controlled value used in standalone mode (outside a Form).\n * Ignored when `name` is set and the component is inside a pi/form.\n */\n value?: string;\n\n /**\n * HTML input type.\n * Common values: \"text\" | \"email\" | \"password\" | \"number\" | \"search\" |\n * \"url\" | \"tel\" | \"date\" | \"file\".\n * Defaults to \"text\".\n */\n type?: string;\n\n /** Placeholder text shown when the input is empty. */\n placeholder?: string;\n\n /** When true, the input is disabled and non-interactive. */\n disabled?: boolean;\n\n /**\n * Optional label text rendered in a `<label>` element above the input.\n * The label is automatically associated with the input via `htmlFor`.\n */\n label?: string;\n\n /**\n * Optional helper / description text rendered below the input.\n * Use this for hints, formatting guidance, or validation feedback.\n */\n description?: string;\n\n /** Extra Tailwind / CSS classes forwarded to the underlying <input> element. */\n className?: string;\n};\n\nexport type PiInputChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** New value after the change. */\n value: string;\n};\n\n/**\n * Payload for the `onCommitted` event (blur / Enter key).\n * Same shape as `PiInputChangedEvent`.\n */\nexport type PiInputCommittedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** Final committed value. */\n value: string;\n};\n\nexport type PiInputEvents = {\n /** Fires on every keystroke. */\n onChanged: PiInputChangedEvent;\n /**\n * Fires once when the user finishes editing (blur or Enter key).\n * Use this instead of `onChanged` when each change triggers expensive\n * downstream work (e.g. rebuilding a panel).\n */\n onCommitted: PiInputCommittedEvent;\n};\n",
31
- "type": "registry:component",
32
- "target": "src/cards/input/input.types.ts"
33
- }
34
- ]
35
- }
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
- }
@@ -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
- }
@@ -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
- }