@databricks/appkit-ui 0.61.1 → 0.63.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/CLAUDE.md +1 -0
- package/dist/cli/commands/codemod/index.js.map +1 -1
- package/dist/cli/commands/codemod/on-plugins-ready.js.map +1 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/doctor/bundle.js.map +1 -1
- package/dist/cli/commands/doctor/index.js.map +1 -1
- package/dist/cli/commands/doctor/report.js.map +1 -1
- package/dist/cli/commands/doctor/resolve-targets.js.map +1 -1
- package/dist/cli/commands/doctor/run.js.map +1 -1
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/lint.js.map +1 -1
- package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
- package/dist/cli/commands/plugin/create/create.js.map +1 -1
- package/dist/cli/commands/plugin/create/prompt-resource.js.map +1 -1
- package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
- package/dist/cli/commands/plugin/index.js.map +1 -1
- package/dist/cli/commands/plugin/list/list.js.map +1 -1
- package/dist/cli/commands/plugin/promote/promote.js.map +1 -1
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
- package/dist/cli/commands/registry/add.js.map +1 -1
- package/dist/cli/commands/registry/client.js.map +1 -1
- package/dist/cli/commands/registry/config-writer.js.map +1 -1
- package/dist/cli/commands/registry/env-reconcile.js.map +1 -1
- package/dist/cli/commands/registry/env-writer.js.map +1 -1
- package/dist/cli/commands/registry/index.js.map +1 -1
- package/dist/cli/commands/registry/info.js.map +1 -1
- package/dist/cli/commands/registry/list.js.map +1 -1
- package/dist/cli/commands/registry/requirements.js.map +1 -1
- package/dist/cli/commands/registry/server-register.js.map +1 -1
- package/dist/cli/commands/registry/workspace-picker.js.map +1 -1
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/js/arrow/arrow-client.d.ts.map +1 -1
- package/dist/js/arrow/arrow-client.js.map +1 -1
- package/dist/react/charts/area/index.d.ts.map +1 -1
- package/dist/react/charts/area/index.js.map +1 -1
- package/dist/react/charts/bar/index.d.ts.map +1 -1
- package/dist/react/charts/bar/index.js.map +1 -1
- package/dist/react/charts/base.d.ts.map +1 -1
- package/dist/react/charts/base.js +1 -1
- package/dist/react/charts/base.js.map +1 -1
- package/dist/react/charts/heatmap/index.d.ts.map +1 -1
- package/dist/react/charts/heatmap/index.js.map +1 -1
- package/dist/react/charts/line/index.d.ts.map +1 -1
- package/dist/react/charts/line/index.js.map +1 -1
- package/dist/react/charts/normalize.d.ts.map +1 -1
- package/dist/react/charts/normalize.js.map +1 -1
- package/dist/react/charts/pie/index.d.ts.map +1 -1
- package/dist/react/charts/pie/index.js.map +1 -1
- package/dist/react/charts/radar/index.d.ts.map +1 -1
- package/dist/react/charts/radar/index.js.map +1 -1
- package/dist/react/charts/scatter/index.d.ts.map +1 -1
- package/dist/react/charts/scatter/index.js.map +1 -1
- package/dist/react/charts/theme.d.ts.map +1 -1
- package/dist/react/charts/theme.js.map +1 -1
- package/dist/react/charts/wrapper.d.ts.map +1 -1
- package/dist/react/charts/wrapper.js.map +1 -1
- package/dist/react/file-browser/directory-list.d.ts.map +1 -1
- package/dist/react/file-browser/directory-list.js.map +1 -1
- package/dist/react/file-browser/file-breadcrumb.d.ts.map +1 -1
- package/dist/react/file-browser/file-breadcrumb.js.map +1 -1
- package/dist/react/file-browser/file-entry.d.ts.map +1 -1
- package/dist/react/file-browser/file-entry.js.map +1 -1
- package/dist/react/file-browser/file-preview-panel.d.ts.map +1 -1
- package/dist/react/file-browser/file-preview-panel.js.map +1 -1
- package/dist/react/file-browser/new-folder-input.d.ts.map +1 -1
- package/dist/react/file-browser/new-folder-input.js.map +1 -1
- package/dist/react/genie/genie-chat-input.d.ts.map +1 -1
- package/dist/react/genie/genie-chat-input.js.map +1 -1
- package/dist/react/genie/genie-chat-message-list.d.ts.map +1 -1
- package/dist/react/genie/genie-chat-message-list.js.map +1 -1
- package/dist/react/genie/genie-chat-message.d.ts.map +1 -1
- package/dist/react/genie/genie-chat-message.js.map +1 -1
- package/dist/react/genie/genie-query-visualization.d.ts.map +1 -1
- package/dist/react/genie/genie-query-visualization.js.map +1 -1
- package/dist/react/genie/use-genie-chat.d.ts.map +1 -1
- package/dist/react/genie/use-genie-chat.js.map +1 -1
- package/dist/react/hooks/analytics-request-store.js +144 -0
- package/dist/react/hooks/analytics-request-store.js.map +1 -0
- package/dist/react/hooks/request-store.js +92 -0
- package/dist/react/hooks/request-store.js.map +1 -0
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-agent-chat.d.ts.map +1 -1
- package/dist/react/hooks/use-agent-chat.js.map +1 -1
- package/dist/react/hooks/use-ai-search-query.d.ts.map +1 -1
- package/dist/react/hooks/use-ai-search-query.js.map +1 -1
- package/dist/react/hooks/use-analytics-query.d.ts +6 -0
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js +34 -143
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-analytics-warehouse-status.js.map +1 -1
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
- package/dist/react/hooks/use-chart-data.js.map +1 -1
- package/dist/react/hooks/use-metric-view.d.ts.map +1 -1
- package/dist/react/hooks/use-metric-view.js.map +1 -1
- package/dist/react/hooks/use-plugin-config.d.ts.map +1 -1
- package/dist/react/hooks/use-plugin-config.js.map +1 -1
- package/dist/react/hooks/use-serving-invoke.d.ts.map +1 -1
- package/dist/react/hooks/use-serving-invoke.js.map +1 -1
- package/dist/react/hooks/use-serving-stream.d.ts.map +1 -1
- package/dist/react/hooks/use-serving-stream.js.map +1 -1
- package/dist/react/resource-status-indicator.d.ts.map +1 -1
- package/dist/react/resource-status-indicator.js.map +1 -1
- package/dist/react/table/data-table.d.ts.map +1 -1
- package/dist/react/table/data-table.js.map +1 -1
- package/dist/react/table/table-wrapper.js.map +1 -1
- package/dist/react/ui/button.d.ts +1 -1
- package/dist/react/ui/input-group.d.ts +1 -1
- package/dist/react/ui-variants/variants.d.ts.map +1 -1
- package/dist/react/ui-variants/variants.js.map +1 -1
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js.map +1 -1
- package/dist/schemas/metric-fqn.js.map +1 -1
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/styles.css +0 -3
- package/dist/workspace-client/legacy.js.map +1 -1
- package/dist/workspace-client/types.d.ts.map +1 -1
- package/docs/plugins/testing.md +245 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-serving-invoke.js","names":[],"sources":["../../../src/react/hooks/use-serving-invoke.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n InferServingRequest,\n InferServingResponse,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingInvokeOptions<\n K extends ServingAlias = ServingAlias,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If false, does not invoke automatically on mount. Default: false */\n autoStart?: boolean;\n}\n\nexport interface UseServingInvokeResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the invocation. Pass an optional body override for this invocation. */\n invoke: (overrideBody?: TBody) => Promise<T | null>;\n /** Response data, null until loaded. */\n data: T | null;\n /** Whether a request is in progress. */\n loading: boolean;\n /** Error message, if any. */\n error: string | null;\n}\n\n/**\n * Hook for non-streaming invocation of a serving endpoint.\n * Calls `POST /api/serving/invoke` (default) or `POST /api/serving/{alias}/invoke` (named).\n *\n * When the type generator has populated `ServingEndpointRegistry`, the response type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingInvoke<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingInvokeOptions<K> = {} as UseServingInvokeOptions<K>,\n): UseServingInvokeResult<InferServingResponse<K>, InferServingRequest<K>> {\n type TResponse = InferServingResponse<K>;\n const { alias, autoStart = false } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [data, setData] = useState<TResponse | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/invoke`\n : \"/api/serving/invoke\";\n\n const bodyJson = JSON.stringify(body);\n\n const invoke = useCallback(\n (overrideBody?: InferServingRequest<K>): Promise<TResponse | null> => {\n if (aliasError) {\n setError(aliasError);\n return Promise.resolve(null);\n }\n\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n setLoading(true);\n setError(null);\n setData(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n return fetch(urlSuffix, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: payload,\n signal: abortController.signal,\n })\n .then(async (res) => {\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null);\n throw new Error(errorBody?.error || `HTTP ${res.status}`);\n }\n return res.json();\n })\n .then((result: TResponse) => {\n if (abortController.signal.aborted) return null;\n setData(result);\n setLoading(false);\n return result;\n })\n .catch((err: Error) => {\n if (abortController.signal.aborted) return null;\n setError(err.message || \"Request failed\");\n setLoading(false);\n return null;\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n invoke();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [invoke, autoStart]);\n\n return { invoke, data, loading, error };\n}\n"],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-serving-invoke.js","names":[],"sources":["../../../src/react/hooks/use-serving-invoke.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type {\n InferServingRequest,\n InferServingResponse,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingInvokeOptions<\n K extends ServingAlias = ServingAlias,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If false, does not invoke automatically on mount. Default: false */\n autoStart?: boolean;\n}\n\nexport interface UseServingInvokeResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the invocation. Pass an optional body override for this invocation. */\n invoke: (overrideBody?: TBody) => Promise<T | null>;\n /** Response data, null until loaded. */\n data: T | null;\n /** Whether a request is in progress. */\n loading: boolean;\n /** Error message, if any. */\n error: string | null;\n}\n\n/**\n * Hook for non-streaming invocation of a serving endpoint.\n * Calls `POST /api/serving/invoke` (default) or `POST /api/serving/{alias}/invoke` (named).\n *\n * When the type generator has populated `ServingEndpointRegistry`, the response type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingInvoke<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingInvokeOptions<K> = {} as UseServingInvokeOptions<K>,\n): UseServingInvokeResult<InferServingResponse<K>, InferServingRequest<K>> {\n type TResponse = InferServingResponse<K>;\n const { alias, autoStart = false } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [data, setData] = useState<TResponse | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/invoke`\n : \"/api/serving/invoke\";\n\n const bodyJson = JSON.stringify(body);\n\n const invoke = useCallback(\n (overrideBody?: InferServingRequest<K>): Promise<TResponse | null> => {\n if (aliasError) {\n setError(aliasError);\n return Promise.resolve(null);\n }\n\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n setLoading(true);\n setError(null);\n setData(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n return fetch(urlSuffix, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: payload,\n signal: abortController.signal,\n })\n .then(async (res) => {\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null);\n throw new Error(errorBody?.error || `HTTP ${res.status}`);\n }\n return res.json();\n })\n .then((result: TResponse) => {\n if (abortController.signal.aborted) return null;\n setData(result);\n setLoading(false);\n return result;\n })\n .catch((err: Error) => {\n if (abortController.signal.aborted) return null;\n setError(err.message || \"Request failed\");\n setLoading(false);\n return null;\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n invoke();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [invoke, autoStart]);\n\n return { invoke, data, loading, error };\n}\n"],"mappings":";;;;;;;;;;;AAwCA,SAAgB,iBACd,MACA,UAAsC,EAAE,EACiC;CAEzE,MAAM,EAAE,OAAO,YAAY,UAAU;CAErC,MAAM,SAAS,sBAA2C,UAAU;CAEpE,MAAM,aAAa,cAAc;AAC/B,MAAI,CAAC,SAAS,CAAC,OAAO,QAAS,QAAO;EACtC,MAAM,WAAW,OAAO,MAAM;AAC9B,MAAI,CAAC,OAAO,QAAQ,SAAS,SAAS,CACpC,QAAO,0BAA0B,SAAS,gBAAgB,OAAO,QAAQ,KAAK,KAAK;AAErF,SAAO;IACN,CAAC,OAAO,OAAO,QAAQ,CAAC;CAE3B,MAAM,CAAC,MAAM,WAAW,SAA2B,KAAK;CACxD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAAwB,WAAW;CAC7D,MAAM,qBAAqB,OAA+B,KAAK;CAE/D,MAAM,YAAY,QACd,gBAAgB,mBAAmB,OAAO,MAAM,CAAC,CAAC,WAClD;CAEJ,MAAM,WAAW,KAAK,UAAU,KAAK;CAErC,MAAM,SAAS,aACZ,iBAAqE;AACpE,MAAI,YAAY;AACd,YAAS,WAAW;AACpB,UAAO,QAAQ,QAAQ,KAAK;;AAG9B,MAAI,mBAAmB,QACrB,oBAAmB,QAAQ,OAAO;AAGpC,aAAW,KAAK;AAChB,WAAS,KAAK;AACd,UAAQ,KAAK;EAEb,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;EAE7B,MAAM,UAAU,eAAe,KAAK,UAAU,aAAa,GAAG;AAE9D,SAAO,MAAM,WAAW;GACtB,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM;GACN,QAAQ,gBAAgB;GACzB,CAAC,CACC,KAAK,OAAO,QAAQ;AACnB,OAAI,CAAC,IAAI,IAAI;IACX,MAAM,YAAY,MAAM,IAAI,MAAM,CAAC,YAAY,KAAK;AACpD,UAAM,IAAI,MAAM,WAAW,SAAS,QAAQ,IAAI,SAAS;;AAE3D,UAAO,IAAI,MAAM;IACjB,CACD,MAAM,WAAsB;AAC3B,OAAI,gBAAgB,OAAO,QAAS,QAAO;AAC3C,WAAQ,OAAO;AACf,cAAW,MAAM;AACjB,UAAO;IACP,CACD,OAAO,QAAe;AACrB,OAAI,gBAAgB,OAAO,QAAS,QAAO;AAC3C,YAAS,IAAI,WAAW,iBAAiB;AACzC,cAAW,MAAM;AACjB,UAAO;IACP;IAEN;EAAC;EAAW;EAAU;EAAW,CAClC;AAED,iBAAgB;AACd,MAAI,UACF,SAAQ;AAGV,eAAa;AACX,sBAAmB,SAAS,OAAO;;IAEpC,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAO;EAAE;EAAQ;EAAM;EAAS;EAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-serving-stream.d.ts","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"use-serving-stream.d.ts","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"mappings":";;;UAYiB,uBAAA,WACL,YAAA,GAAe,YAAA,MACrB,iBAAA,CAAkB,CAAA;;EAGtB,KAAA,GAAQ,CAAA;EAL8B;EAOtC,SAAA;EANU;EAQV,UAAA,IAAc,MAAA,EAAQ,CAAA;AAAA;AAAA,UAGP,sBAAA,sBAEP,MAAA;EATA;EAYR,MAAA,GAAS,YAAA,GAAe,KAAA;EARD;EAUvB,MAAA,EAAQ,CAAA;EAlBR;EAoBA,SAAA;EApByB;EAsBzB,KAAA;EArBI;EAuBJ,KAAA;AAAA;;;;;;;;AAbF;iBAwBgB,gBAAA,WAA2B,YAAA,GAAe,YAAA,CAAA,CACxD,IAAA,EAAM,mBAAA,CAAoB,CAAA,GAC1B,OAAA,GAAS,uBAAA,CAAwB,CAAA,IAChC,sBAAA,CAAuB,iBAAA,CAAkB,CAAA,GAAI,mBAAA,CAAoB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-serving-stream.js","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { connectSSE } from \"@/js\";\nimport type {\n InferServingChunk,\n InferServingRequest,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingStreamOptions<\n K extends ServingAlias = ServingAlias,\n T = InferServingChunk<K>,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If true, starts streaming automatically on mount. Default: false */\n autoStart?: boolean;\n /** Called with accumulated chunks when the stream completes successfully. */\n onComplete?: (chunks: T[]) => void;\n}\n\nexport interface UseServingStreamResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the streaming invocation. Pass an optional body override for this invocation. */\n stream: (overrideBody?: TBody) => void;\n /** Accumulated chunks received so far. */\n chunks: T[];\n /** Whether streaming is in progress. */\n streaming: boolean;\n /** Error message, if any. */\n error: string | null;\n /** Reset chunks and abort any active stream. */\n reset: () => void;\n}\n\n/**\n * Hook for streaming invocation of a serving endpoint via SSE.\n * Calls `POST /api/serving/stream` (default) or `POST /api/serving/{alias}/stream` (named).\n * Accumulates parsed chunks in state.\n *\n * When the type generator has populated `ServingEndpointRegistry`, the chunk type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingStream<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingStreamOptions<K> = {} as UseServingStreamOptions<K>,\n): UseServingStreamResult<InferServingChunk<K>, InferServingRequest<K>> {\n type TChunk = InferServingChunk<K>;\n const { alias, autoStart = false, onComplete } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [chunks, setChunks] = useState<TChunk[]>([]);\n const [streaming, setStreaming] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n const chunksRef = useRef<TChunk[]>([]);\n const onCompleteRef = useRef(onComplete);\n onCompleteRef.current = onComplete;\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/stream`\n : \"/api/serving/stream\";\n\n const reset = useCallback(() => {\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n chunksRef.current = [];\n setChunks([]);\n setStreaming(false);\n setError(null);\n }, []);\n\n const bodyJson = JSON.stringify(body);\n\n const stream = useCallback(\n (overrideBody?: InferServingRequest<K>) => {\n if (aliasError) {\n setError(aliasError);\n return;\n }\n\n // Abort any existing stream\n abortControllerRef.current?.abort();\n\n setStreaming(true);\n setError(null);\n setChunks([]);\n chunksRef.current = [];\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n if (abortController.signal.aborted) return;\n try {\n const parsed = JSON.parse(message.data);\n\n chunksRef.current = [...chunksRef.current, parsed as TChunk];\n setChunks(chunksRef.current);\n } catch {\n // Skip malformed messages\n }\n },\n onError: (err) => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(err instanceof Error ? err.message : \"Streaming failed\");\n },\n })\n .then(() => {\n if (abortController.signal.aborted) return;\n // Stream completed\n setStreaming(false);\n onCompleteRef.current?.(chunksRef.current);\n })\n .catch(() => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(\"Connection error\");\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n stream();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [stream, autoStart]);\n\n return { stream, chunks, streaming, error, reset };\n}\n"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-serving-stream.js","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { connectSSE } from \"@/js\";\n\nimport type {\n InferServingChunk,\n InferServingRequest,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingStreamOptions<\n K extends ServingAlias = ServingAlias,\n T = InferServingChunk<K>,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If true, starts streaming automatically on mount. Default: false */\n autoStart?: boolean;\n /** Called with accumulated chunks when the stream completes successfully. */\n onComplete?: (chunks: T[]) => void;\n}\n\nexport interface UseServingStreamResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the streaming invocation. Pass an optional body override for this invocation. */\n stream: (overrideBody?: TBody) => void;\n /** Accumulated chunks received so far. */\n chunks: T[];\n /** Whether streaming is in progress. */\n streaming: boolean;\n /** Error message, if any. */\n error: string | null;\n /** Reset chunks and abort any active stream. */\n reset: () => void;\n}\n\n/**\n * Hook for streaming invocation of a serving endpoint via SSE.\n * Calls `POST /api/serving/stream` (default) or `POST /api/serving/{alias}/stream` (named).\n * Accumulates parsed chunks in state.\n *\n * When the type generator has populated `ServingEndpointRegistry`, the chunk type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingStream<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingStreamOptions<K> = {} as UseServingStreamOptions<K>,\n): UseServingStreamResult<InferServingChunk<K>, InferServingRequest<K>> {\n type TChunk = InferServingChunk<K>;\n const { alias, autoStart = false, onComplete } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [chunks, setChunks] = useState<TChunk[]>([]);\n const [streaming, setStreaming] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n const chunksRef = useRef<TChunk[]>([]);\n const onCompleteRef = useRef(onComplete);\n onCompleteRef.current = onComplete;\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/stream`\n : \"/api/serving/stream\";\n\n const reset = useCallback(() => {\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n chunksRef.current = [];\n setChunks([]);\n setStreaming(false);\n setError(null);\n }, []);\n\n const bodyJson = JSON.stringify(body);\n\n const stream = useCallback(\n (overrideBody?: InferServingRequest<K>) => {\n if (aliasError) {\n setError(aliasError);\n return;\n }\n\n // Abort any existing stream\n abortControllerRef.current?.abort();\n\n setStreaming(true);\n setError(null);\n setChunks([]);\n chunksRef.current = [];\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n if (abortController.signal.aborted) return;\n try {\n const parsed = JSON.parse(message.data);\n\n chunksRef.current = [...chunksRef.current, parsed as TChunk];\n setChunks(chunksRef.current);\n } catch {\n // Skip malformed messages\n }\n },\n onError: (err) => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(err instanceof Error ? err.message : \"Streaming failed\");\n },\n })\n .then(() => {\n if (abortController.signal.aborted) return;\n // Stream completed\n setStreaming(false);\n onCompleteRef.current?.(chunksRef.current);\n })\n .catch(() => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(\"Connection error\");\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n stream();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [stream, autoStart]);\n\n return { stream, chunks, streaming, error, reset };\n}\n"],"mappings":";;;;;;;;;;;;;AAgDA,SAAgB,iBACd,MACA,UAAsC,EAAE,EAC8B;CAEtE,MAAM,EAAE,OAAO,YAAY,OAAO,eAAe;CAEjD,MAAM,SAAS,sBAA2C,UAAU;CAEpE,MAAM,aAAa,cAAc;AAC/B,MAAI,CAAC,SAAS,CAAC,OAAO,QAAS,QAAO;EACtC,MAAM,WAAW,OAAO,MAAM;AAC9B,MAAI,CAAC,OAAO,QAAQ,SAAS,SAAS,CACpC,QAAO,0BAA0B,SAAS,gBAAgB,OAAO,QAAQ,KAAK,KAAK;AAErF,SAAO;IACN,CAAC,OAAO,OAAO,QAAQ,CAAC;CAE3B,MAAM,CAAC,QAAQ,aAAa,SAAmB,EAAE,CAAC;CAClD,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,OAAO,YAAY,SAAwB,WAAW;CAC7D,MAAM,qBAAqB,OAA+B,KAAK;CAC/D,MAAM,YAAY,OAAiB,EAAE,CAAC;CACtC,MAAM,gBAAgB,OAAO,WAAW;AACxC,eAAc,UAAU;CAExB,MAAM,YAAY,QACd,gBAAgB,mBAAmB,OAAO,MAAM,CAAC,CAAC,WAClD;CAEJ,MAAM,QAAQ,kBAAkB;AAC9B,qBAAmB,SAAS,OAAO;AACnC,qBAAmB,UAAU;AAC7B,YAAU,UAAU,EAAE;AACtB,YAAU,EAAE,CAAC;AACb,eAAa,MAAM;AACnB,WAAS,KAAK;IACb,EAAE,CAAC;CAEN,MAAM,WAAW,KAAK,UAAU,KAAK;CAErC,MAAM,SAAS,aACZ,iBAA0C;AACzC,MAAI,YAAY;AACd,YAAS,WAAW;AACpB;;AAIF,qBAAmB,SAAS,OAAO;AAEnC,eAAa,KAAK;AAClB,WAAS,KAAK;AACd,YAAU,EAAE,CAAC;AACb,YAAU,UAAU,EAAE;EAEtB,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;AAI7B,aAAW;GACT,KAAK;GACL,SAJc,eAAe,KAAK,UAAU,aAAa,GAAG;GAK5D,QAAQ,gBAAgB;GACxB,WAAW,OAAO,YAAY;AAC5B,QAAI,gBAAgB,OAAO,QAAS;AACpC,QAAI;KACF,MAAM,SAAS,KAAK,MAAM,QAAQ,KAAK;AAEvC,eAAU,UAAU,CAAC,GAAG,UAAU,SAAS,OAAiB;AAC5D,eAAU,UAAU,QAAQ;YACtB;;GAIV,UAAU,QAAQ;AAChB,QAAI,gBAAgB,OAAO,QAAS;AACpC,iBAAa,MAAM;AACnB,aAAS,eAAe,QAAQ,IAAI,UAAU,mBAAmB;;GAEpE,CAAC,CACC,WAAW;AACV,OAAI,gBAAgB,OAAO,QAAS;AAEpC,gBAAa,MAAM;AACnB,iBAAc,UAAU,UAAU,QAAQ;IAC1C,CACD,YAAY;AACX,OAAI,gBAAgB,OAAO,QAAS;AACpC,gBAAa,MAAM;AACnB,YAAS,mBAAmB;IAC5B;IAEN;EAAC;EAAW;EAAU;EAAW,CAClC;AAED,iBAAgB;AACd,MAAI,UACF,SAAQ;AAGV,eAAa;AACX,sBAAmB,SAAS,OAAO;;IAEpC,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAO;EAAE;EAAQ;EAAQ;EAAW;EAAO;EAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-status-indicator.d.ts","names":[],"sources":["../../src/react/resource-status-indicator.tsx"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"resource-status-indicator.d.ts","names":[],"sources":["../../src/react/resource-status-indicator.tsx"],"mappings":";;;;;;;UAeiB,oBAAA;EACf,KAAA,GAAQ,MAAA,EAAQ,cAAA;EAChB,WAAA,GACE,MAAA,EAAQ,cAAA,EACR,SAAA,EAAW,wBAAA;EAJsB;EAOnC,IAAA,GAAO,UAAA;AAAA;;UAIQ,4BAAA;EAJR;EAMP,IAAA;EANiB;EAQjB,SAAA,GAAY,MAAA,SAAe,oBAAA;EAdX;EAgBhB,cAAA;EAfA;EAiBA,MAAA,IAAU,SAAA,EAAW,wBAAA,KAA6B,KAAA,CAAM,SAAA;AAAA;;;;;;;iBA+B1C,wBAAA,CACd,OAAA,GAAS,4BAAA;AAAA,UAsFM,4BAAA,SACP,4BAAA,EAA8B,IAAA,CAAK,YAAA;;EAE3C,SAAA;AAAA;;;;;;;;;;;;;;;;;AA1FF;;;;;AAuFA;;;;;;;;;;;iBAsCgB,uBAAA,CAAA;EACd,IAAA;EACA,SAAA;EACA,cAAA;EACA,MAAA;EACA,QAAA;EAAA,GACG;AAAA,IACF,4BAAA,GAAiC,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-status-indicator.js","names":["Toaster"],"sources":["../../src/react/resource-status-indicator.tsx"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { type ToasterProps, toast } from \"sonner\";\nimport {\n type AggregatedResourceStatus,\n type ResourceStatus,\n useResourceStatus,\n} from \"./hooks/use-resource-status\";\nimport { Toaster } from \"./ui/sonner\";\n\n/** ~1Hz tick driving the elapsed-time display between store events. */\nconst ELAPSED_TICK_MS = 1000;\n\n/** Per-kind copy + icon overrides for {@link ResourceStatusIndicator}. */\nexport interface ResourceKindRenderer {\n title: (status: ResourceStatus) => string;\n description: (\n status: ResourceStatus,\n aggregate: AggregatedResourceStatus,\n ) => string;\n /** Defaults to sonner's built-in loading/error icon. */\n icon?: LucideIcon;\n}\n\n/** Options shared by {@link ResourceStatusIndicator} and {@link useResourceStatusToaster}. */\nexport interface ResourceStatusToasterOptions {\n /** Restrict to a single resource kind. Otherwise shows the worst across all kinds. */\n kind?: string;\n /** Per-kind copy + icon overrides. */\n renderers?: Record<string, ResourceKindRenderer>;\n /** Class name applied to the toast (not the Toaster wrapper). */\n toastClassName?: string;\n /** Full custom render override, rendered inside `toast.custom`. */\n render?: (aggregate: AggregatedResourceStatus) => React.ReactNode;\n}\n\nconst DEFAULT_KIND_RENDERERS: Record<string, ResourceKindRenderer> = {\n warehouse: {\n title: (s) =>\n s.severity === \"error\"\n ? \"SQL warehouse unavailable\"\n : \"SQL warehouse warming up\",\n description: (s, agg) => {\n if (s.severity === \"error\") {\n return (\n s.summary ??\n \"The configured SQL warehouse is unavailable. Update DATABRICKS_WAREHOUSE_ID and reload.\"\n );\n }\n const labels = agg.affectedLabels.length;\n if (labels === 0) {\n return `Waiting for the warehouse to reach RUNNING · ${formatElapsed(agg.elapsedMs)}`;\n }\n return `${labels} ${labels === 1 ? \"query\" : \"queries\"} waiting · ${formatElapsed(agg.elapsedMs)}`;\n },\n },\n};\n\n/**\n * Drives a sticky sonner toast that mirrors the worst pending resource\n * status. Does not render anything — supply your own `<Toaster />`. Most\n * apps should prefer {@link ResourceStatusIndicator}; use this hook only\n * to share an existing Toaster with unrelated app toasts.\n */\nexport function useResourceStatusToaster(\n options: ResourceStatusToasterOptions = {},\n): void {\n const { kind, renderers, toastClassName, render } = options;\n const aggregate = useResourceStatus(kind ? { kind } : undefined);\n const worst = aggregate.worst;\n // Per-instance + per-kind toast id: instance scoping isolates multiple\n // indicators in the same tree; kind scoping forces dismiss-and-recreate\n // when severity flips between toast types (sonner can't morph\n // jsx/description cleanly across `custom` ↔ `loading`/`error`).\n const instanceId = useId();\n\n // The store is event-driven, so re-render at ~1Hz to keep the elapsed\n // counter advancing between status emissions.\n const [, forceTick] = useState(0);\n useEffect(() => {\n if (!worst) return;\n const id = setInterval(() => forceTick((n) => n + 1), ELAPSED_TICK_MS);\n return () => clearInterval(id);\n }, [worst]);\n\n const liveIdRef = useRef<string | null>(null);\n\n // Runs every render: cheap because sonner patches the same id in place.\n useEffect(() => {\n if (!worst) {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n return;\n }\n\n // Live elapsed; the snapshot only refreshes on store events.\n const liveAggregate: AggregatedResourceStatus = {\n ...aggregate,\n elapsedMs: Math.max(0, Date.now() - worst.startedAt),\n };\n\n const nextId = `appkit-resource:${instanceId}:${worst.kind}:${worst.severity}`;\n if (liveIdRef.current && liveIdRef.current !== nextId) {\n toast.dismiss(liveIdRef.current);\n }\n liveIdRef.current = nextId;\n\n if (render) {\n const node = render(liveAggregate);\n toast.custom(() => <>{node}</>, {\n id: nextId,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n });\n return;\n }\n\n const merged = { ...DEFAULT_KIND_RENDERERS, ...renderers };\n const renderer = merged[worst.kind];\n const title = renderer?.title(worst) ?? defaultTitle(worst);\n const description =\n renderer?.description(worst, liveAggregate) ?? defaultDescription(worst);\n const Icon = renderer?.icon;\n const opts = {\n id: nextId,\n description,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n ...(Icon ? { icon: <Icon className=\"size-4\" /> } : {}),\n };\n\n if (worst.severity === \"error\") {\n toast.error(title, opts);\n } else {\n toast.loading(title, opts);\n }\n });\n\n // Dismiss on unmount so the toast doesn't outlive its mount point.\n useEffect(() => {\n return () => {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n };\n }, []);\n}\n\nexport interface ResourceStatusIndicatorProps\n extends ResourceStatusToasterOptions,\n Omit<ToasterProps, \"className\"> {\n /** Class name applied to the Toaster wrapper. */\n className?: string;\n}\n\n/**\n * Drop-in indicator that mounts a `<Toaster />` and surfaces the worst\n * pending {@link ResourceStatus} across every plugin/component publishing\n * into the nearest {@link ResourceStatusProvider} as a sonner toast\n * (`toast.loading` for cold starts, `toast.error` for unrecoverable\n * states), keyed by the worst kind so only one indicator toast is on\n * screen at a time.\n *\n * Forwards `Toaster` props (`position` defaults to `top-right`, plus\n * `theme`, `richColors`, …). Apps that already mount their own\n * `<Toaster />` should drop this component and call\n * {@link useResourceStatusToaster} instead.\n *\n * @example\n * ```tsx\n * <ResourceStatusProvider>\n * <ResourceStatusIndicator />\n * <App />\n * </ResourceStatusProvider>\n * ```\n *\n * @example Custom render\n * ```tsx\n * <ResourceStatusIndicator\n * render={(agg) => (\n * <div className=\"my-card\">\n * {agg.worst?.kind} {agg.worst?.state.toLowerCase()}\n * </div>\n * )}\n * />\n * ```\n */\nexport function ResourceStatusIndicator({\n kind,\n renderers,\n toastClassName,\n render,\n position = \"top-right\",\n ...toasterProps\n}: ResourceStatusIndicatorProps = {}) {\n useResourceStatusToaster({ kind, renderers, toastClassName, render });\n return <Toaster position={position} {...toasterProps} />;\n}\n\nfunction defaultTitle(status: ResourceStatus): string {\n switch (status.severity) {\n case \"error\":\n return `${humanizeKind(status.kind)} unavailable`;\n case \"warning\":\n return `${humanizeKind(status.kind)} degraded`;\n default:\n return `${humanizeKind(status.kind)} not ready`;\n }\n}\n\nfunction defaultDescription(status: ResourceStatus): string {\n return status.summary ?? `Current state: ${status.state}.`;\n}\n\nfunction humanizeKind(kind: string): string {\n if (!kind) return \"Resource\";\n return kind\n .split(/[-_]/g)\n .filter(Boolean)\n .map((part) => part[0].toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nfunction formatElapsed(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-status-indicator.js","names":["Toaster"],"sources":["../../src/react/resource-status-indicator.tsx"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { type ToasterProps, toast } from \"sonner\";\n\nimport {\n type AggregatedResourceStatus,\n type ResourceStatus,\n useResourceStatus,\n} from \"./hooks/use-resource-status\";\nimport { Toaster } from \"./ui/sonner\";\n\n/** ~1Hz tick driving the elapsed-time display between store events. */\nconst ELAPSED_TICK_MS = 1000;\n\n/** Per-kind copy + icon overrides for {@link ResourceStatusIndicator}. */\nexport interface ResourceKindRenderer {\n title: (status: ResourceStatus) => string;\n description: (\n status: ResourceStatus,\n aggregate: AggregatedResourceStatus,\n ) => string;\n /** Defaults to sonner's built-in loading/error icon. */\n icon?: LucideIcon;\n}\n\n/** Options shared by {@link ResourceStatusIndicator} and {@link useResourceStatusToaster}. */\nexport interface ResourceStatusToasterOptions {\n /** Restrict to a single resource kind. Otherwise shows the worst across all kinds. */\n kind?: string;\n /** Per-kind copy + icon overrides. */\n renderers?: Record<string, ResourceKindRenderer>;\n /** Class name applied to the toast (not the Toaster wrapper). */\n toastClassName?: string;\n /** Full custom render override, rendered inside `toast.custom`. */\n render?: (aggregate: AggregatedResourceStatus) => React.ReactNode;\n}\n\nconst DEFAULT_KIND_RENDERERS: Record<string, ResourceKindRenderer> = {\n warehouse: {\n title: (s) =>\n s.severity === \"error\"\n ? \"SQL warehouse unavailable\"\n : \"SQL warehouse warming up\",\n description: (s, agg) => {\n if (s.severity === \"error\") {\n return (\n s.summary ??\n \"The configured SQL warehouse is unavailable. Update DATABRICKS_WAREHOUSE_ID and reload.\"\n );\n }\n const labels = agg.affectedLabels.length;\n if (labels === 0) {\n return `Waiting for the warehouse to reach RUNNING · ${formatElapsed(agg.elapsedMs)}`;\n }\n return `${labels} ${labels === 1 ? \"query\" : \"queries\"} waiting · ${formatElapsed(agg.elapsedMs)}`;\n },\n },\n};\n\n/**\n * Drives a sticky sonner toast that mirrors the worst pending resource\n * status. Does not render anything — supply your own `<Toaster />`. Most\n * apps should prefer {@link ResourceStatusIndicator}; use this hook only\n * to share an existing Toaster with unrelated app toasts.\n */\nexport function useResourceStatusToaster(\n options: ResourceStatusToasterOptions = {},\n): void {\n const { kind, renderers, toastClassName, render } = options;\n const aggregate = useResourceStatus(kind ? { kind } : undefined);\n const worst = aggregate.worst;\n // Per-instance + per-kind toast id: instance scoping isolates multiple\n // indicators in the same tree; kind scoping forces dismiss-and-recreate\n // when severity flips between toast types (sonner can't morph\n // jsx/description cleanly across `custom` ↔ `loading`/`error`).\n const instanceId = useId();\n\n // The store is event-driven, so re-render at ~1Hz to keep the elapsed\n // counter advancing between status emissions.\n const [, forceTick] = useState(0);\n useEffect(() => {\n if (!worst) return;\n const id = setInterval(() => forceTick((n) => n + 1), ELAPSED_TICK_MS);\n return () => clearInterval(id);\n }, [worst]);\n\n const liveIdRef = useRef<string | null>(null);\n\n // Runs every render: cheap because sonner patches the same id in place.\n useEffect(() => {\n if (!worst) {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n return;\n }\n\n // Live elapsed; the snapshot only refreshes on store events.\n const liveAggregate: AggregatedResourceStatus = {\n ...aggregate,\n elapsedMs: Math.max(0, Date.now() - worst.startedAt),\n };\n\n const nextId = `appkit-resource:${instanceId}:${worst.kind}:${worst.severity}`;\n if (liveIdRef.current && liveIdRef.current !== nextId) {\n toast.dismiss(liveIdRef.current);\n }\n liveIdRef.current = nextId;\n\n if (render) {\n const node = render(liveAggregate);\n toast.custom(() => <>{node}</>, {\n id: nextId,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n });\n return;\n }\n\n const merged = { ...DEFAULT_KIND_RENDERERS, ...renderers };\n const renderer = merged[worst.kind];\n const title = renderer?.title(worst) ?? defaultTitle(worst);\n const description =\n renderer?.description(worst, liveAggregate) ?? defaultDescription(worst);\n const Icon = renderer?.icon;\n const opts = {\n id: nextId,\n description,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n ...(Icon ? { icon: <Icon className=\"size-4\" /> } : {}),\n };\n\n if (worst.severity === \"error\") {\n toast.error(title, opts);\n } else {\n toast.loading(title, opts);\n }\n });\n\n // Dismiss on unmount so the toast doesn't outlive its mount point.\n useEffect(() => {\n return () => {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n };\n }, []);\n}\n\nexport interface ResourceStatusIndicatorProps\n extends ResourceStatusToasterOptions, Omit<ToasterProps, \"className\"> {\n /** Class name applied to the Toaster wrapper. */\n className?: string;\n}\n\n/**\n * Drop-in indicator that mounts a `<Toaster />` and surfaces the worst\n * pending {@link ResourceStatus} across every plugin/component publishing\n * into the nearest {@link ResourceStatusProvider} as a sonner toast\n * (`toast.loading` for cold starts, `toast.error` for unrecoverable\n * states), keyed by the worst kind so only one indicator toast is on\n * screen at a time.\n *\n * Forwards `Toaster` props (`position` defaults to `top-right`, plus\n * `theme`, `richColors`, …). Apps that already mount their own\n * `<Toaster />` should drop this component and call\n * {@link useResourceStatusToaster} instead.\n *\n * @example\n * ```tsx\n * <ResourceStatusProvider>\n * <ResourceStatusIndicator />\n * <App />\n * </ResourceStatusProvider>\n * ```\n *\n * @example Custom render\n * ```tsx\n * <ResourceStatusIndicator\n * render={(agg) => (\n * <div className=\"my-card\">\n * {agg.worst?.kind} {agg.worst?.state.toLowerCase()}\n * </div>\n * )}\n * />\n * ```\n */\nexport function ResourceStatusIndicator({\n kind,\n renderers,\n toastClassName,\n render,\n position = \"top-right\",\n ...toasterProps\n}: ResourceStatusIndicatorProps = {}) {\n useResourceStatusToaster({ kind, renderers, toastClassName, render });\n return <Toaster position={position} {...toasterProps} />;\n}\n\nfunction defaultTitle(status: ResourceStatus): string {\n switch (status.severity) {\n case \"error\":\n return `${humanizeKind(status.kind)} unavailable`;\n case \"warning\":\n return `${humanizeKind(status.kind)} degraded`;\n default:\n return `${humanizeKind(status.kind)} not ready`;\n }\n}\n\nfunction defaultDescription(status: ResourceStatus): string {\n return status.summary ?? `Current state: ${status.state}.`;\n}\n\nfunction humanizeKind(kind: string): string {\n if (!kind) return \"Resource\";\n return kind\n .split(/[-_]/g)\n .filter(Boolean)\n .map((part) => part[0].toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nfunction formatElapsed(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.d.ts","names":[],"sources":["../../../src/react/table/data-table.tsx"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","names":[],"sources":["../../../src/react/table/data-table.tsx"],"mappings":";;;;;;;AAuEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAU,KAAA,EAAO,cAAA,GAAc,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.js","names":["Table"],"sources":["../../../src/react/table/data-table.tsx"],"sourcesContent":["import { flexRender } from \"@tanstack/react-table\";\nimport { ChevronDown } from \"lucide-react\";\nimport { formatFieldLabel } from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\nimport { Input } from \"../ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"../ui/table\";\nimport { TableWrapper } from \"./table-wrapper\";\nimport type { DataTableLabels, DataTableProps } from \"./types\";\n\n/**\n * Production-ready data table with automatic data fetching and state management\n *\n * Features:\n * - Automatic column generation from data structure\n * - Integrated with useAnalyticsQuery for data fetching\n * - Built-in loading, error, and empty states\n * - Dynamic filtering, sorting and pagination\n * - Column visibility controls\n * - Responsive design\n * - Supports opinionated mode (auto columns) and full-control mode (`children(table)`)\n *\n * @param props - Props for the DataTable component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query. Required - use `{}` if none.\n * @param props.filterColumn - The column to filter by\n * @param props.filterPlaceholder - The placeholder for the filter input\n * @param props.children - Optional children for full control mode\n * @returns - The rendered data table component\n *\n * @example\n * // Opinionated mode\n * <DataTable\n * queryKey=\"users-list\"\n * parameters={{ status: \"active\" }}\n * filterColumn=\"email\"\n * filterPlaceholder=\"Filter by email...\"\n * />\n * @example\n * // full control mode\n * <DataTable queryKey=\"users-list\" parameters={{ status: \"active\" }}>\n * {(table) => (\n * <div>\n * <h2>Custom Table UI</h2>\n * {table.getRowModel().rows.map(row => (\n * <div key={row.id}>{row.original.name}</div>\n * ))}\n * </div>\n * )}\n * </DataTable>\n */\nexport function DataTable(props: DataTableProps) {\n const {\n parameters,\n queryKey,\n filterColumn,\n filterPlaceholder,\n transform,\n labels,\n ariaLabel,\n testId,\n className,\n enableRowSelection,\n onRowSelectionChange,\n children,\n pageSize = 10,\n pageSizeOptions = [10, 25, 50, 100],\n } = props;\n\n const defaultLabels: Required<DataTableLabels> = {\n columnsButton: \"Columns\",\n noResults: \"No results found.\",\n rowsFound: `\\${count} row(s) found`,\n previousButton: \"Previous\",\n nextButton: \"Next\",\n rowsPerPage: \"Rows per page\",\n showing: `Showing \\${from} to \\${to} of \\${total}`,\n };\n\n const finalLabels = { ...defaultLabels, ...labels };\n\n return (\n <TableWrapper\n queryKey={queryKey}\n parameters={parameters}\n ariaLabel={ariaLabel}\n testId={testId}\n className={className}\n transformer={transform}\n enableRowSelection={enableRowSelection}\n onRowSelectionChange={onRowSelectionChange}\n pageSize={pageSize}\n >\n {(table) => {\n if (children) {\n return children(table);\n }\n\n const data = table.options.data;\n\n const defaultFilterColumn =\n filterColumn ||\n (data && data.length > 0\n ? Object.keys(data[0] as Record<string, any>).find(\n (key) =>\n typeof (data[0] as Record<string, any>)[key] === \"string\",\n )\n : null);\n\n const totalRows = table.getFilteredRowModel().rows.length;\n const currentPage = table.getState().pagination.pageIndex + 1;\n const currentPageSize = table.getState().pagination.pageSize;\n const fromRow =\n totalRows === 0 ? 0 : (currentPage - 1) * currentPageSize + 1;\n const toRow = Math.min(currentPage * currentPageSize, totalRows);\n\n return (\n <div className=\"w-full\">\n <div className=\"flex items-center py-4 gap-2\">\n {defaultFilterColumn && (\n <Input\n placeholder={\n filterPlaceholder ||\n `Filter by ${formatFieldLabel(defaultFilterColumn)}...`\n }\n value={\n (table\n .getColumn(defaultFilterColumn)\n ?.getFilterValue() as string) ?? \"\"\n }\n onChange={(event) =>\n table\n .getColumn(defaultFilterColumn)\n ?.setFilterValue(event.target.value)\n }\n className=\"max-w-sm\"\n />\n )}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"ml-auto\">\n {finalLabels.columnsButton}{\" \"}\n <ChevronDown className=\"ml-2 h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n <DropdownMenuCheckboxItem\n key={column.id}\n className=\"capitalize\"\n checked={column.getIsVisible()}\n onCheckedChange={(value) =>\n column.toggleVisibility(!!value)\n }\n >\n {formatFieldLabel(column.id)}\n </DropdownMenuCheckboxItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <div className=\"overflow-hidden rounded-md border border-border\">\n <div className=\"max-h-[600px] overflow-y-auto\">\n <Table>\n <TableHeader className=\"sticky top-0 bg-background z-10\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const isSelectColumn = header.column.id === \"select\";\n return (\n <TableHead\n key={header.id}\n style={{\n width: header.getSize(),\n position: \"relative\",\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </TableHead>\n );\n })}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={row.getIsSelected() && \"selected\"}\n >\n {row.getVisibleCells().map((cell) => {\n const isSelectColumn = cell.column.id === \"select\";\n return (\n <TableCell\n key={cell.id}\n style={{\n width: cell.column.getSize(),\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </TableCell>\n );\n })}\n </TableRow>\n ))\n ) : (\n <TableRow>\n <TableCell\n colSpan={table.getAllColumns().length}\n className=\"h-24 text-center\"\n >\n {finalLabels.noResults}\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n </div>\n {/* On mobile, flex-col-reverse places pagination buttons above the info row for easier thumb access */}\n <div className=\"flex flex-col-reverse sm:flex-row items-start sm:items-center justify-between gap-3 py-4\">\n <div className=\"flex flex-wrap items-center gap-3 sm:gap-6\">\n <div className=\"text-foreground text-sm\">\n {totalRows > 0\n ? finalLabels.showing\n .replace(`\\${from}`, fromRow.toString())\n .replace(`\\${to}`, toRow.toString())\n .replace(`\\${total}`, totalRows.toString())\n : finalLabels.noResults}\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-foreground\">\n {finalLabels.rowsPerPage}\n </span>\n <Select\n value={currentPageSize.toString()}\n onValueChange={(value) => {\n table.setPageSize(Number(value));\n }}\n >\n <SelectTrigger className=\"w-[70px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizeOptions.map((size) => (\n <SelectItem key={size} value={size.toString()}>\n {size}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"space-x-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n {finalLabels.previousButton}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n {finalLabels.nextButton}\n </Button>\n </div>\n </div>\n </div>\n );\n }}\n </TableWrapper>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"data-table.js","names":["Table"],"sources":["../../../src/react/table/data-table.tsx"],"sourcesContent":["import { flexRender } from \"@tanstack/react-table\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { formatFieldLabel } from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\nimport { Input } from \"../ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"../ui/table\";\nimport { TableWrapper } from \"./table-wrapper\";\nimport type { DataTableLabels, DataTableProps } from \"./types\";\n\n/**\n * Production-ready data table with automatic data fetching and state management\n *\n * Features:\n * - Automatic column generation from data structure\n * - Integrated with useAnalyticsQuery for data fetching\n * - Built-in loading, error, and empty states\n * - Dynamic filtering, sorting and pagination\n * - Column visibility controls\n * - Responsive design\n * - Supports opinionated mode (auto columns) and full-control mode (`children(table)`)\n *\n * @param props - Props for the DataTable component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query. Required - use `{}` if none.\n * @param props.filterColumn - The column to filter by\n * @param props.filterPlaceholder - The placeholder for the filter input\n * @param props.children - Optional children for full control mode\n * @returns - The rendered data table component\n *\n * @example\n * // Opinionated mode\n * <DataTable\n * queryKey=\"users-list\"\n * parameters={{ status: \"active\" }}\n * filterColumn=\"email\"\n * filterPlaceholder=\"Filter by email...\"\n * />\n * @example\n * // full control mode\n * <DataTable queryKey=\"users-list\" parameters={{ status: \"active\" }}>\n * {(table) => (\n * <div>\n * <h2>Custom Table UI</h2>\n * {table.getRowModel().rows.map(row => (\n * <div key={row.id}>{row.original.name}</div>\n * ))}\n * </div>\n * )}\n * </DataTable>\n */\nexport function DataTable(props: DataTableProps) {\n const {\n parameters,\n queryKey,\n filterColumn,\n filterPlaceholder,\n transform,\n labels,\n ariaLabel,\n testId,\n className,\n enableRowSelection,\n onRowSelectionChange,\n children,\n pageSize = 10,\n pageSizeOptions = [10, 25, 50, 100],\n } = props;\n\n const defaultLabels: Required<DataTableLabels> = {\n columnsButton: \"Columns\",\n noResults: \"No results found.\",\n rowsFound: `\\${count} row(s) found`,\n previousButton: \"Previous\",\n nextButton: \"Next\",\n rowsPerPage: \"Rows per page\",\n showing: `Showing \\${from} to \\${to} of \\${total}`,\n };\n\n const finalLabels = { ...defaultLabels, ...labels };\n\n return (\n <TableWrapper\n queryKey={queryKey}\n parameters={parameters}\n ariaLabel={ariaLabel}\n testId={testId}\n className={className}\n transformer={transform}\n enableRowSelection={enableRowSelection}\n onRowSelectionChange={onRowSelectionChange}\n pageSize={pageSize}\n >\n {(table) => {\n if (children) {\n return children(table);\n }\n\n const data = table.options.data;\n\n const defaultFilterColumn =\n filterColumn ||\n (data && data.length > 0\n ? Object.keys(data[0] as Record<string, any>).find(\n (key) =>\n typeof (data[0] as Record<string, any>)[key] === \"string\",\n )\n : null);\n\n const totalRows = table.getFilteredRowModel().rows.length;\n const currentPage = table.getState().pagination.pageIndex + 1;\n const currentPageSize = table.getState().pagination.pageSize;\n const fromRow =\n totalRows === 0 ? 0 : (currentPage - 1) * currentPageSize + 1;\n const toRow = Math.min(currentPage * currentPageSize, totalRows);\n\n return (\n <div className=\"w-full\">\n <div className=\"flex items-center py-4 gap-2\">\n {defaultFilterColumn && (\n <Input\n placeholder={\n filterPlaceholder ||\n `Filter by ${formatFieldLabel(defaultFilterColumn)}...`\n }\n value={\n (table\n .getColumn(defaultFilterColumn)\n ?.getFilterValue() as string) ?? \"\"\n }\n onChange={(event) =>\n table\n .getColumn(defaultFilterColumn)\n ?.setFilterValue(event.target.value)\n }\n className=\"max-w-sm\"\n />\n )}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"ml-auto\">\n {finalLabels.columnsButton}{\" \"}\n <ChevronDown className=\"ml-2 h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n <DropdownMenuCheckboxItem\n key={column.id}\n className=\"capitalize\"\n checked={column.getIsVisible()}\n onCheckedChange={(value) =>\n column.toggleVisibility(!!value)\n }\n >\n {formatFieldLabel(column.id)}\n </DropdownMenuCheckboxItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <div className=\"overflow-hidden rounded-md border border-border\">\n <div className=\"max-h-[600px] overflow-y-auto\">\n <Table>\n <TableHeader className=\"sticky top-0 bg-background z-10\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const isSelectColumn = header.column.id === \"select\";\n return (\n <TableHead\n key={header.id}\n style={{\n width: header.getSize(),\n position: \"relative\",\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </TableHead>\n );\n })}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={row.getIsSelected() && \"selected\"}\n >\n {row.getVisibleCells().map((cell) => {\n const isSelectColumn = cell.column.id === \"select\";\n return (\n <TableCell\n key={cell.id}\n style={{\n width: cell.column.getSize(),\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </TableCell>\n );\n })}\n </TableRow>\n ))\n ) : (\n <TableRow>\n <TableCell\n colSpan={table.getAllColumns().length}\n className=\"h-24 text-center\"\n >\n {finalLabels.noResults}\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n </div>\n {/* On mobile, flex-col-reverse places pagination buttons above the info row for easier thumb access */}\n <div className=\"flex flex-col-reverse sm:flex-row items-start sm:items-center justify-between gap-3 py-4\">\n <div className=\"flex flex-wrap items-center gap-3 sm:gap-6\">\n <div className=\"text-foreground text-sm\">\n {totalRows > 0\n ? finalLabels.showing\n .replace(`\\${from}`, fromRow.toString())\n .replace(`\\${to}`, toRow.toString())\n .replace(`\\${total}`, totalRows.toString())\n : finalLabels.noResults}\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-foreground\">\n {finalLabels.rowsPerPage}\n </span>\n <Select\n value={currentPageSize.toString()}\n onValueChange={(value) => {\n table.setPageSize(Number(value));\n }}\n >\n <SelectTrigger className=\"w-[70px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizeOptions.map((size) => (\n <SelectItem key={size} value={size.toString()}>\n {size}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"space-x-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n {finalLabels.previousButton}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n {finalLabels.nextButton}\n </Button>\n </div>\n </div>\n </div>\n );\n }}\n </TableWrapper>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-wrapper.js","names":[],"sources":["../../../src/react/table/table-wrapper.tsx"],"sourcesContent":["import {\n type Column,\n type ColumnFiltersState,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type Row,\n type RowSelectionState,\n type SortingState,\n type Table,\n useReactTable,\n type VisibilityState,\n} from \"@tanstack/react-table\";\nimport { ArrowUpDown } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useAnalyticsQuery } from \"..\";\nimport {\n formatChartValue,\n formatFieldLabel,\n SAFE_KEY_REGEX,\n} from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport { Checkbox } from \"../ui/checkbox\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { TableWrapperProps } from \"./types\";\n\nconst CHECKBOX_COLUMN_WIDTH = 40;\n\n/**\n * Wrapper component for tables with automatic data fetching and state management\n * This component handles:\n * - Data fetching via useAnalyticsQuery\n * - Loading, error, and empty states with proper UI components\n * - Data transformation (optional)\n * - Dynamic column generation from data structure\n * - TanStack Table instance creation with all features (sorting, filtering, pagination, etc.)\n *\n * @template TRaw - The raw data type returned by the analytics query\n * @template TProcessed - The processed data type after transformation\n *\n * @param props - Props for the TableWrapper component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query\n * @param props.transformer - Optional function to transform raw data before creating table\n * @param props.children - Render function that receives the TanStack Table instance\n * @param props.className - Optional CSS class name for the wrapper\n * @param props.ariaLabel - Optional accessibility label\n * @param props.testId - Optional test ID for testing\n *\n * @returns The rendered table with state management\n */\nexport function TableWrapper<TRaw = any, TProcessed = any>(\n props: TableWrapperProps<TRaw, TProcessed>,\n) {\n const {\n queryKey,\n parameters,\n transformer,\n children,\n className,\n ariaLabel,\n testId,\n enableRowSelection = false,\n onRowSelectionChange,\n pageSize = 10,\n } = props;\n\n const [sorting, setSorting] = useState<SortingState>([]);\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);\n const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});\n const [rowSelection, setRowSelection] = useState<RowSelectionState>({});\n\n const { data, loading, error } = useAnalyticsQuery<TRaw[]>(\n queryKey,\n parameters,\n );\n\n useEffect(() => {\n if (onRowSelectionChange && enableRowSelection) {\n onRowSelectionChange(rowSelection);\n }\n }, [rowSelection, onRowSelectionChange, enableRowSelection]);\n\n const hasData = data && data.length > 0;\n\n const processedData = hasData\n ? transformer\n ? transformer(data)\n : (data as unknown as TProcessed[])\n : [];\n\n const tableColumns = useMemo(() => {\n if (!hasData) return [];\n\n if (!processedData[0] || typeof processedData[0] !== \"object\") {\n console.warn(\"Invalid data format for DataTable\");\n return [];\n }\n\n const dataColumns = Object.keys(processedData[0] as object)\n .filter((key) => SAFE_KEY_REGEX.test(key))\n .map((key) => {\n const formattedLabel = formatFieldLabel(key);\n return {\n accessorKey: key,\n header: ({ column }: { column: Column<TProcessed> }) => {\n return (\n <Button\n variant=\"ghost\"\n onClick={() =>\n column.toggleSorting(column.getIsSorted() === \"asc\")\n }\n className=\"h-8 px-2\"\n >\n {formattedLabel}\n <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n </Button>\n );\n },\n cell: ({ row }: { row: Row<TProcessed> }) => {\n const value = row.getValue(key);\n if (typeof value === \"number\" || Number.isFinite(Number(value))) {\n return (\n <div className=\"text-right font-mono\">\n {formatChartValue(Number(value), key)}\n </div>\n );\n }\n return <div>{String(value)}</div>;\n },\n };\n });\n\n if (enableRowSelection) {\n return [\n {\n id: \"select\",\n maxSize: CHECKBOX_COLUMN_WIDTH,\n minSize: CHECKBOX_COLUMN_WIDTH,\n header: ({ table }: { table: Table<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={\n table.getIsAllPageRowsSelected() ||\n (table.getIsSomePageRowsSelected() && \"indeterminate\")\n }\n onCheckedChange={(value) =>\n table.toggleAllPageRowsSelected(!!value)\n }\n aria-label=\"Select all\"\n />\n </div>\n ),\n cell: ({ row }: { row: Row<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={row.getIsSelected()}\n onCheckedChange={(value) => row.toggleSelected(!!value)}\n aria-label=\"Select row\"\n />\n </div>\n ),\n enableSorting: false,\n enableHiding: false,\n },\n ...dataColumns,\n ];\n }\n\n return dataColumns;\n }, [hasData, processedData, enableRowSelection]);\n\n const table = useReactTable({\n data: processedData,\n columns: tableColumns,\n onSortingChange: setSorting,\n onColumnFiltersChange: setColumnFilters,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n onColumnVisibilityChange: setColumnVisibility,\n onRowSelectionChange: setRowSelection,\n state: {\n sorting,\n columnFilters,\n columnVisibility,\n rowSelection,\n },\n initialState: {\n pagination: {\n pageSize: pageSize,\n },\n },\n });\n\n if (loading) return <LoadingSkeleton />;\n if (error)\n return (\n <ErrorState error={typeof error === \"string\" ? error : \"Unknown error\"} />\n );\n\n if (!hasData) return <EmptyState />;\n\n return (\n <section className={className} aria-label={ariaLabel} data-testid={testId}>\n {children(table)}\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"table-wrapper.js","names":[],"sources":["../../../src/react/table/table-wrapper.tsx"],"sourcesContent":["import {\n type Column,\n type ColumnFiltersState,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type Row,\n type RowSelectionState,\n type SortingState,\n type Table,\n useReactTable,\n type VisibilityState,\n} from \"@tanstack/react-table\";\nimport { ArrowUpDown } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport { useAnalyticsQuery } from \"..\";\nimport {\n formatChartValue,\n formatFieldLabel,\n SAFE_KEY_REGEX,\n} from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport { Checkbox } from \"../ui/checkbox\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { TableWrapperProps } from \"./types\";\n\nconst CHECKBOX_COLUMN_WIDTH = 40;\n\n/**\n * Wrapper component for tables with automatic data fetching and state management\n * This component handles:\n * - Data fetching via useAnalyticsQuery\n * - Loading, error, and empty states with proper UI components\n * - Data transformation (optional)\n * - Dynamic column generation from data structure\n * - TanStack Table instance creation with all features (sorting, filtering, pagination, etc.)\n *\n * @template TRaw - The raw data type returned by the analytics query\n * @template TProcessed - The processed data type after transformation\n *\n * @param props - Props for the TableWrapper component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query\n * @param props.transformer - Optional function to transform raw data before creating table\n * @param props.children - Render function that receives the TanStack Table instance\n * @param props.className - Optional CSS class name for the wrapper\n * @param props.ariaLabel - Optional accessibility label\n * @param props.testId - Optional test ID for testing\n *\n * @returns The rendered table with state management\n */\nexport function TableWrapper<TRaw = any, TProcessed = any>(\n props: TableWrapperProps<TRaw, TProcessed>,\n) {\n const {\n queryKey,\n parameters,\n transformer,\n children,\n className,\n ariaLabel,\n testId,\n enableRowSelection = false,\n onRowSelectionChange,\n pageSize = 10,\n } = props;\n\n const [sorting, setSorting] = useState<SortingState>([]);\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);\n const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});\n const [rowSelection, setRowSelection] = useState<RowSelectionState>({});\n\n const { data, loading, error } = useAnalyticsQuery<TRaw[]>(\n queryKey,\n parameters,\n );\n\n useEffect(() => {\n if (onRowSelectionChange && enableRowSelection) {\n onRowSelectionChange(rowSelection);\n }\n }, [rowSelection, onRowSelectionChange, enableRowSelection]);\n\n const hasData = data && data.length > 0;\n\n const processedData = hasData\n ? transformer\n ? transformer(data)\n : (data as unknown as TProcessed[])\n : [];\n\n const tableColumns = useMemo(() => {\n if (!hasData) return [];\n\n if (!processedData[0] || typeof processedData[0] !== \"object\") {\n console.warn(\"Invalid data format for DataTable\");\n return [];\n }\n\n const dataColumns = Object.keys(processedData[0] as object)\n .filter((key) => SAFE_KEY_REGEX.test(key))\n .map((key) => {\n const formattedLabel = formatFieldLabel(key);\n return {\n accessorKey: key,\n header: ({ column }: { column: Column<TProcessed> }) => {\n return (\n <Button\n variant=\"ghost\"\n onClick={() =>\n column.toggleSorting(column.getIsSorted() === \"asc\")\n }\n className=\"h-8 px-2\"\n >\n {formattedLabel}\n <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n </Button>\n );\n },\n cell: ({ row }: { row: Row<TProcessed> }) => {\n const value = row.getValue(key);\n if (typeof value === \"number\" || Number.isFinite(Number(value))) {\n return (\n <div className=\"text-right font-mono\">\n {formatChartValue(Number(value), key)}\n </div>\n );\n }\n return <div>{String(value)}</div>;\n },\n };\n });\n\n if (enableRowSelection) {\n return [\n {\n id: \"select\",\n maxSize: CHECKBOX_COLUMN_WIDTH,\n minSize: CHECKBOX_COLUMN_WIDTH,\n header: ({ table }: { table: Table<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={\n table.getIsAllPageRowsSelected() ||\n (table.getIsSomePageRowsSelected() && \"indeterminate\")\n }\n onCheckedChange={(value) =>\n table.toggleAllPageRowsSelected(!!value)\n }\n aria-label=\"Select all\"\n />\n </div>\n ),\n cell: ({ row }: { row: Row<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={row.getIsSelected()}\n onCheckedChange={(value) => row.toggleSelected(!!value)}\n aria-label=\"Select row\"\n />\n </div>\n ),\n enableSorting: false,\n enableHiding: false,\n },\n ...dataColumns,\n ];\n }\n\n return dataColumns;\n }, [hasData, processedData, enableRowSelection]);\n\n const table = useReactTable({\n data: processedData,\n columns: tableColumns,\n onSortingChange: setSorting,\n onColumnFiltersChange: setColumnFilters,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n onColumnVisibilityChange: setColumnVisibility,\n onRowSelectionChange: setRowSelection,\n state: {\n sorting,\n columnFilters,\n columnVisibility,\n rowSelection,\n },\n initialState: {\n pagination: {\n pageSize: pageSize,\n },\n },\n });\n\n if (loading) return <LoadingSkeleton />;\n if (error)\n return (\n <ErrorState error={typeof error === \"string\" ? error : \"Unknown error\"} />\n );\n\n if (!hasData) return <EmptyState />;\n\n return (\n <section className={className} aria-label={ariaLabel} data-testid={testId}>\n {children(table)}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import * as class_variance_authority_types0 from "class-variance-authority/types
|
|
|
6
6
|
//#region src/react/ui/button.d.ts
|
|
7
7
|
declare const buttonVariants: (props?: ({
|
|
8
8
|
variant?: "default" | "destructive" | "secondary" | "outline" | "ghost" | "link" | null | undefined;
|
|
9
|
-
size?: "default" | "sm" | "
|
|
9
|
+
size?: "default" | "sm" | "icon-sm" | "lg" | "icon" | "icon-lg" | null | undefined;
|
|
10
10
|
} & class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
11
11
|
/** Clickable button with multiple variants and sizes */
|
|
12
12
|
declare function Button({
|
|
@@ -20,7 +20,7 @@ declare function InputGroupAddon({
|
|
|
20
20
|
...props
|
|
21
21
|
}: React$1.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>): react_jsx_runtime0.JSX.Element;
|
|
22
22
|
declare const inputGroupButtonVariants: (props?: ({
|
|
23
|
-
size?: "
|
|
23
|
+
size?: "xs" | "sm" | "icon-xs" | "icon-sm" | null | undefined;
|
|
24
24
|
} & class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
25
25
|
/** Button integrated within an input group */
|
|
26
26
|
declare function InputGroupButton({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variants.d.ts","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"variants.d.ts","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"mappings":";;;;;UAqCiB,aAAA;;AAAjB;;;;;EAOE,OAAA;EAEU;EAAV,QAAA,EAAU,SAAA;EAUV;;;;AA0DF;;;EA5DE,MAAA;EA8DA;EA5DA,SAAA;EA8DA;;;;;;;;EArDA,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiDc,QAAA,CAAA;EACd,OAAA;EACA,QAAA;EACA,MAAA;EACA,SAAA;EACA;AAAA,GACC,aAAA,GAAa,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variants.js","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"sourcesContent":["import {\n Check,\n CheckCheck,\n ChevronLeft,\n ChevronRight,\n Loader2,\n} from \"lucide-react\";\nimport {\n Children,\n type ComponentProps,\n type CSSProperties,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useMemo,\n useState,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Variant, type VariantProps } from \"./variant\";\n\n/** Endpoint the recorder plugin (`uiVariants()`) mounts in development. */\nconst CONFIRM_ENDPOINT = \"/api/ui-variants/confirm\";\n\n/** Abort the confirm request after this long so the button can't hang. */\nconst CONFIRM_TIMEOUT_MS = 5000;\n\n/**\n * Default switcher accent — a violet chosen to stand apart from most apps'\n * `primary`, so the dev-only picker chrome doesn't blend into the content it\n * wraps. Override per-block via the `accent` prop.\n */\nconst DEFAULT_ACCENT = \"#8b5cf6\";\n\ntype ConfirmState = \"idle\" | \"saving\" | \"recorded\" | \"unavailable\" | \"error\";\n\n/** Props for {@link Variants}. */\nexport interface VariantsProps {\n /**\n * Stable, unique identifier for this block. The variant-picking agent matches\n * on it to finalize the chosen variant into source, so it must be unique per\n * file. (Named `blockId`, not `id`, because it is a semantic key — not a DOM\n * element id.)\n */\n blockId: string;\n /** `<Variant>` children — one candidate UI each. */\n children: ReactNode;\n /**\n * How the wrapper flows in the layout. Defaults to `\"block\"` (full width,\n * variants stack vertically) — the right choice for page sections, heroes,\n * and other block-level regions, which is the common case. Use `\"inline\"`\n * when wrapping a small inline element such as a single button, so the\n * wrapper hugs its content instead of spanning the row.\n */\n layout?: \"block\" | \"inline\";\n /** Class name applied to the block wrapper (merged after `layout`). */\n className?: string;\n /**\n * Any CSS color for the switcher chrome (dot, ring, control cluster, count\n * pill, confirm tick). Every accent element derives from this one value.\n * Defaults to {@link DEFAULT_ACCENT}. The error state stays red regardless.\n *\n * @example accent=\"var(--primary)\" // match the app theme\n * @example accent=\"#f43f5e\" // rose\n */\n accent?: string;\n}\n\nfunction isVariantElement(node: ReactNode): node is ReactElement<VariantProps> {\n return isValidElement(node) && node.type === Variant;\n}\n\n/**\n * Dev-time UI picker: renders several `<Variant>` candidates one at a time.\n *\n * At rest the block shows a small, slowly-pulsing corner dot so it's clear\n * it's \"in variant state\" without hovering — the dot pulses while a choice is\n * still pending and goes calm once recorded. On hover (or focus) an accent\n * ring outlines the block and the dot gives way to a compact control cluster\n * (‹ index/total › ✓) that scales out of the corner. Confirming POSTs the\n * choice to the `uiVariants()` recorder plugin; a coding agent then finalizes\n * the chosen variant into source, removing this wrapper.\n *\n * All switcher chrome uses the `accent` color (default {@link DEFAULT_ACCENT},\n * a violet distinct from most apps' `primary`) so it doesn't blend into the\n * content it wraps.\n *\n * Degrades gracefully. In a production build the picker chrome is dropped\n * entirely and only the first variant renders as plain content — a safety net\n * for a block that shipped without being finalized. In dev, if the recorder\n * endpoint is absent (feature off) the switcher still works as a viewer and\n * Confirm reports that recording is unavailable.\n *\n * @example Page section (default layout — full-width, stacks vertically)\n * ```tsx\n * <Variants blockId=\"hero\">\n * <Variant label=\"Split\"><section>…</section></Variant>\n * <Variant label=\"Centered\"><section>…</section></Variant>\n * </Variants>\n * ```\n *\n * @example Small inline element (hug the content)\n * ```tsx\n * <Variants blockId=\"hero-cta\" layout=\"inline\">\n * <Variant label=\"Ghost\"><Button variant=\"ghost\">Get started</Button></Variant>\n * <Variant label=\"Solid\"><Button>Get started</Button></Variant>\n * </Variants>\n * ```\n *\n * @example Custom accent\n * ```tsx\n * <Variants blockId=\"hero\" accent=\"#f43f5e\">…</Variants>\n * ```\n */\nexport function Variants({\n blockId,\n children,\n layout = \"block\",\n className,\n accent = DEFAULT_ACCENT,\n}: VariantsProps) {\n const variants = useMemo(\n () => Children.toArray(children).filter(isVariantElement),\n [children],\n );\n const [active, setActive] = useState(0);\n const [confirmState, setConfirmState] = useState<ConfirmState>(\"idle\");\n\n if (variants.length === 0) return null;\n if (!import.meta.env.DEV) return <>{variants[0]}</>;\n\n const clampedActive = Math.min(active, variants.length - 1);\n const current = variants[clampedActive];\n const label = current.props.label ?? `Variant ${clampedActive + 1}`;\n const atStart = clampedActive === 0;\n const atEnd = clampedActive === variants.length - 1;\n\n const go = (delta: number) => {\n // No wrap-around: clamp within bounds.\n setActive((i) => Math.max(0, Math.min(variants.length - 1, i + delta)));\n setConfirmState(\"idle\");\n };\n\n const confirm = async () => {\n if (confirmState === \"saving\") return;\n setConfirmState(\"saving\");\n\n // Bound the request so a hung recorder can't leave the button stuck in\n // \"saving\" forever.\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CONFIRM_TIMEOUT_MS);\n try {\n const res = await fetch(CONFIRM_ENDPOINT, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ blockId, chosenIndex: clampedActive, label }),\n signal: controller.signal,\n });\n // 404/403: the recorder isn't mounted (feature off or a production\n // build). Degrade to a plain viewer.\n if (res.status === 404 || res.status === 403) {\n setConfirmState(\"unavailable\");\n return;\n }\n // Any other non-OK status (e.g. a 500) is a failure the user can retry.\n setConfirmState(res.ok ? \"recorded\" : \"error\");\n } catch {\n // Network failure or timeout abort — the request never completed.\n setConfirmState(\"error\");\n } finally {\n clearTimeout(timeout);\n }\n };\n\n return (\n <div\n data-slot=\"variants\"\n // All accent chrome derives from this one variable; children read it via\n // the cascade, so the `accent` prop flows everywhere without prop-drilling.\n style={{ \"--variants-accent\": accent } as CSSProperties}\n className={cn(\n // Ring appears only on hover/focus so blocks stay calm at rest; the\n // corner dot is the resting \"in variant state\" indicator.\n \"group relative rounded-md ring-[color:var(--variants-accent)] transition-[box-shadow] duration-200\",\n layout === \"inline\" ? \"inline-block\" : \"block w-full\",\n \"hover:ring-1 focus-within:ring-1\",\n className,\n )}\n >\n {current}\n\n <StateDot\n state={confirmState}\n className=\"group-hover:pointer-events-none group-hover:opacity-0 group-focus-within:pointer-events-none group-focus-within:opacity-0\"\n />\n\n <div\n data-slot=\"variants-switcher\"\n className={cn(\n \"absolute -top-3 right-0 z-20 flex items-center gap-0.5 rounded-full border bg-background/95 px-1 py-0.5 shadow-md backdrop-blur\",\n \"border-[color-mix(in_srgb,var(--variants-accent)_40%,transparent)]\",\n \"pointer-events-none origin-top-right scale-90 opacity-0 transition-all duration-150\",\n \"group-hover:pointer-events-auto group-hover:scale-100 group-hover:opacity-100\",\n \"group-focus-within:pointer-events-auto group-focus-within:scale-100 group-focus-within:opacity-100\",\n )}\n >\n <RoundButton\n aria-label=\"Previous variant\"\n disabled={atStart}\n onClick={() => go(-1)}\n >\n <ChevronLeft className=\"size-3.5\" />\n </RoundButton>\n\n <span className=\"select-none rounded-full bg-[color-mix(in_srgb,var(--variants-accent)_15%,transparent)] px-1 text-[10px] font-semibold text-[color:var(--variants-accent)] tabular-nums\">\n {clampedActive + 1}/{variants.length}\n </span>\n\n <RoundButton\n aria-label=\"Next variant\"\n disabled={atEnd}\n onClick={() => go(1)}\n >\n <ChevronRight className=\"size-3.5\" />\n </RoundButton>\n\n <ConfirmButton state={confirmState} label={label} onClick={confirm} />\n </div>\n </div>\n );\n}\n\nfunction dotColorFor(state: ConfirmState): string {\n switch (state) {\n case \"error\":\n return \"bg-destructive\";\n case \"unavailable\":\n return \"bg-muted-foreground/40\";\n default:\n return \"bg-[color:var(--variants-accent)]\";\n }\n}\n\n/**\n * Resting corner indicator: pulses while a choice is pending, calm once\n * recorded — so a block's picked/unpicked state reads at a glance without\n * hovering.\n */\nfunction StateDot({\n state,\n className,\n}: {\n state: ConfirmState;\n className?: string;\n}) {\n const pulsing = state === \"idle\" || state === \"saving\";\n const color = dotColorFor(state);\n\n return (\n <span\n aria-hidden\n data-slot=\"variants-dot\"\n className={cn(\n \"absolute -top-1 -right-1 z-10 flex size-3 items-center justify-center transition-opacity duration-150\",\n className,\n )}\n >\n {pulsing && (\n <span\n className={cn(\n // Slow, gentle pulse (~2.5s) so it reads as an ambient hint,\n // not an alarm. Overrides tailwind's default 1s ping cadence.\n \"absolute inline-flex size-full animate-ping rounded-full opacity-50 [animation-duration:2.5s]\",\n color,\n )}\n />\n )}\n <span\n className={cn(\n \"relative inline-flex size-2.5 rounded-full ring-2 ring-background\",\n color,\n )}\n />\n </span>\n );\n}\n\nfunction RoundButton({\n children,\n disabled,\n className,\n ...props\n}: ComponentProps<\"button\">) {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded-full text-muted-foreground transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground\",\n \"disabled:pointer-events-none disabled:opacity-30\",\n className,\n )}\n {...props}\n >\n {children}\n </button>\n );\n}\n\n/**\n * Round tick that confirms the active variant. State is conveyed by icon +\n * color only (no text). The chosen `label` rides along in the title/aria for\n * accessibility and hover context.\n */\nfunction ConfirmButton({\n state,\n label,\n onClick,\n}: {\n state: ConfirmState;\n label: string;\n onClick: () => void;\n}) {\n const title =\n state === \"recorded\"\n ? `Recorded \"${label}\" — ask the agent to finalize`\n : state === \"unavailable\"\n ? \"Recorder unavailable\"\n : state === \"error\"\n ? \"Failed — click to retry\"\n : `Confirm \"${label}\"`;\n\n return (\n <RoundButton\n aria-label={title}\n title={title}\n disabled={state === \"saving\" || state === \"unavailable\"}\n onClick={onClick}\n className={cn(\n state === \"recorded\" &&\n \"text-[color:var(--variants-accent)] hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n state === \"error\" &&\n \"text-destructive hover:bg-destructive/10 hover:text-destructive\",\n (state === \"idle\" || state === \"saving\") &&\n \"hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n )}\n >\n {state === \"saving\" ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : state === \"recorded\" ? (\n <CheckCheck className=\"size-3.5\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n </RoundButton>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"variants.js","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"sourcesContent":["import {\n Check,\n CheckCheck,\n ChevronLeft,\n ChevronRight,\n Loader2,\n} from \"lucide-react\";\nimport {\n Children,\n type ComponentProps,\n type CSSProperties,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useMemo,\n useState,\n} from \"react\";\n\nimport { cn } from \"../lib/utils\";\nimport { Variant, type VariantProps } from \"./variant\";\n\n/** Endpoint the recorder plugin (`uiVariants()`) mounts in development. */\nconst CONFIRM_ENDPOINT = \"/api/ui-variants/confirm\";\n\n/** Abort the confirm request after this long so the button can't hang. */\nconst CONFIRM_TIMEOUT_MS = 5000;\n\n/**\n * Default switcher accent — a violet chosen to stand apart from most apps'\n * `primary`, so the dev-only picker chrome doesn't blend into the content it\n * wraps. Override per-block via the `accent` prop.\n */\nconst DEFAULT_ACCENT = \"#8b5cf6\";\n\ntype ConfirmState = \"idle\" | \"saving\" | \"recorded\" | \"unavailable\" | \"error\";\n\n/** Props for {@link Variants}. */\nexport interface VariantsProps {\n /**\n * Stable, unique identifier for this block. The variant-picking agent matches\n * on it to finalize the chosen variant into source, so it must be unique per\n * file. (Named `blockId`, not `id`, because it is a semantic key — not a DOM\n * element id.)\n */\n blockId: string;\n /** `<Variant>` children — one candidate UI each. */\n children: ReactNode;\n /**\n * How the wrapper flows in the layout. Defaults to `\"block\"` (full width,\n * variants stack vertically) — the right choice for page sections, heroes,\n * and other block-level regions, which is the common case. Use `\"inline\"`\n * when wrapping a small inline element such as a single button, so the\n * wrapper hugs its content instead of spanning the row.\n */\n layout?: \"block\" | \"inline\";\n /** Class name applied to the block wrapper (merged after `layout`). */\n className?: string;\n /**\n * Any CSS color for the switcher chrome (dot, ring, control cluster, count\n * pill, confirm tick). Every accent element derives from this one value.\n * Defaults to {@link DEFAULT_ACCENT}. The error state stays red regardless.\n *\n * @example accent=\"var(--primary)\" // match the app theme\n * @example accent=\"#f43f5e\" // rose\n */\n accent?: string;\n}\n\nfunction isVariantElement(node: ReactNode): node is ReactElement<VariantProps> {\n return isValidElement(node) && node.type === Variant;\n}\n\n/**\n * Dev-time UI picker: renders several `<Variant>` candidates one at a time.\n *\n * At rest the block shows a small, slowly-pulsing corner dot so it's clear\n * it's \"in variant state\" without hovering — the dot pulses while a choice is\n * still pending and goes calm once recorded. On hover (or focus) an accent\n * ring outlines the block and the dot gives way to a compact control cluster\n * (‹ index/total › ✓) that scales out of the corner. Confirming POSTs the\n * choice to the `uiVariants()` recorder plugin; a coding agent then finalizes\n * the chosen variant into source, removing this wrapper.\n *\n * All switcher chrome uses the `accent` color (default {@link DEFAULT_ACCENT},\n * a violet distinct from most apps' `primary`) so it doesn't blend into the\n * content it wraps.\n *\n * Degrades gracefully. In a production build the picker chrome is dropped\n * entirely and only the first variant renders as plain content — a safety net\n * for a block that shipped without being finalized. In dev, if the recorder\n * endpoint is absent (feature off) the switcher still works as a viewer and\n * Confirm reports that recording is unavailable.\n *\n * @example Page section (default layout — full-width, stacks vertically)\n * ```tsx\n * <Variants blockId=\"hero\">\n * <Variant label=\"Split\"><section>…</section></Variant>\n * <Variant label=\"Centered\"><section>…</section></Variant>\n * </Variants>\n * ```\n *\n * @example Small inline element (hug the content)\n * ```tsx\n * <Variants blockId=\"hero-cta\" layout=\"inline\">\n * <Variant label=\"Ghost\"><Button variant=\"ghost\">Get started</Button></Variant>\n * <Variant label=\"Solid\"><Button>Get started</Button></Variant>\n * </Variants>\n * ```\n *\n * @example Custom accent\n * ```tsx\n * <Variants blockId=\"hero\" accent=\"#f43f5e\">…</Variants>\n * ```\n */\nexport function Variants({\n blockId,\n children,\n layout = \"block\",\n className,\n accent = DEFAULT_ACCENT,\n}: VariantsProps) {\n const variants = useMemo(\n () => Children.toArray(children).filter(isVariantElement),\n [children],\n );\n const [active, setActive] = useState(0);\n const [confirmState, setConfirmState] = useState<ConfirmState>(\"idle\");\n\n if (variants.length === 0) return null;\n if (!import.meta.env.DEV) return <>{variants[0]}</>;\n\n const clampedActive = Math.min(active, variants.length - 1);\n const current = variants[clampedActive];\n const label = current.props.label ?? `Variant ${clampedActive + 1}`;\n const atStart = clampedActive === 0;\n const atEnd = clampedActive === variants.length - 1;\n\n const go = (delta: number) => {\n // No wrap-around: clamp within bounds.\n setActive((i) => Math.max(0, Math.min(variants.length - 1, i + delta)));\n setConfirmState(\"idle\");\n };\n\n const confirm = async () => {\n if (confirmState === \"saving\") return;\n setConfirmState(\"saving\");\n\n // Bound the request so a hung recorder can't leave the button stuck in\n // \"saving\" forever.\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CONFIRM_TIMEOUT_MS);\n try {\n const res = await fetch(CONFIRM_ENDPOINT, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ blockId, chosenIndex: clampedActive, label }),\n signal: controller.signal,\n });\n // 404/403: the recorder isn't mounted (feature off or a production\n // build). Degrade to a plain viewer.\n if (res.status === 404 || res.status === 403) {\n setConfirmState(\"unavailable\");\n return;\n }\n // Any other non-OK status (e.g. a 500) is a failure the user can retry.\n setConfirmState(res.ok ? \"recorded\" : \"error\");\n } catch {\n // Network failure or timeout abort — the request never completed.\n setConfirmState(\"error\");\n } finally {\n clearTimeout(timeout);\n }\n };\n\n return (\n <div\n data-slot=\"variants\"\n // All accent chrome derives from this one variable; children read it via\n // the cascade, so the `accent` prop flows everywhere without prop-drilling.\n style={{ \"--variants-accent\": accent } as CSSProperties}\n className={cn(\n // Ring appears only on hover/focus so blocks stay calm at rest; the\n // corner dot is the resting \"in variant state\" indicator.\n \"group relative rounded-md ring-[color:var(--variants-accent)] transition-[box-shadow] duration-200\",\n layout === \"inline\" ? \"inline-block\" : \"block w-full\",\n \"hover:ring-1 focus-within:ring-1\",\n className,\n )}\n >\n {current}\n\n <StateDot\n state={confirmState}\n className=\"group-hover:pointer-events-none group-hover:opacity-0 group-focus-within:pointer-events-none group-focus-within:opacity-0\"\n />\n\n <div\n data-slot=\"variants-switcher\"\n className={cn(\n \"absolute -top-3 right-0 z-20 flex items-center gap-0.5 rounded-full border bg-background/95 px-1 py-0.5 shadow-md backdrop-blur\",\n \"border-[color-mix(in_srgb,var(--variants-accent)_40%,transparent)]\",\n \"pointer-events-none origin-top-right scale-90 opacity-0 transition-all duration-150\",\n \"group-hover:pointer-events-auto group-hover:scale-100 group-hover:opacity-100\",\n \"group-focus-within:pointer-events-auto group-focus-within:scale-100 group-focus-within:opacity-100\",\n )}\n >\n <RoundButton\n aria-label=\"Previous variant\"\n disabled={atStart}\n onClick={() => go(-1)}\n >\n <ChevronLeft className=\"size-3.5\" />\n </RoundButton>\n\n <span className=\"select-none rounded-full bg-[color-mix(in_srgb,var(--variants-accent)_15%,transparent)] px-1 text-[10px] font-semibold text-[color:var(--variants-accent)] tabular-nums\">\n {clampedActive + 1}/{variants.length}\n </span>\n\n <RoundButton\n aria-label=\"Next variant\"\n disabled={atEnd}\n onClick={() => go(1)}\n >\n <ChevronRight className=\"size-3.5\" />\n </RoundButton>\n\n <ConfirmButton state={confirmState} label={label} onClick={confirm} />\n </div>\n </div>\n );\n}\n\nfunction dotColorFor(state: ConfirmState): string {\n switch (state) {\n case \"error\":\n return \"bg-destructive\";\n case \"unavailable\":\n return \"bg-muted-foreground/40\";\n default:\n return \"bg-[color:var(--variants-accent)]\";\n }\n}\n\n/**\n * Resting corner indicator: pulses while a choice is pending, calm once\n * recorded — so a block's picked/unpicked state reads at a glance without\n * hovering.\n */\nfunction StateDot({\n state,\n className,\n}: {\n state: ConfirmState;\n className?: string;\n}) {\n const pulsing = state === \"idle\" || state === \"saving\";\n const color = dotColorFor(state);\n\n return (\n <span\n aria-hidden\n data-slot=\"variants-dot\"\n className={cn(\n \"absolute -top-1 -right-1 z-10 flex size-3 items-center justify-center transition-opacity duration-150\",\n className,\n )}\n >\n {pulsing && (\n <span\n className={cn(\n // Slow, gentle pulse (~2.5s) so it reads as an ambient hint,\n // not an alarm. Overrides tailwind's default 1s ping cadence.\n \"absolute inline-flex size-full animate-ping rounded-full opacity-50 [animation-duration:2.5s]\",\n color,\n )}\n />\n )}\n <span\n className={cn(\n \"relative inline-flex size-2.5 rounded-full ring-2 ring-background\",\n color,\n )}\n />\n </span>\n );\n}\n\nfunction RoundButton({\n children,\n disabled,\n className,\n ...props\n}: ComponentProps<\"button\">) {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded-full text-muted-foreground transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground\",\n \"disabled:pointer-events-none disabled:opacity-30\",\n className,\n )}\n {...props}\n >\n {children}\n </button>\n );\n}\n\n/**\n * Round tick that confirms the active variant. State is conveyed by icon +\n * color only (no text). The chosen `label` rides along in the title/aria for\n * accessibility and hover context.\n */\nfunction ConfirmButton({\n state,\n label,\n onClick,\n}: {\n state: ConfirmState;\n label: string;\n onClick: () => void;\n}) {\n const title =\n state === \"recorded\"\n ? `Recorded \"${label}\" — ask the agent to finalize`\n : state === \"unavailable\"\n ? \"Recorder unavailable\"\n : state === \"error\"\n ? \"Failed — click to retry\"\n : `Confirm \"${label}\"`;\n\n return (\n <RoundButton\n aria-label={title}\n title={title}\n disabled={state === \"saving\" || state === \"unavailable\"}\n onClick={onClick}\n className={cn(\n state === \"recorded\" &&\n \"text-[color:var(--variants-accent)] hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n state === \"error\" &&\n \"text-destructive hover:bg-destructive/10 hover:text-destructive\",\n (state === \"idle\" || state === \"saving\") &&\n \"hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n )}\n >\n {state === \"saving\" ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : state === \"recorded\" ? (\n <CheckCheck className=\"size-3.5\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n </RoundButton>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../../src/schemas/manifest.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../../src/schemas/manifest.ts"],"mappings":";;;cAwPa,yBAAA,EAAyB,CAAA,CAAA,qBAAA,EAAA,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;cA8FzB,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkQxB,4BAAA,EAA4B,CAAA,CAAA,SAAA;;;;;cAoE5B,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAqgBrB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,yBAAA;AAAA,KACrC,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,KAIpC,sBAAA,GAAyB,CAAA,CAAE,KAAA,QAC9B,4BAAA;AAAA,KAEG,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA"}
|