@ekanos/sdk 0.15.1 → 0.17.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/dist/components/base-marketplace-tile.js +2 -4
- package/dist/components/base-marketplace-tile.js.map +1 -1
- package/dist/components/widgets/ai-prompt-chip.js +3 -5
- package/dist/components/widgets/ai-prompt-chip.js.map +1 -1
- package/dist/components/widgets/widget-ask-bar.js +3 -4
- package/dist/components/widgets/widget-ask-bar.js.map +1 -1
- package/dist/components/widgets/widget-state-components.js +1 -1
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/eslint.design-preset.mjs +2 -2
- package/eslint.design-rules.mjs +148 -7
- package/package.json +3 -3
|
@@ -27,7 +27,7 @@ export function BaseMarketplaceTile(props) {
|
|
|
27
27
|
if (target.nextSibling) {
|
|
28
28
|
target.nextSibling.style.display = 'flex';
|
|
29
29
|
}
|
|
30
|
-
} }), _jsx("div", { className: "bg-border ring-border/50
|
|
30
|
+
} }), _jsx("div", { className: "bg-border ring-border/50 heading-headline hidden h-full w-full items-center justify-center rounded-lg ring-1", children: displayBrandName.charAt(0).toUpperCase() })] })) : (_jsx("div", { className: "bg-border ring-border/50 heading-headline flex h-16 w-16 items-center justify-center rounded-lg border-2 ring-1", children: displayBrandName.charAt(0).toUpperCase() })) }), _jsx(CardTitle, { className: "heading-subhead-lg line-clamp-2", children: displayTitle }), _jsx(If, { condition: categories.length > 0, children: _jsxs("div", { className: "mt-2 flex flex-wrap justify-center gap-1", children: [categories.slice(0, 2).map((cat) => (_jsx(CategoryChip, { productColor: productColor, children: cat.name }, cat.id))), _jsx(If, { condition: categories.length > 2, children: _jsxs(CategoryChip, { productColor: productColor, children: ["+", categories.length - 2] }) })] }) })] }), _jsx(CardContent, { className: "flex-1 pb-8 text-center", children: _jsx(If, { condition: displaySummary, children: _jsx(CardDescription, { className: "text-muted-foreground text-body line-clamp-2 leading-relaxed", children: displaySummary }) }) }), _jsx(If, { condition: displayBrandName !== displayTitle, children: _jsx("p", { className: "text-muted-foreground/70 text-meta absolute right-3 bottom-3", children: displayBrandName }) }), _jsx(If, { condition: !!ctaLink, children: _jsx("div", { className: "text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors", "aria-hidden": "true", children: _jsx(Icon, { name: "fa-light fa-arrow-up-right-from-square", className: "h-4 w-4" }) }) })] }));
|
|
31
31
|
// External link: opens in new tab
|
|
32
32
|
if (ctaLink) {
|
|
33
33
|
return (_jsx("a", { href: ctaLink, target: "_blank", rel: "noopener noreferrer", className: "block h-full", "aria-label": `${displayTitle} (opens in new tab)`, children: cardContent }));
|
|
@@ -46,9 +46,7 @@ export function BaseMarketplaceTile(props) {
|
|
|
46
46
|
* its colour). The tint is that colour at 40% alpha.
|
|
47
47
|
*/
|
|
48
48
|
function CategoryChip({ productColor, children, }) {
|
|
49
|
-
return (_jsx("span", { className: cn(
|
|
50
|
-
// oxlint-disable-next-line design/no-raw-text-size -- category chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change
|
|
51
|
-
'text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap', productColor ? 'bg-(--product-color)/40' : 'bg-secondary'), style: productColor
|
|
49
|
+
return (_jsx("span", { className: cn('text-secondary-foreground text-fine inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 font-medium whitespace-nowrap', productColor ? 'bg-(--product-color)/40' : 'bg-secondary'), style: productColor
|
|
52
50
|
? { '--product-color': productColor }
|
|
53
51
|
: undefined, children: children }));
|
|
54
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-marketplace-tile.js","sourceRoot":"","sources":["../../src/components/base-marketplace-tile.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { CSSProperties, ReactNode } from 'react';\n\n// `next/link.js`, not `next/link`. `next` ships no `exports` map, so the\n// extensionless subpath only resolves under a bundler — Node's own ESM loader\n// throws ERR_MODULE_NOT_FOUND, and any partner running vitest or plain Node\n// against the published tarball loses this whole module. The `.js` form is not\n// a bypass of Next's resolution: `create-compiler-aliases.js` keys its aliases\n// on the RESOLVED FILE PATH (`<next>/link.js`, with the comment \"Handle fully\n// specified imports like `next/image.js`\"), so an App Router build still gets\n// `next/dist/client/app-dir/link`, exactly as `next/link` would. Next's dist\n// also ends with `Object.assign(exports.default, exports); module.exports =\n// exports.default`, so Node's CJS-default interop and a bundler's `__esModule`\n// interop land on the same component object. Do not \"tidy\" the extension away.\nimport Link from 'next/link.js';\n\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { MarketplaceTileProps } from '../types/integration';\n\n// Type for product category\nexport interface ProductCategory {\n id: string;\n name: string;\n slug: string;\n description?: string;\n}\n\nexport type BaseMarketplaceTileProps = MarketplaceTileProps & {\n /** The display name of the product */\n name: string;\n /** Full description of the product (used for detail pages) */\n description: string;\n /** URL-friendly identifier for the product */\n slug: string;\n /** Short summary text displayed on the tile (1-2 sentences) */\n summary?: string;\n /** Name of the company/brand that provides this product */\n brandName?: string;\n /** Path to the product logo image */\n logoPath?: string;\n /** Text for the call-to-action button */\n ctaText?: string;\n /** External URL for the CTA (opens in new tab when set) */\n ctaLink?: string;\n\n /** Array of categories this product belongs to */\n categories?: ProductCategory[];\n\n /** Hex color from product schema for tile theming */\n productColor?: string;\n /** Additional CSS classes to apply to the tile */\n className?: string;\n\n /** Internal navigation URL for the integration detail page */\n href?: string;\n};\n\nexport function BaseMarketplaceTile(props: BaseMarketplaceTileProps) {\n const {\n name,\n summary,\n brandName,\n logoPath,\n categories = [],\n productColor,\n className,\n href,\n ctaLink,\n } = props;\n\n const displayTitle = name || 'Unknown Product';\n const displaySummary = summary;\n const displayBrandName = brandName || displayTitle;\n\n const cardContent = (\n <Card\n className={cn(\n 'group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md',\n className,\n )}\n >\n {/* Header: Centered Logo, Title, Categories */}\n <CardHeader className=\"flex flex-col items-center pt-6 pb-3 text-center\">\n {/* Logo */}\n <div className=\"mb-3\">\n {logoPath ? (\n <div className=\"flex h-16 w-16 items-center justify-center\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoPath}\n alt={`${displayBrandName} logo`}\n width={64}\n height={64}\n className=\"max-h-full max-w-full object-contain\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = 'none';\n if (target.nextSibling) {\n (target.nextSibling as HTMLElement).style.display = 'flex';\n }\n }}\n />\n <div className=\"bg-border ring-border/50 text-headline hidden h-full w-full items-center justify-center rounded-lg ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n </div>\n ) : (\n <div className=\"bg-border ring-border/50 text-headline flex h-16 w-16 items-center justify-center rounded-lg border-2 ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n )}\n </div>\n\n {/* Title */}\n <CardTitle className=\"text-subhead-lg line-clamp-2 leading-tight\">\n {displayTitle}\n </CardTitle>\n\n {/* Category Badges - Under Title */}\n <If condition={categories.length > 0}>\n <div className=\"mt-2 flex flex-wrap justify-center gap-1\">\n {categories.slice(0, 2).map((cat) => (\n <CategoryChip key={cat.id} productColor={productColor}>\n {cat.name}\n </CategoryChip>\n ))}\n <If condition={categories.length > 2}>\n <CategoryChip productColor={productColor}>\n +{categories.length - 2}\n </CategoryChip>\n </If>\n </div>\n </If>\n </CardHeader>\n\n {/* Content: Summary */}\n <CardContent className=\"flex-1 pb-8 text-center\">\n <If condition={displaySummary}>\n <CardDescription className=\"text-muted-foreground text-body line-clamp-2 leading-relaxed\">\n {displaySummary}\n </CardDescription>\n </If>\n </CardContent>\n\n {/* Brand Name - Bottom Right */}\n <If condition={displayBrandName !== displayTitle}>\n <p className=\"text-muted-foreground/70 text-meta absolute right-3 bottom-3\">\n {displayBrandName}\n </p>\n </If>\n\n {/* External Link Indicator */}\n <If condition={!!ctaLink}>\n <div\n className=\"text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors\"\n aria-hidden=\"true\"\n >\n <Icon\n name=\"fa-light fa-arrow-up-right-from-square\"\n className=\"h-4 w-4\"\n />\n </div>\n </If>\n </Card>\n );\n\n // External link: opens in new tab\n if (ctaLink) {\n return (\n <a\n href={ctaLink}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block h-full\"\n aria-label={`${displayTitle} (opens in new tab)`}\n >\n {cardContent}\n </a>\n );\n }\n\n // Internal link: uses Next.js Link\n if (href) {\n return (\n <Link href={href} className=\"block h-full\">\n {cardContent}\n </Link>\n );\n }\n\n // Fallback: render without Link (for backwards compatibility)\n return cardContent;\n}\n\n/**\n * A category chip, tinted with the product's own colour when it has one. The\n * colour is a partner's product colour from the product schema, not a theme\n * token — a data colour, so it is a chip rather than a `Badge` (a Badge owns\n * its colour). The tint is that colour at 40% alpha.\n */\nfunction CategoryChip({\n productColor,\n children,\n}: {\n productColor?: string;\n children: ReactNode;\n}) {\n return (\n <span\n className={cn(\n // oxlint-disable-next-line design/no-raw-text-size -- category chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change\n 'text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap',\n productColor ? 'bg-(--product-color)/40' : 'bg-secondary',\n )}\n style={\n productColor\n ? ({ '--product-color': productColor } as CSSProperties)\n : undefined\n }\n >\n {children}\n </span>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"base-marketplace-tile.js","sourceRoot":"","sources":["../../src/components/base-marketplace-tile.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { CSSProperties, ReactNode } from 'react';\n\n// `next/link.js`, not `next/link`. `next` ships no `exports` map, so the\n// extensionless subpath only resolves under a bundler — Node's own ESM loader\n// throws ERR_MODULE_NOT_FOUND, and any partner running vitest or plain Node\n// against the published tarball loses this whole module. The `.js` form is not\n// a bypass of Next's resolution: `create-compiler-aliases.js` keys its aliases\n// on the RESOLVED FILE PATH (`<next>/link.js`, with the comment \"Handle fully\n// specified imports like `next/image.js`\"), so an App Router build still gets\n// `next/dist/client/app-dir/link`, exactly as `next/link` would. Next's dist\n// also ends with `Object.assign(exports.default, exports); module.exports =\n// exports.default`, so Node's CJS-default interop and a bundler's `__esModule`\n// interop land on the same component object. Do not \"tidy\" the extension away.\nimport Link from 'next/link.js';\n\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { MarketplaceTileProps } from '../types/integration';\n\n// Type for product category\nexport interface ProductCategory {\n id: string;\n name: string;\n slug: string;\n description?: string;\n}\n\nexport type BaseMarketplaceTileProps = MarketplaceTileProps & {\n /** The display name of the product */\n name: string;\n /** Full description of the product (used for detail pages) */\n description: string;\n /** URL-friendly identifier for the product */\n slug: string;\n /** Short summary text displayed on the tile (1-2 sentences) */\n summary?: string;\n /** Name of the company/brand that provides this product */\n brandName?: string;\n /** Path to the product logo image */\n logoPath?: string;\n /** Text for the call-to-action button */\n ctaText?: string;\n /** External URL for the CTA (opens in new tab when set) */\n ctaLink?: string;\n\n /** Array of categories this product belongs to */\n categories?: ProductCategory[];\n\n /** Hex color from product schema for tile theming */\n productColor?: string;\n /** Additional CSS classes to apply to the tile */\n className?: string;\n\n /** Internal navigation URL for the integration detail page */\n href?: string;\n};\n\nexport function BaseMarketplaceTile(props: BaseMarketplaceTileProps) {\n const {\n name,\n summary,\n brandName,\n logoPath,\n categories = [],\n productColor,\n className,\n href,\n ctaLink,\n } = props;\n\n const displayTitle = name || 'Unknown Product';\n const displaySummary = summary;\n const displayBrandName = brandName || displayTitle;\n\n const cardContent = (\n <Card\n className={cn(\n 'group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md',\n className,\n )}\n >\n {/* Header: Centered Logo, Title, Categories */}\n <CardHeader className=\"flex flex-col items-center pt-6 pb-3 text-center\">\n {/* Logo */}\n <div className=\"mb-3\">\n {logoPath ? (\n <div className=\"flex h-16 w-16 items-center justify-center\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoPath}\n alt={`${displayBrandName} logo`}\n width={64}\n height={64}\n className=\"max-h-full max-w-full object-contain\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = 'none';\n if (target.nextSibling) {\n (target.nextSibling as HTMLElement).style.display = 'flex';\n }\n }}\n />\n <div className=\"bg-border ring-border/50 heading-headline hidden h-full w-full items-center justify-center rounded-lg ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n </div>\n ) : (\n <div className=\"bg-border ring-border/50 heading-headline flex h-16 w-16 items-center justify-center rounded-lg border-2 ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n )}\n </div>\n\n {/* Title */}\n <CardTitle className=\"heading-subhead-lg line-clamp-2\">\n {displayTitle}\n </CardTitle>\n\n {/* Category Badges - Under Title */}\n <If condition={categories.length > 0}>\n <div className=\"mt-2 flex flex-wrap justify-center gap-1\">\n {categories.slice(0, 2).map((cat) => (\n <CategoryChip key={cat.id} productColor={productColor}>\n {cat.name}\n </CategoryChip>\n ))}\n <If condition={categories.length > 2}>\n <CategoryChip productColor={productColor}>\n +{categories.length - 2}\n </CategoryChip>\n </If>\n </div>\n </If>\n </CardHeader>\n\n {/* Content: Summary */}\n <CardContent className=\"flex-1 pb-8 text-center\">\n <If condition={displaySummary}>\n <CardDescription className=\"text-muted-foreground text-body line-clamp-2 leading-relaxed\">\n {displaySummary}\n </CardDescription>\n </If>\n </CardContent>\n\n {/* Brand Name - Bottom Right */}\n <If condition={displayBrandName !== displayTitle}>\n <p className=\"text-muted-foreground/70 text-meta absolute right-3 bottom-3\">\n {displayBrandName}\n </p>\n </If>\n\n {/* External Link Indicator */}\n <If condition={!!ctaLink}>\n <div\n className=\"text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors\"\n aria-hidden=\"true\"\n >\n <Icon\n name=\"fa-light fa-arrow-up-right-from-square\"\n className=\"h-4 w-4\"\n />\n </div>\n </If>\n </Card>\n );\n\n // External link: opens in new tab\n if (ctaLink) {\n return (\n <a\n href={ctaLink}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block h-full\"\n aria-label={`${displayTitle} (opens in new tab)`}\n >\n {cardContent}\n </a>\n );\n }\n\n // Internal link: uses Next.js Link\n if (href) {\n return (\n <Link href={href} className=\"block h-full\">\n {cardContent}\n </Link>\n );\n }\n\n // Fallback: render without Link (for backwards compatibility)\n return cardContent;\n}\n\n/**\n * A category chip, tinted with the product's own colour when it has one. The\n * colour is a partner's product colour from the product schema, not a theme\n * token — a data colour, so it is a chip rather than a `Badge` (a Badge owns\n * its colour). The tint is that colour at 40% alpha.\n */\nfunction CategoryChip({\n productColor,\n children,\n}: {\n productColor?: string;\n children: ReactNode;\n}) {\n return (\n <span\n className={cn(\n 'text-secondary-foreground text-fine inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 font-medium whitespace-nowrap',\n productColor ? 'bg-(--product-color)/40' : 'bg-secondary',\n )}\n style={\n productColor\n ? ({ '--product-color': productColor } as CSSProperties)\n : undefined\n }\n >\n {children}\n </span>\n );\n}\n"]}
|
|
@@ -27,14 +27,12 @@ export function AiPromptChip({ label, prompt, context, sourceWidget, title, onAf
|
|
|
27
27
|
onAfterAsk === null || onAfterAsk === void 0 ? void 0 : onAfterAsk();
|
|
28
28
|
};
|
|
29
29
|
if (variant === 'header') {
|
|
30
|
-
return (_jsx(Button, { variant: "ghost", size: "sm", className: cn('h-8 w-8 p-0', className), onClick: handleClick, "aria-label": `Ask AI: ${label}`, children: _jsx(Icon, { name: "fa-light fa-sparkles", className: "h-4 w-4" }) }));
|
|
30
|
+
return (_jsx(Button, { variant: "ghost", size: "sm", className: cn('h-8 w-8 p-0', className), onClick: handleClick, "aria-label": `Ask AI: ${label}`, children: _jsx(Icon, { name: "fa-light fa-wand-magic-sparkles", className: "h-4 w-4" }) }));
|
|
31
31
|
}
|
|
32
32
|
if (variant === 'footer') {
|
|
33
|
-
return (_jsxs(
|
|
33
|
+
return (_jsxs(Button, { type: "button", variant: "outline", onClick: handleClick, className: cn('h-auto w-full justify-start rounded-lg px-4 py-3 whitespace-normal', className), "aria-label": label, children: [_jsx(Icon, { name: "fa-light fa-wand-magic-sparkles", className: "text-primary-strong h-4 w-4 shrink-0" }), _jsx("span", { className: "text-left", children: label })] }));
|
|
34
34
|
}
|
|
35
35
|
// inline (Type B)
|
|
36
|
-
return (_jsxs(
|
|
37
|
-
// oxlint-disable-next-line design/no-raw-text-size -- AI suggestion chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change. Repeated in widget-ask-bar.tsx's suggestion chip.
|
|
38
|
-
'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none', className), "aria-label": label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: label })] }));
|
|
36
|
+
return (_jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleClick, className: cn('rounded-full px-3', className), "aria-label": label, children: [_jsx(Icon, { name: "fa-light fa-wand-magic-sparkles", className: "text-primary-strong h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: label })] }));
|
|
39
37
|
}
|
|
40
38
|
//# sourceMappingURL=ai-prompt-chip.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-prompt-chip.js","sourceRoot":"","sources":["../../../src/components/widgets/ai-prompt-chip.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,OAAO,EACP,YAAY,EACZ,KAAK,EACL,UAAU,EACV,OAAO,GAAG,QAAQ,EAClB,SAAS,GACS;IAClB,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,YAAY,CAAC;YACX,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,KAAK;YACrB,QAAQ,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE;YACvB,eAAe,EAAE,MAAM;SACxB,CAAC,CAAC;QACH,UAAU,aAAV,UAAU,uBAAV,UAAU,EAAI,CAAC;IACjB,CAAC,CAAC;IAEF,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CACL,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EACvC,OAAO,EAAE,WAAW,gBACR,WAAW,KAAK,EAAE,YAE9B,KAAC,IAAI,IAAC,IAAI,EAAC,
|
|
1
|
+
{"version":3,"file":"ai-prompt-chip.js","sourceRoot":"","sources":["../../../src/components/widgets/ai-prompt-chip.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,OAAO,EACP,YAAY,EACZ,KAAK,EACL,UAAU,EACV,OAAO,GAAG,QAAQ,EAClB,SAAS,GACS;IAClB,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,YAAY,CAAC;YACX,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,KAAK;YACrB,QAAQ,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE;YACvB,eAAe,EAAE,MAAM;SACxB,CAAC,CAAC;QACH,UAAU,aAAV,UAAU,uBAAV,UAAU,EAAI,CAAC;IACjB,CAAC,CAAC;IAEF,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CACL,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EACvC,OAAO,EAAE,WAAW,gBACR,WAAW,KAAK,EAAE,YAE9B,KAAC,IAAI,IAAC,IAAI,EAAC,iCAAiC,EAAC,SAAS,EAAC,SAAS,GAAG,GAC5D,CACV,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CACL,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,SAAS,EACjB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,EAAE,CACX,oEAAoE,EACpE,SAAS,CACV,gBACW,KAAK,aAEjB,KAAC,IAAI,IACH,IAAI,EAAC,iCAAiC,EACtC,SAAS,EAAC,sCAAsC,GAChD,EACF,eAAM,SAAS,EAAC,WAAW,YAAE,KAAK,GAAQ,IACnC,CACV,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,OAAO,CACL,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,EAAE,CAAC,mBAAmB,EAAE,SAAS,CAAC,gBACjC,KAAK,aAEjB,KAAC,IAAI,IACH,IAAI,EAAC,iCAAiC,EACtC,SAAS,EAAC,0CAA0C,GACpD,EACF,yBAAO,KAAK,GAAQ,IACb,CACV,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { cn } from '@ekanos/ui/utils';\n\nimport { useAskAssistant } from '../../hooks/ask-assistant-context';\n\ntype AiPromptChipProps = {\n label: string;\n prompt: string;\n context?: Record<string, unknown>;\n sourceWidget: string;\n /**\n * Human-readable label for the dock's ArrivalChip (\"Asking about …\").\n * Falls back to the chip's display label when omitted.\n */\n title?: string;\n /**\n * Fires after the assistant has been invoked. Drawers pass their close\n * handler here so the dock takes over the screen on click.\n */\n onAfterAsk?: () => void;\n variant?: 'header' | 'inline' | 'footer';\n className?: string;\n};\n\n/**\n * AI handoff chip that opens the assistant dock pre-seeded with this\n * widget/drawer's snapshot. Routes through `useAskAssistant` so the dock\n * receives the standard `{widgetId, title, snapshot, suggestedPrompt}`\n * shape used by all AI handoffs in the app.\n *\n * Variants:\n * - header (Type A): Compact icon button for widget headers\n * - inline (Type B): Chip with sparkle icon + text, below data sections\n * - footer (Type C): Full-width chip in drawer footers\n */\nexport function AiPromptChip({\n label,\n prompt,\n context,\n sourceWidget,\n title,\n onAfterAsk,\n variant = 'inline',\n className,\n}: AiPromptChipProps) {\n const askAssistant = useAskAssistant();\n\n const handleClick = () => {\n askAssistant({\n widgetId: sourceWidget,\n title: title ?? label,\n snapshot: context ?? {},\n suggestedPrompt: prompt,\n });\n onAfterAsk?.();\n };\n\n if (variant === 'header') {\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className={cn('h-8 w-8 p-0', className)}\n onClick={handleClick}\n aria-label={`Ask AI: ${label}`}\n >\n <Icon name=\"fa-light fa-wand-magic-sparkles\" className=\"h-4 w-4\" />\n </Button>\n );\n }\n\n if (variant === 'footer') {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleClick}\n className={cn(\n 'h-auto w-full justify-start rounded-lg px-4 py-3 whitespace-normal',\n className,\n )}\n aria-label={label}\n >\n <Icon\n name=\"fa-light fa-wand-magic-sparkles\"\n className=\"text-primary-strong h-4 w-4 shrink-0\"\n />\n <span className=\"text-left\">{label}</span>\n </Button>\n );\n }\n\n // inline (Type B)\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n onClick={handleClick}\n className={cn('rounded-full px-3', className)}\n aria-label={label}\n >\n <Icon\n name=\"fa-light fa-wand-magic-sparkles\"\n className=\"text-primary-strong h-3.5 w-3.5 shrink-0\"\n />\n <span>{label}</span>\n </Button>\n );\n}\n"]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { PromptInput, PromptInputBody, PromptInputFooter, PromptInputSubmit, PromptInputTextarea, PromptInputTools, } from '@ekanos/ui/ai-prompt-input';
|
|
5
|
+
import { Button } from '@ekanos/ui/button';
|
|
5
6
|
import { Icon } from '@ekanos/ui/icon';
|
|
6
7
|
import { cn } from '@ekanos/ui/utils';
|
|
7
8
|
import { useAskAssistant } from '../../hooks/ask-assistant-context.js';
|
|
@@ -50,11 +51,9 @@ export function WidgetAskBar({ context }) {
|
|
|
50
51
|
document.addEventListener('pointerdown', onPointerDown);
|
|
51
52
|
return () => document.removeEventListener('pointerdown', onPointerDown);
|
|
52
53
|
}, [dismiss, isActive]);
|
|
53
|
-
return (_jsxs("div", { ref: containerRef, className: "border-border/60 shrink-0 border-t", "data-test": "widget-ask-bar", children: [_jsxs(
|
|
54
|
+
return (_jsxs("div", { ref: containerRef, className: "border-border/60 shrink-0 border-t", "data-test": "widget-ask-bar", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => (isActive ? dismiss() : setIsActive(true)), "aria-expanded": isActive, "aria-label": isActive
|
|
54
55
|
? `Close ask input for ${context.title}`
|
|
55
|
-
: `Ask about ${context.title}`, "data-test": "widget-ask-bar-toggle", className:
|
|
56
|
-
// oxlint-disable-next-line design/no-raw-text-size -- AI suggestion chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change. Repeated in ai-prompt-chip.tsx's inline chip.
|
|
57
|
-
'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none'), "aria-label": s.label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "text-left", children: s.label })] }, s.label))) })), _jsxs(PromptInput, { onSubmit: handleSubmit, children: [_jsx(PromptInputBody, { children: _jsx(PromptInputTextarea, { autoFocus: true, placeholder: `Ask about ${context.title.toLowerCase()}…`, "aria-label": `Ask about ${context.title}`, onKeyDown: (e) => {
|
|
56
|
+
: `Ask about ${context.title}`, "data-test": "widget-ask-bar-toggle", className: "group/tag h-auto w-full justify-start gap-2.5 rounded-none px-4 py-2.5 text-left sm:px-5", children: [_jsx(AskIcon, { className: cn('text-primary h-5 w-5 shrink-0 transition-transform duration-200', isActive && 'scale-110') }), _jsx("span", { className: "text-muted-foreground group-hover/tag:text-foreground text-body flex-1 font-medium transition-colors", children: "Ask about this" }), _jsx(Icon, { name: "fa-light fa-chevron-up", className: cn('text-muted-foreground/50 group-hover/tag:text-muted-foreground h-3.5 w-3.5 shrink-0 transition-transform duration-200', isActive && 'rotate-180') })] }), isActive && (_jsxs("div", { className: "border-border/40 space-y-3 border-t p-3 sm:px-5", "data-test": "widget-ask-bar-input", children: [suggestions.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", "data-test": "widget-ask-bar-suggestions", children: suggestions.map((s) => (_jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: () => handleSuggestion(s.prompt), className: "rounded-full px-3", "aria-label": s.label, children: [_jsx(Icon, { name: "fa-light fa-wand-magic-sparkles", className: "text-primary-strong h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "text-left", children: s.label })] }, s.label))) })), _jsxs(PromptInput, { onSubmit: handleSubmit, children: [_jsx(PromptInputBody, { children: _jsx(PromptInputTextarea, { autoFocus: true, placeholder: `Ask about ${context.title.toLowerCase()}…`, "aria-label": `Ask about ${context.title}`, onKeyDown: (e) => {
|
|
58
57
|
if (e.key === 'Escape') {
|
|
59
58
|
e.preventDefault();
|
|
60
59
|
dismiss();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-ask-bar.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-ask-bar.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjE,OAAO,EACL,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEpE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAMrC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,OAAO,EAAS;;IAC7C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,WAAW,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,OAAyB,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,OAAO,CAAC,eAAe,CAAC;QAC9D,YAAY,iCAAM,OAAO,KAAE,eAAe,EAAE,MAAM,IAAG,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CACjC,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,MAAc,EAAE,EAAE;QACjB,YAAY,iCAAM,OAAO,KAAE,eAAe,EAAE,MAAM,IAAG,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CACjC,CAAC;IAEF,MAAM,WAAW,SAAG,OAAO,CAAC,gBAAgB,mCAAI,EAAE,CAAC;IAEnD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,MAAM,aAAa,GAAG,CAAC,CAAe,EAAE,EAAE;YACxC,MAAM,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC;YAChC,IAAI,CAAC,EAAE;gBAAE,OAAO;YAChB,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC;gBAAE,OAAO;YAC1C,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACxD,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC1E,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAExB,OAAO,CACL,eACE,GAAG,EAAE,YAAY,EACjB,SAAS,EAAC,oCAAoC,eACpC,gBAAgB,aAE1B,
|
|
1
|
+
{"version":3,"file":"widget-ask-bar.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-ask-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {\n PromptInput,\n PromptInputBody,\n PromptInputFooter,\n PromptInputSubmit,\n PromptInputTextarea,\n PromptInputTools,\n} from '@ekanos/ui/ai-prompt-input';\nimport { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { cn } from '@ekanos/ui/utils';\n\nimport { useAskAssistant } from '../../hooks/ask-assistant-context';\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { AskIcon } from './ask-icon';\n\ninterface Props {\n context: WidgetAskContext;\n}\n\n/**\n * Widget footer tag + expanding prompt input.\n *\n * - closed: a quiet, always-visible tag row at the bottom of the widget\n * (\"Aperture icon · Ask about this · chevron up\")\n * - open: clicking the tag reveals an inline `PromptInput` (the canonical\n * AI input component) below it. The tag itself doubles as the\n * close affordance (chevron rotates).\n *\n * Dismisses on click-outside or Escape. Submitting sends the user's typed\n * question (or the widget's suggestedPrompt fallback) to the assistant dock.\n */\nexport function WidgetAskBar({ context }: Props) {\n const [isActive, setIsActive] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const askAssistant = useAskAssistant();\n\n const dismiss = useCallback(() => {\n setIsActive(false);\n }, []);\n\n const handleSubmit = useCallback(\n (message: { text: string }) => {\n const prompt = message.text.trim() || context.suggestedPrompt;\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const handleSuggestion = useCallback(\n (prompt: string) => {\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const suggestions = context.suggestedPrompts ?? [];\n\n useEffect(() => {\n if (!isActive) return;\n const onPointerDown = (e: PointerEvent) => {\n const el = containerRef.current;\n if (!el) return;\n if (el.contains(e.target as Node)) return;\n dismiss();\n };\n document.addEventListener('pointerdown', onPointerDown);\n return () => document.removeEventListener('pointerdown', onPointerDown);\n }, [dismiss, isActive]);\n\n return (\n <div\n ref={containerRef}\n className=\"border-border/60 shrink-0 border-t\"\n data-test=\"widget-ask-bar\"\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => (isActive ? dismiss() : setIsActive(true))}\n aria-expanded={isActive}\n aria-label={\n isActive\n ? `Close ask input for ${context.title}`\n : `Ask about ${context.title}`\n }\n data-test=\"widget-ask-bar-toggle\"\n className=\"group/tag h-auto w-full justify-start gap-2.5 rounded-none px-4 py-2.5 text-left sm:px-5\"\n >\n <AskIcon\n className={cn(\n 'text-primary h-5 w-5 shrink-0 transition-transform duration-200',\n isActive && 'scale-110',\n )}\n />\n <span className=\"text-muted-foreground group-hover/tag:text-foreground text-body flex-1 font-medium transition-colors\">\n Ask about this\n </span>\n <Icon\n name=\"fa-light fa-chevron-up\"\n className={cn(\n 'text-muted-foreground/50 group-hover/tag:text-muted-foreground h-3.5 w-3.5 shrink-0 transition-transform duration-200',\n isActive && 'rotate-180',\n )}\n />\n </Button>\n\n {isActive && (\n <div\n className=\"border-border/40 space-y-3 border-t p-3 sm:px-5\"\n data-test=\"widget-ask-bar-input\"\n >\n {suggestions.length > 0 && (\n <div\n className=\"flex flex-wrap gap-2\"\n data-test=\"widget-ask-bar-suggestions\"\n >\n {suggestions.map((s) => (\n <Button\n key={s.label}\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n onClick={() => handleSuggestion(s.prompt)}\n className=\"rounded-full px-3\"\n aria-label={s.label}\n >\n <Icon\n name=\"fa-light fa-wand-magic-sparkles\"\n className=\"text-primary-strong h-3.5 w-3.5 shrink-0\"\n />\n <span className=\"text-left\">{s.label}</span>\n </Button>\n ))}\n </div>\n )}\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputBody>\n <PromptInputTextarea\n autoFocus\n placeholder={`Ask about ${context.title.toLowerCase()}…`}\n aria-label={`Ask about ${context.title}`}\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n dismiss();\n }\n }}\n />\n </PromptInputBody>\n <PromptInputFooter>\n <PromptInputTools />\n <PromptInputSubmit data-test=\"widget-ask-bar-send\" />\n </PromptInputFooter>\n </PromptInput>\n </div>\n )}\n </div>\n );\n}\n"]}
|
|
@@ -17,7 +17,7 @@ function StateAction({ action, variant = 'default', }) {
|
|
|
17
17
|
* and at most one action, left-aligned in the body's padding.
|
|
18
18
|
*/
|
|
19
19
|
function StateNote({ icon, title, description, action, actionVariant, role, testId, }) {
|
|
20
|
-
return (_jsxs("div", { role: role, className: "flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5", "data-test": testId, children: [_jsx(Icon, { name: icon, className: "text-muted-foreground h-6 w-6", "aria-hidden": true }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-
|
|
20
|
+
return (_jsxs("div", { role: role, className: "flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5", "data-test": testId, children: [_jsx(Icon, { name: icon, className: "text-muted-foreground h-6 w-6", "aria-hidden": true }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-body font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground text-meta max-w-prose", children: description })) : null] }), action ? _jsx(StateAction, { action: action, variant: actionVariant }) : null] }));
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* The empty state for a widget with nothing to show. Positive by default:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-state-components.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-state-components.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react';\n\nimport Link from 'next/link.js';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { Skeleton } from '@ekanos/ui/skeleton';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\n/**\n * The one action a state view offers: \"Connect QuickBooks\", \"Ask an admin\",\n * \"Create a deal\". A link with `href`, otherwise a button running `onClick`.\n */\nexport interface WidgetStateAction {\n label: ReactNode;\n href?: string;\n /** Open `href` in a new tab. */\n external?: boolean;\n onClick?: () => void;\n}\n\n/** The body shape a loading skeleton stands in for. */\nexport type WidgetSkeletonVariant = 'rows' | 'stat' | 'chart' | 'schedule';\n\n/** Why an active integration still can't show this widget's data. */\nexport type WidgetInactiveReason =\n | 'not-connected'\n | 'no-access'\n | 'setup-needed';\n\nexport interface WidgetEmptyStateProps {\n /**\n * `positive` (the default) says nothing needs the owner: \"You're all caught\n * up\". `neutral` is for an empty list that isn't good news, like a search\n * with no results.\n */\n tone?: 'positive' | 'neutral';\n title?: ReactNode;\n description?: ReactNode;\n action?: WidgetStateAction;\n}\n\nexport interface WidgetInactiveStateProps {\n reason?: WidgetInactiveReason;\n /** The integration's name, for the default copy (\"Connect QuickBooks\"). */\n integrationName?: string;\n title?: ReactNode;\n /** One sentence on what connecting or access gives the owner. */\n description?: ReactNode;\n action?: WidgetStateAction;\n}\n\nfunction StateAction({\n action,\n variant = 'default',\n}: {\n action: WidgetStateAction;\n variant?: 'default' | 'outline';\n}) {\n if (action.href) {\n const link = action.external ? (\n <a href={action.href} target=\"_blank\" rel=\"noopener noreferrer\" />\n ) : (\n <Link href={action.href} />\n );\n\n return (\n <Button size=\"sm\" variant={variant} render={link} nativeButton={false}>\n {action.label}\n </Button>\n );\n }\n\n return (\n <Button size=\"sm\" variant={variant} type=\"button\" onClick={action.onClick}>\n {action.label}\n </Button>\n );\n}\n\n/**\n * The shared shape of every non-data state: an icon, a title, one sentence\n * and at most one action, left-aligned in the body's padding.\n */\nfunction StateNote({\n icon,\n title,\n description,\n action,\n actionVariant,\n role,\n testId,\n}: {\n icon: string;\n title: ReactNode;\n description?: ReactNode;\n action?: WidgetStateAction;\n actionVariant?: 'default' | 'outline';\n role?: 'alert' | 'status';\n testId: string;\n}) {\n return (\n <div\n role={role}\n className=\"flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5\"\n data-test={testId}\n >\n <Icon name={icon} className=\"text-muted-foreground h-6 w-6\" aria-hidden />\n <div className=\"space-y-1\">\n {/* oxlint-disable-next-line design/no-raw-text-size -- state-note title at 16px/500 (text-base font-medium); no tier pairs 500 at 16px (text-title bakes 600, text-body bakes 400) */}\n <p className=\"text-foreground text-base font-medium\">{title}</p>\n {description ? (\n <p className=\"text-muted-foreground text-meta max-w-prose\">\n {description}\n </p>\n ) : null}\n </div>\n {action ? <StateAction action={action} variant={actionVariant} /> : null}\n </div>\n );\n}\n\n/**\n * The empty state for a widget with nothing to show. Positive by default:\n * an empty review queue is good news, so it reassures instead of apologizing.\n */\nexport function WidgetEmptyState({\n tone = 'positive',\n title,\n description,\n action,\n}: WidgetEmptyStateProps) {\n const positive = tone === 'positive';\n\n return (\n <StateNote\n icon={positive ? 'fa-light fa-circle-check' : 'fa-light fa-inbox'}\n title={\n title ??\n (positive ? (\n <Trans\n i18nKey=\"dashboard:widgetEmptyPositive\"\n defaults=\"You're all caught up\"\n />\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetEmptyNeutral\"\n defaults=\"Nothing here yet\"\n />\n ))\n }\n description={description}\n action={action}\n actionVariant=\"outline\"\n role=\"status\"\n testId={`widget-empty-${tone}`}\n />\n );\n}\n\nconst INACTIVE_ICON: Record<WidgetInactiveReason, string> = {\n 'not-connected': 'fa-light fa-plug',\n 'no-access': 'fa-light fa-user-group',\n 'setup-needed': 'fa-light fa-sliders',\n};\n\n/**\n * Why the widget can't show data. `not-connected` offers one Connect button;\n * `no-access` is a calm note that points to an admin, never a lock screen;\n * `setup-needed` points to the integration's settings. Without a `reason` it\n * keeps the original one-line \"Widget inactive\".\n */\nexport function WidgetInactiveState({\n reason,\n integrationName,\n title,\n description,\n action,\n}: WidgetInactiveStateProps = {}) {\n if (!reason) {\n return (\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <p className=\"text-muted-foreground text-meta\">\n {title ?? (\n <Trans\n i18nKey=\"dashboard:widgetInactive\"\n defaults=\"Widget inactive\"\n />\n )}\n </p>\n </div>\n );\n }\n\n const name = integrationName ?? 'this integration';\n\n const defaults: Record<\n WidgetInactiveReason,\n { title: ReactNode; description: ReactNode }\n > = {\n 'not-connected': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetNotConnectedTitle\"\n defaults=\"Connect {{name}}\"\n values={{ name }}\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetNotConnectedBody\"\n defaults=\"Connect {{name}} and this card fills in with your own data.\"\n values={{ name }}\n />\n ),\n },\n 'no-access': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetNoAccessTitle\"\n defaults=\"Shared with admins for now\"\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetNoAccessBody\"\n defaults=\"Your role doesn't include this data yet. Ask an admin if you need it.\"\n />\n ),\n },\n 'setup-needed': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetSetupNeededTitle\"\n defaults=\"Finish setting up {{name}}\"\n values={{ name }}\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetSetupNeededBody\"\n defaults=\"One more step and this card starts showing data.\"\n />\n ),\n },\n };\n\n return (\n <StateNote\n icon={INACTIVE_ICON[reason]}\n title={title ?? defaults[reason].title}\n description={description ?? defaults[reason].description}\n action={action}\n actionVariant={reason === 'no-access' ? 'outline' : 'default'}\n testId={`widget-inactive-${reason}`}\n />\n );\n}\n\n/**\n * Loading, drawn as the body it stands in for so the card doesn't jump when\n * data lands. Rows suit lists and feeds, `stat` a headline figure, `chart` a\n * chart, `schedule` a day of events.\n */\nexport function WidgetLoadingSkeleton({\n variant = 'rows',\n}: {\n variant?: WidgetSkeletonVariant;\n}) {\n return (\n <div\n role=\"status\"\n aria-busy=\"true\"\n className=\"flex flex-1 flex-col\"\n data-test={`widget-skeleton-${variant}`}\n >\n <span className=\"sr-only\">\n <Trans i18nKey=\"dashboard:widgetLoading\" defaults=\"Loading...\" />\n </span>\n {variant === 'stat' ? (\n <div className=\"space-y-2 px-5 pt-2 pb-5\">\n <Skeleton className=\"h-3 w-24\" />\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-3 w-32\" />\n </div>\n ) : variant === 'chart' ? (\n <div className=\"space-y-3 px-5 pt-2 pb-5\">\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-60 w-full\" />\n </div>\n ) : (\n <div className=\"divide-border/50 divide-y\">\n {[0, 1, 2].map((row) => (\n <div\n key={row}\n className={cn(\n 'px-5 py-3',\n variant === 'schedule' ? 'flex items-start gap-3' : 'space-y-2',\n )}\n >\n {variant === 'schedule' ? (\n <>\n <Skeleton className=\"h-4 w-14 shrink-0\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n </>\n ) : (\n <>\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function WidgetErrorState({\n title,\n description,\n onRetry,\n retrying = false,\n}: {\n title?: ReactNode;\n description?: ReactNode;\n onRetry?: () => void;\n retrying?: boolean;\n}) {\n return (\n <div\n role=\"alert\"\n className=\"flex flex-col items-center justify-center gap-3 px-5 py-8 text-center\"\n >\n <div className=\"bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-5 w-5\"\n aria-hidden\n />\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-foreground text-label\">\n {title ?? (\n <Trans\n i18nKey=\"dashboard:widgetErrorTitle\"\n defaults=\"Couldn't load this widget\"\n />\n )}\n </p>\n <p className=\"text-muted-foreground text-meta\">\n {description ? (\n description\n ) : onRetry ? (\n <Trans\n i18nKey=\"dashboard:widgetErrorBodyRetry\"\n defaults=\"Try again, or check back in a few minutes.\"\n />\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetErrorBody\"\n defaults=\"Check back in a few minutes.\"\n />\n )}\n </p>\n </div>\n {onRetry ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={onRetry}\n disabled={retrying}\n data-test=\"widget-error-retry\"\n >\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className={cn('h-4 w-4', retrying && 'animate-spin')}\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRetry\" defaults=\"Try again\" />\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport function WidgetDataLoading() {\n return (\n <div className=\"flex flex-1 items-center justify-center py-12\">\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"text-muted-foreground h-8 w-8 animate-spin\"\n />\n </div>\n );\n}\n\nexport function WidgetDisabledState() {\n return (\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <Icon\n name=\"fa-light fa-ban\"\n className=\"text-muted-foreground mb-4 h-8 w-8\"\n />\n <p className=\"text-muted-foreground text-meta\">\n <Trans i18nKey=\"dashboard:widgetDisabled\" defaults=\"Widget Disabled\" />\n </p>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"widget-state-components.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-state-components.tsx"],"names":[],"mappings":";AAEA,OAAO,IAAI,MAAM,cAAc,CAAC;AAEhC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AA6CtC,SAAS,WAAW,CAAC,EACnB,MAAM,EACN,OAAO,GAAG,SAAS,GAIpB;IACC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC7B,YAAG,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,qBAAqB,GAAG,CACnE,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAE,MAAM,CAAC,IAAI,GAAI,CAC5B,CAAC;QAEF,OAAO,CACL,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,YAClE,MAAM,CAAC,KAAK,GACN,CACV,CAAC;IACJ,CAAC;IAED,OAAO,CACL,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,MAAM,CAAC,OAAO,YACtE,MAAM,CAAC,KAAK,GACN,CACV,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,EACjB,IAAI,EACJ,KAAK,EACL,WAAW,EACX,MAAM,EACN,aAAa,EACb,IAAI,EACJ,MAAM,GASP;IACC,OAAO,CACL,eACE,IAAI,EAAE,IAAI,EACV,SAAS,EAAC,uDAAuD,eACtD,MAAM,aAEjB,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAC,+BAA+B,wBAAe,EAC1E,eAAK,SAAS,EAAC,WAAW,aACxB,YAAG,SAAS,EAAC,uCAAuC,YAAE,KAAK,GAAK,EAC/D,WAAW,CAAC,CAAC,CAAC,CACb,YAAG,SAAS,EAAC,6CAA6C,YACvD,WAAW,GACV,CACL,CAAC,CAAC,CAAC,IAAI,IACJ,EACL,MAAM,CAAC,CAAC,CAAC,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAI,CAAC,CAAC,CAAC,IAAI,IACpE,CACP,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,IAAI,GAAG,UAAU,EACjB,KAAK,EACL,WAAW,EACX,MAAM,GACgB;IACtB,MAAM,QAAQ,GAAG,IAAI,KAAK,UAAU,CAAC;IAErC,OAAO,CACL,KAAC,SAAS,IACR,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,mBAAmB,EACjE,KAAK,EACH,KAAK,aAAL,KAAK,cAAL,KAAK,GACL,CAAC,QAAQ,CAAC,CAAC,CAAC,CACV,KAAC,KAAK,IACJ,OAAO,EAAC,+BAA+B,EACvC,QAAQ,EAAC,sBAAsB,GAC/B,CACH,CAAC,CAAC,CAAC,CACF,KAAC,KAAK,IACJ,OAAO,EAAC,8BAA8B,EACtC,QAAQ,EAAC,kBAAkB,GAC3B,CACH,CAAC,EAEJ,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,aAAa,EAAC,SAAS,EACvB,IAAI,EAAC,QAAQ,EACb,MAAM,EAAE,gBAAgB,IAAI,EAAE,GAC9B,CACH,CAAC;AACJ,CAAC;AAED,MAAM,aAAa,GAAyC;IAC1D,eAAe,EAAE,kBAAkB;IACnC,WAAW,EAAE,wBAAwB;IACrC,cAAc,EAAE,qBAAqB;CACtC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAClC,MAAM,EACN,eAAe,EACf,KAAK,EACL,WAAW,EACX,MAAM,GACP,GAA6B,EAAE;IAC9B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CACL,cAAK,SAAS,EAAC,4DAA4D,YACzE,YAAG,SAAS,EAAC,iCAAiC,YAC3C,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,CACR,KAAC,KAAK,IACJ,OAAO,EAAC,0BAA0B,EAClC,QAAQ,EAAC,iBAAiB,GAC1B,CACH,GACC,GACA,CACP,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,eAAe,aAAf,eAAe,cAAf,eAAe,GAAI,kBAAkB,CAAC;IAEnD,MAAM,QAAQ,GAGV;QACF,eAAe,EAAE;YACf,KAAK,EAAE,CACL,KAAC,KAAK,IACJ,OAAO,EAAC,mCAAmC,EAC3C,QAAQ,EAAC,kBAAkB,EAC3B,MAAM,EAAE,EAAE,IAAI,EAAE,GAChB,CACH;YACD,WAAW,EAAE,CACX,KAAC,KAAK,IACJ,OAAO,EAAC,kCAAkC,EAC1C,QAAQ,EAAC,6DAA6D,EACtE,MAAM,EAAE,EAAE,IAAI,EAAE,GAChB,CACH;SACF;QACD,WAAW,EAAE;YACX,KAAK,EAAE,CACL,KAAC,KAAK,IACJ,OAAO,EAAC,+BAA+B,EACvC,QAAQ,EAAC,4BAA4B,GACrC,CACH;YACD,WAAW,EAAE,CACX,KAAC,KAAK,IACJ,OAAO,EAAC,8BAA8B,EACtC,QAAQ,EAAC,uEAAuE,GAChF,CACH;SACF;QACD,cAAc,EAAE;YACd,KAAK,EAAE,CACL,KAAC,KAAK,IACJ,OAAO,EAAC,kCAAkC,EAC1C,QAAQ,EAAC,4BAA4B,EACrC,MAAM,EAAE,EAAE,IAAI,EAAE,GAChB,CACH;YACD,WAAW,EAAE,CACX,KAAC,KAAK,IACJ,OAAO,EAAC,iCAAiC,EACzC,QAAQ,EAAC,kDAAkD,GAC3D,CACH;SACF;KACF,CAAC;IAEF,OAAO,CACL,KAAC,SAAS,IACR,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EACtC,WAAW,EAAE,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,WAAW,EACxD,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAC7D,MAAM,EAAE,mBAAmB,MAAM,EAAE,GACnC,CACH,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,OAAO,GAAG,MAAM,GAGjB;IACC,OAAO,CACL,eACE,IAAI,EAAC,QAAQ,eACH,MAAM,EAChB,SAAS,EAAC,sBAAsB,eACrB,mBAAmB,OAAO,EAAE,aAEvC,eAAM,SAAS,EAAC,SAAS,YACvB,KAAC,KAAK,IAAC,OAAO,EAAC,yBAAyB,EAAC,QAAQ,EAAC,YAAY,GAAG,GAC5D,EACN,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,CACpB,eAAK,SAAS,EAAC,0BAA0B,aACvC,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,EACjC,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,EACjC,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,IAC7B,CACP,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,CACxB,eAAK,SAAS,EAAC,0BAA0B,aACvC,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,EACjC,KAAC,QAAQ,IAAC,SAAS,EAAC,aAAa,GAAG,IAChC,CACP,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,2BAA2B,YACvC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CACtB,cAEE,SAAS,EAAE,EAAE,CACX,WAAW,EACX,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,WAAW,CAChE,YAEA,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,CACxB,8BACE,KAAC,QAAQ,IAAC,SAAS,EAAC,mBAAmB,GAAG,EAC1C,eAAK,SAAS,EAAC,kBAAkB,aAC/B,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,EAClC,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,IAC9B,IACL,CACJ,CAAC,CAAC,CAAC,CACF,8BACE,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,EAClC,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,IACjC,CACJ,IAnBI,GAAG,CAoBJ,CACP,CAAC,GACE,CACP,IACG,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAC/B,KAAK,EACL,WAAW,EACX,OAAO,EACP,QAAQ,GAAG,KAAK,GAMjB;IACC,OAAO,CACL,eACE,IAAI,EAAC,OAAO,EACZ,SAAS,EAAC,uEAAuE,aAEjF,cAAK,SAAS,EAAC,2EAA2E,YACxF,KAAC,IAAI,IACH,IAAI,EAAC,gCAAgC,EACrC,SAAS,EAAC,0BAA0B,wBAEpC,GACE,EACN,eAAK,SAAS,EAAC,WAAW,aACxB,YAAG,SAAS,EAAC,4BAA4B,YACtC,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,CACR,KAAC,KAAK,IACJ,OAAO,EAAC,4BAA4B,EACpC,QAAQ,EAAC,2BAA2B,GACpC,CACH,GACC,EACJ,YAAG,SAAS,EAAC,iCAAiC,YAC3C,WAAW,CAAC,CAAC,CAAC,CACb,WAAW,CACZ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CACZ,KAAC,KAAK,IACJ,OAAO,EAAC,gCAAgC,EACxC,QAAQ,EAAC,4CAA4C,GACrD,CACH,CAAC,CAAC,CAAC,CACF,KAAC,KAAK,IACJ,OAAO,EAAC,2BAA2B,EACnC,QAAQ,EAAC,8BAA8B,GACvC,CACH,GACC,IACA,EACL,OAAO,CAAC,CAAC,CAAC,CACT,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,eACR,oBAAoB,aAE9B,KAAC,IAAI,IACH,IAAI,EAAC,2BAA2B,EAChC,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,QAAQ,IAAI,cAAc,CAAC,wBAEpD,EACF,KAAC,KAAK,IAAC,OAAO,EAAC,uBAAuB,EAAC,QAAQ,EAAC,WAAW,GAAG,IACvD,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CACL,cAAK,SAAS,EAAC,+CAA+C,YAC5D,KAAC,IAAI,IACH,IAAI,EAAC,2BAA2B,EAChC,SAAS,EAAC,4CAA4C,GACtD,GACE,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,CACL,eAAK,SAAS,EAAC,4DAA4D,aACzE,KAAC,IAAI,IACH,IAAI,EAAC,iBAAiB,EACtB,SAAS,EAAC,oCAAoC,GAC9C,EACF,YAAG,SAAS,EAAC,iCAAiC,YAC5C,KAAC,KAAK,IAAC,OAAO,EAAC,0BAA0B,EAAC,QAAQ,EAAC,iBAAiB,GAAG,GACrE,IACA,CACP,CAAC;AACJ,CAAC","sourcesContent":["import type { ReactNode } from 'react';\n\nimport Link from 'next/link.js';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { Skeleton } from '@ekanos/ui/skeleton';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\n/**\n * The one action a state view offers: \"Connect QuickBooks\", \"Ask an admin\",\n * \"Create a deal\". A link with `href`, otherwise a button running `onClick`.\n */\nexport interface WidgetStateAction {\n label: ReactNode;\n href?: string;\n /** Open `href` in a new tab. */\n external?: boolean;\n onClick?: () => void;\n}\n\n/** The body shape a loading skeleton stands in for. */\nexport type WidgetSkeletonVariant = 'rows' | 'stat' | 'chart' | 'schedule';\n\n/** Why an active integration still can't show this widget's data. */\nexport type WidgetInactiveReason =\n | 'not-connected'\n | 'no-access'\n | 'setup-needed';\n\nexport interface WidgetEmptyStateProps {\n /**\n * `positive` (the default) says nothing needs the owner: \"You're all caught\n * up\". `neutral` is for an empty list that isn't good news, like a search\n * with no results.\n */\n tone?: 'positive' | 'neutral';\n title?: ReactNode;\n description?: ReactNode;\n action?: WidgetStateAction;\n}\n\nexport interface WidgetInactiveStateProps {\n reason?: WidgetInactiveReason;\n /** The integration's name, for the default copy (\"Connect QuickBooks\"). */\n integrationName?: string;\n title?: ReactNode;\n /** One sentence on what connecting or access gives the owner. */\n description?: ReactNode;\n action?: WidgetStateAction;\n}\n\nfunction StateAction({\n action,\n variant = 'default',\n}: {\n action: WidgetStateAction;\n variant?: 'default' | 'outline';\n}) {\n if (action.href) {\n const link = action.external ? (\n <a href={action.href} target=\"_blank\" rel=\"noopener noreferrer\" />\n ) : (\n <Link href={action.href} />\n );\n\n return (\n <Button size=\"sm\" variant={variant} render={link} nativeButton={false}>\n {action.label}\n </Button>\n );\n }\n\n return (\n <Button size=\"sm\" variant={variant} type=\"button\" onClick={action.onClick}>\n {action.label}\n </Button>\n );\n}\n\n/**\n * The shared shape of every non-data state: an icon, a title, one sentence\n * and at most one action, left-aligned in the body's padding.\n */\nfunction StateNote({\n icon,\n title,\n description,\n action,\n actionVariant,\n role,\n testId,\n}: {\n icon: string;\n title: ReactNode;\n description?: ReactNode;\n action?: WidgetStateAction;\n actionVariant?: 'default' | 'outline';\n role?: 'alert' | 'status';\n testId: string;\n}) {\n return (\n <div\n role={role}\n className=\"flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5\"\n data-test={testId}\n >\n <Icon name={icon} className=\"text-muted-foreground h-6 w-6\" aria-hidden />\n <div className=\"space-y-1\">\n <p className=\"text-foreground text-body font-medium\">{title}</p>\n {description ? (\n <p className=\"text-muted-foreground text-meta max-w-prose\">\n {description}\n </p>\n ) : null}\n </div>\n {action ? <StateAction action={action} variant={actionVariant} /> : null}\n </div>\n );\n}\n\n/**\n * The empty state for a widget with nothing to show. Positive by default:\n * an empty review queue is good news, so it reassures instead of apologizing.\n */\nexport function WidgetEmptyState({\n tone = 'positive',\n title,\n description,\n action,\n}: WidgetEmptyStateProps) {\n const positive = tone === 'positive';\n\n return (\n <StateNote\n icon={positive ? 'fa-light fa-circle-check' : 'fa-light fa-inbox'}\n title={\n title ??\n (positive ? (\n <Trans\n i18nKey=\"dashboard:widgetEmptyPositive\"\n defaults=\"You're all caught up\"\n />\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetEmptyNeutral\"\n defaults=\"Nothing here yet\"\n />\n ))\n }\n description={description}\n action={action}\n actionVariant=\"outline\"\n role=\"status\"\n testId={`widget-empty-${tone}`}\n />\n );\n}\n\nconst INACTIVE_ICON: Record<WidgetInactiveReason, string> = {\n 'not-connected': 'fa-light fa-plug',\n 'no-access': 'fa-light fa-user-group',\n 'setup-needed': 'fa-light fa-sliders',\n};\n\n/**\n * Why the widget can't show data. `not-connected` offers one Connect button;\n * `no-access` is a calm note that points to an admin, never a lock screen;\n * `setup-needed` points to the integration's settings. Without a `reason` it\n * keeps the original one-line \"Widget inactive\".\n */\nexport function WidgetInactiveState({\n reason,\n integrationName,\n title,\n description,\n action,\n}: WidgetInactiveStateProps = {}) {\n if (!reason) {\n return (\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <p className=\"text-muted-foreground text-meta\">\n {title ?? (\n <Trans\n i18nKey=\"dashboard:widgetInactive\"\n defaults=\"Widget inactive\"\n />\n )}\n </p>\n </div>\n );\n }\n\n const name = integrationName ?? 'this integration';\n\n const defaults: Record<\n WidgetInactiveReason,\n { title: ReactNode; description: ReactNode }\n > = {\n 'not-connected': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetNotConnectedTitle\"\n defaults=\"Connect {{name}}\"\n values={{ name }}\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetNotConnectedBody\"\n defaults=\"Connect {{name}} and this card fills in with your own data.\"\n values={{ name }}\n />\n ),\n },\n 'no-access': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetNoAccessTitle\"\n defaults=\"Shared with admins for now\"\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetNoAccessBody\"\n defaults=\"Your role doesn't include this data yet. Ask an admin if you need it.\"\n />\n ),\n },\n 'setup-needed': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetSetupNeededTitle\"\n defaults=\"Finish setting up {{name}}\"\n values={{ name }}\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetSetupNeededBody\"\n defaults=\"One more step and this card starts showing data.\"\n />\n ),\n },\n };\n\n return (\n <StateNote\n icon={INACTIVE_ICON[reason]}\n title={title ?? defaults[reason].title}\n description={description ?? defaults[reason].description}\n action={action}\n actionVariant={reason === 'no-access' ? 'outline' : 'default'}\n testId={`widget-inactive-${reason}`}\n />\n );\n}\n\n/**\n * Loading, drawn as the body it stands in for so the card doesn't jump when\n * data lands. Rows suit lists and feeds, `stat` a headline figure, `chart` a\n * chart, `schedule` a day of events.\n */\nexport function WidgetLoadingSkeleton({\n variant = 'rows',\n}: {\n variant?: WidgetSkeletonVariant;\n}) {\n return (\n <div\n role=\"status\"\n aria-busy=\"true\"\n className=\"flex flex-1 flex-col\"\n data-test={`widget-skeleton-${variant}`}\n >\n <span className=\"sr-only\">\n <Trans i18nKey=\"dashboard:widgetLoading\" defaults=\"Loading...\" />\n </span>\n {variant === 'stat' ? (\n <div className=\"space-y-2 px-5 pt-2 pb-5\">\n <Skeleton className=\"h-3 w-24\" />\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-3 w-32\" />\n </div>\n ) : variant === 'chart' ? (\n <div className=\"space-y-3 px-5 pt-2 pb-5\">\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-60 w-full\" />\n </div>\n ) : (\n <div className=\"divide-border/50 divide-y\">\n {[0, 1, 2].map((row) => (\n <div\n key={row}\n className={cn(\n 'px-5 py-3',\n variant === 'schedule' ? 'flex items-start gap-3' : 'space-y-2',\n )}\n >\n {variant === 'schedule' ? (\n <>\n <Skeleton className=\"h-4 w-14 shrink-0\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n </>\n ) : (\n <>\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function WidgetErrorState({\n title,\n description,\n onRetry,\n retrying = false,\n}: {\n title?: ReactNode;\n description?: ReactNode;\n onRetry?: () => void;\n retrying?: boolean;\n}) {\n return (\n <div\n role=\"alert\"\n className=\"flex flex-col items-center justify-center gap-3 px-5 py-8 text-center\"\n >\n <div className=\"bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-5 w-5\"\n aria-hidden\n />\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-foreground text-label\">\n {title ?? (\n <Trans\n i18nKey=\"dashboard:widgetErrorTitle\"\n defaults=\"Couldn't load this widget\"\n />\n )}\n </p>\n <p className=\"text-muted-foreground text-meta\">\n {description ? (\n description\n ) : onRetry ? (\n <Trans\n i18nKey=\"dashboard:widgetErrorBodyRetry\"\n defaults=\"Try again, or check back in a few minutes.\"\n />\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetErrorBody\"\n defaults=\"Check back in a few minutes.\"\n />\n )}\n </p>\n </div>\n {onRetry ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={onRetry}\n disabled={retrying}\n data-test=\"widget-error-retry\"\n >\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className={cn('h-4 w-4', retrying && 'animate-spin')}\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRetry\" defaults=\"Try again\" />\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport function WidgetDataLoading() {\n return (\n <div className=\"flex flex-1 items-center justify-center py-12\">\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"text-muted-foreground h-8 w-8 animate-spin\"\n />\n </div>\n );\n}\n\nexport function WidgetDisabledState() {\n return (\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <Icon\n name=\"fa-light fa-ban\"\n className=\"text-muted-foreground mb-4 h-8 w-8\"\n />\n <p className=\"text-muted-foreground text-meta\">\n <Trans i18nKey=\"dashboard:widgetDisabled\" defaults=\"Widget Disabled\" />\n </p>\n </div>\n );\n}\n"]}
|
package/eslint.design-preset.mjs
CHANGED
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
* inputs, selects, textareas and labels, each of which carries accessibility
|
|
23
23
|
* wiring a bare element does not.
|
|
24
24
|
*
|
|
25
|
-
* The other
|
|
25
|
+
* The other eleven are the host's own `design/*` rules — palette-adjacent
|
|
26
26
|
* white/black utilities, Badge tone variants, overlay widths, rounded row
|
|
27
27
|
* hovers, faded muted text, viewport breakpoints in widgets, tinted chips,
|
|
28
|
-
* toolbar tabs, raw type steps — imported from `./eslint.design-rules.mjs`, the single
|
|
28
|
+
* toolbar tabs, raw type steps, raw buttons, hand-drawn cards — imported from `./eslint.design-rules.mjs`, the single
|
|
29
29
|
* implementation the host repo's oxlint plugin also loads (through the
|
|
30
30
|
* two-line re-export the host's lint config keeps). There is
|
|
31
31
|
* exactly one copy of each rule, so a partner is held to what the host holds
|
package/eslint.design-rules.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The design-system rules the shadcn linter does not cover —
|
|
2
|
+
* The design-system rules the shadcn linter does not cover — eleven of them,
|
|
3
3
|
* as one lint plugin object. The rules and their reasoning live in the design system's
|
|
4
4
|
* rules; each message names the rule.
|
|
5
5
|
*
|
|
6
6
|
* ── Why this file sits in a published package ────────────────────────────
|
|
7
|
-
* Two consumers need the same
|
|
7
|
+
* Two consumers need the same rules and neither may fork them:
|
|
8
8
|
*
|
|
9
9
|
* - the host repo's own lint, as an oxlint JS plugin. `.oxlintrc.json`
|
|
10
10
|
* still loads this plugin through a
|
|
@@ -205,7 +205,8 @@ function isRoundedRowHover(value) {
|
|
|
205
205
|
const CHIP_TONES = ['success', 'primary', 'warning', 'destructive', 'info'];
|
|
206
206
|
|
|
207
207
|
// The two raw steps PLUS the three tiers that replaced them (the type scale:
|
|
208
|
-
// `text-fine`
|
|
208
|
+
// `text-fine` and `text-eyebrow` are 12px, `text-meta` and `text-body-sm`
|
|
209
|
+
// 14px/400, `text-label` 14px/500). Without
|
|
209
210
|
// the tiers this rule went silently blind the moment the type-scale sweep
|
|
210
211
|
// landed — five weak tinted chips stopped matching while looking identical.
|
|
211
212
|
// Any rule keyed on a raw step has the same failure mode; check for it.
|
|
@@ -214,7 +215,7 @@ const CHIP_TONES = ['success', 'primary', 'warning', 'destructive', 'info'];
|
|
|
214
215
|
// this is the SMALL-text set, and a chip at 20px is not a weak chip, it is a
|
|
215
216
|
// mis-sized one. Re-check this list whenever a tier is added at 12 or 14px.
|
|
216
217
|
const CHIP_SMALL_TEXT =
|
|
217
|
-
/(?:^|\s)(?:[\w-]+:)*text-(?:xs|sm|fine|meta|label)(?=$|\s)/;
|
|
218
|
+
/(?:^|\s)(?:[\w-]+:)*text-(?:xs|sm|fine|eyebrow|meta|body-sm|label)(?=$|\s)/;
|
|
218
219
|
|
|
219
220
|
/** Base-tone small text on a tint of the same tone. */
|
|
220
221
|
/** @param {string} value */
|
|
@@ -293,12 +294,57 @@ const VIEWPORT_BREAKPOINT =
|
|
|
293
294
|
const RAW_TEXT_SIZE =
|
|
294
295
|
/(?:^|[\s:])text-(?:xs|sm|base|lg|xl|[2-9]xl|\[(?:length:[^\]]*|[\d.]+(?:rem|px|em|ex|ch|pt|pc|in|cm|mm|vw|vh|vmin|vmax|%))\])(?:\/[\w.[\]-]+)?(?=$|\s)/;
|
|
295
296
|
|
|
297
|
+
// <type-scale> Generated from the Ekanos type-scale table by `pnpm type-scale`.
|
|
298
|
+
const HEADING_FACE_TIER =
|
|
299
|
+
/(?:^|[\s:])text-(?:display-lg|display|headline-lg|headline|subhead-lg)(?=$|\s)/;
|
|
300
|
+
|
|
301
|
+
const HEADING_FACE_TIER_MESSAGE =
|
|
302
|
+
'A heading-face tier carries its face: write heading-display-lg | heading-display | heading-headline-lg | heading-headline | heading-subhead-lg instead of text-<tier> (with or without font-heading beside it). The heading-<tier> utility sets the face, size, leading, weight and tracking in one class (design-system type-scale rule).';
|
|
303
|
+
|
|
296
304
|
const RAW_TEXT_SIZE_MESSAGE =
|
|
297
|
-
'A raw type step is not the scale. Use the Ekanos type-scale tier:
|
|
305
|
+
'A raw type step is not the scale. Use the Ekanos type-scale tier: heading-display-lg (48px/52, 700) | heading-display (36px/40, 700) | heading-headline-lg (30px/36, 700) | heading-headline (24px/32, 600) | heading-subhead-lg (20px/28, 600) | text-subhead (18px/28, 600) | text-title (16px/24, 600) | text-body (16px/24, 400) | text-body-sm (14px/20, 400) | text-label (14px/20, 500) | text-meta (14px/20, 400) | text-eyebrow (12px/16, 500) | text-fine (12px/16, 400). Each tier carries its leading and weight, so the font-* class beside the old step usually goes with it (design-system type-scale rule).';
|
|
306
|
+
// </type-scale>
|
|
298
307
|
|
|
299
308
|
const TOOLBAR_TAB_ELEMENTS = new Set(['TabsList', 'ScopePills', 'ScopeLinks']);
|
|
300
309
|
const TOOLBARS = new Set(['PageToolbar', 'PageToolbarActions']);
|
|
301
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Whether a JSX element is the value of a `render` prop —
|
|
313
|
+
* `<Item render={<button type="button" />}>`. That is the sanctioned way to
|
|
314
|
+
* give a kit component a native button element, so it is not a raw button.
|
|
315
|
+
*
|
|
316
|
+
* @param {JsxElement} node the opening element
|
|
317
|
+
*/
|
|
318
|
+
function isRenderPropValue(node) {
|
|
319
|
+
const element = node.parent;
|
|
320
|
+
const container = element?.parent;
|
|
321
|
+
const attribute = container?.parent;
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
container?.type === 'JSXExpressionContainer' &&
|
|
325
|
+
attribute?.type === 'JSXAttribute' &&
|
|
326
|
+
attribute.name?.name === 'render'
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* A card surface drawn by hand: a `bg-card` fill with rounded corners and an
|
|
332
|
+
* edge (an elevation tier or a border) on a plain element. `Card` and
|
|
333
|
+
* its `cardSurfaceVariants` own that combination; a call site that spells it out drifts
|
|
334
|
+
* from the radius, padding and elevation the component carries.
|
|
335
|
+
*/
|
|
336
|
+
const CARD_FILL = /(?:^|\s)bg-card(?=$|\s)/;
|
|
337
|
+
const CARD_CORNER = /(?:^|\s)rounded(?:-[\w[\]]+)?(?=$|\s)/;
|
|
338
|
+
const CARD_EDGE =
|
|
339
|
+
/(?:^|\s)(?:shadow-(?:card|widget|xs|sm|md)|border(?:-border)?)(?=$|\s)/;
|
|
340
|
+
|
|
341
|
+
/** @param {string} value */
|
|
342
|
+
function isHandDrawnCard(value) {
|
|
343
|
+
return (
|
|
344
|
+
CARD_FILL.test(value) && CARD_CORNER.test(value) && CARD_EDGE.test(value)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
302
348
|
export default {
|
|
303
349
|
meta: { name: 'design' },
|
|
304
350
|
rules: {
|
|
@@ -485,10 +531,105 @@ export default {
|
|
|
485
531
|
type: 'problem',
|
|
486
532
|
docs: {
|
|
487
533
|
description:
|
|
488
|
-
'Disallow a raw Tailwind type step
|
|
534
|
+
'Disallow a raw Tailwind type step, or a heading-face tier spelled text-<tier>; one scale of tiers, each carrying its leading, weight and face.',
|
|
489
535
|
},
|
|
490
536
|
},
|
|
491
|
-
|
|
537
|
+
/** @param {RuleContext} context */
|
|
538
|
+
create(context) {
|
|
539
|
+
const raw = stringVisitors(
|
|
540
|
+
RAW_TEXT_SIZE,
|
|
541
|
+
RAW_TEXT_SIZE_MESSAGE,
|
|
542
|
+
)(context);
|
|
543
|
+
const face = stringVisitors(
|
|
544
|
+
HEADING_FACE_TIER,
|
|
545
|
+
HEADING_FACE_TIER_MESSAGE,
|
|
546
|
+
)(context);
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
/** @param {StringLiteral} node */
|
|
550
|
+
Literal: (node) => {
|
|
551
|
+
raw.Literal(node);
|
|
552
|
+
face.Literal(node);
|
|
553
|
+
},
|
|
554
|
+
/** @param {TemplateChunk} node */
|
|
555
|
+
TemplateElement: (node) => {
|
|
556
|
+
raw.TemplateElement(node);
|
|
557
|
+
face.TemplateElement(node);
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
'no-raw-button': {
|
|
563
|
+
meta: {
|
|
564
|
+
type: 'problem',
|
|
565
|
+
docs: {
|
|
566
|
+
description:
|
|
567
|
+
'Disallow a raw <button> outside the kit; use Button, or render a button through a kit component.',
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
/** @param {RuleContext} context */
|
|
571
|
+
create(context) {
|
|
572
|
+
return {
|
|
573
|
+
/** @param {JsxElement} node */
|
|
574
|
+
JSXOpeningElement(node) {
|
|
575
|
+
if (jsxName(node.name) !== 'button' || isRenderPropValue(node)) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
context.report({
|
|
580
|
+
node,
|
|
581
|
+
message:
|
|
582
|
+
'A raw <button> misses the focus ring, disabled and loading states, the size scale and the per-Source retint. Use Button (variant="ghost" or "link", size="xs" for a quiet text action), or give a kit row its button element with render={<button type="button" />} (design-system buttons rule).',
|
|
583
|
+
});
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
'no-hand-drawn-card': {
|
|
589
|
+
meta: {
|
|
590
|
+
type: 'problem',
|
|
591
|
+
docs: {
|
|
592
|
+
description:
|
|
593
|
+
'Disallow a card surface (bg-card + rounded + shadow or border) drawn by hand on a plain element.',
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
/** @param {RuleContext} context */
|
|
597
|
+
create(context) {
|
|
598
|
+
return {
|
|
599
|
+
/** @param {JsxElement} node */
|
|
600
|
+
JSXOpeningElement(node) {
|
|
601
|
+
const name = jsxName(node.name);
|
|
602
|
+
|
|
603
|
+
// Only intrinsic elements: a `Card`, `EmptyState` or other kit
|
|
604
|
+
// component given `bg-card` is restyling a component, which is a
|
|
605
|
+
// different question.
|
|
606
|
+
if (!name || name[0] !== name[0]?.toLowerCase()) return;
|
|
607
|
+
|
|
608
|
+
for (const attribute of node.attributes ?? []) {
|
|
609
|
+
if (
|
|
610
|
+
attribute.type !== 'JSXAttribute' ||
|
|
611
|
+
attribute.name?.name !== 'className'
|
|
612
|
+
) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const classes = classNameStrings(attribute.value)
|
|
617
|
+
.map((literal) =>
|
|
618
|
+
typeof literal.value === 'string' ? literal.value : '',
|
|
619
|
+
)
|
|
620
|
+
.join(' ');
|
|
621
|
+
|
|
622
|
+
if (isHandDrawnCard(classes)) {
|
|
623
|
+
context.report({
|
|
624
|
+
node: attribute,
|
|
625
|
+
message:
|
|
626
|
+
'This is a card drawn by hand. Use Card (@kit/ui/card; @ekanos/ui/card for partners) with surface="card" or "widget" and layout="padded" or "bleed", and render={<section />} when it must be another element, so the corner, padding and elevation stay the component\u2019s (design-system cards rule).',
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
},
|
|
492
633
|
},
|
|
493
634
|
},
|
|
494
635
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The official SDK for building Ekanos integrations.",
|
|
6
6
|
"homepage": "https://sdk.ekanos.dev/sdk/overview",
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"access": "public"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@ekanos/integration-schema": "0.
|
|
81
|
-
"@ekanos/ui": "0.
|
|
80
|
+
"@ekanos/integration-schema": "0.17.0",
|
|
81
|
+
"@ekanos/ui": "0.17.0",
|
|
82
82
|
"@supabase/supabase-js": "2.87.1",
|
|
83
83
|
"server-only": "^0.0.1"
|
|
84
84
|
},
|