@alfakit/react 0.1.9 → 0.1.11
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/CHANGELOG.md +12 -0
- package/dist/components/actions/action-button.js +13 -1
- package/dist/components/actions/action-button.js.map +1 -1
- package/dist/components/dev-tools/dev-tools-inspector.js +3 -3
- package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
- package/dist/components/fields/checkbox-list.js +2 -2
- package/dist/components/fields/checkbox-list.js.map +1 -1
- package/dist/components/fields/money-input.d.ts +5 -0
- package/dist/components/fields/money-input.d.ts.map +1 -1
- package/dist/components/fields/money-input.js +2 -5
- package/dist/components/fields/money-input.js.map +1 -1
- package/dist/components/fields/rich-editor-extensions.js +14 -4
- package/dist/components/fields/rich-editor-extensions.js.map +1 -1
- package/dist/components/file-manager/file-manager.js +2 -0
- package/dist/components/file-manager/file-manager.js.map +1 -1
- package/dist/components/layout/elements.d.ts.map +1 -1
- package/dist/components/layout/elements.js +16 -5
- package/dist/components/layout/elements.js.map +1 -1
- package/dist/components/layout/mixed-content.d.ts +6 -4
- package/dist/components/layout/mixed-content.d.ts.map +1 -1
- package/dist/components/layout/mixed-content.js +6 -4
- package/dist/components/layout/mixed-content.js.map +1 -1
- package/dist/components/layout/section.js +4 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/notifications/notifications-menu.d.ts.map +1 -1
- package/dist/components/notifications/notifications-menu.js +45 -17
- package/dist/components/notifications/notifications-menu.js.map +1 -1
- package/dist/components/panel/lazy-placeholder.d.ts +1 -0
- package/dist/components/panel/lazy-placeholder.d.ts.map +1 -1
- package/dist/components/panel/lazy-placeholder.js +38 -4
- package/dist/components/panel/lazy-placeholder.js.map +1 -1
- package/dist/components/panel/shortcuts-overlay.js +13 -10
- package/dist/components/panel/shortcuts-overlay.js.map +1 -1
- package/dist/components/table/cells.js +1 -1
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/column-manager.js +4 -4
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/spreadsheet.js +8 -1
- package/dist/components/table/spreadsheet.js.map +1 -1
- package/dist/components/translation-manager/translation-manager.js +1 -1
- package/dist/components/translation-manager/translation-manager.js.map +1 -1
- package/dist/components/ui/calendar.d.ts.map +1 -1
- package/dist/components/ui/calendar.js +7 -0
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/widgets/dashboard-customizer.js +2 -2
- package/dist/components/widgets/dashboard-customizer.js.map +1 -1
- package/dist/components/widgets/stats-overview.js +3 -3
- package/dist/components/widgets/stats-overview.js.map +1 -1
- package/dist/components/widgets/widgets-block.js +2 -2
- package/dist/components/widgets/widgets-block.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +3 -2
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +4 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.d.ts +0 -10
- package/dist/standalone/alfakit-component.d.ts.map +1 -1
- package/dist/standalone/alfakit-component.js +5 -4
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/runtime.js +2 -1
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone.css +23 -1
- package/dist/starter-kit/index.d.ts +2 -2
- package/dist/starter-kit/index.js +2 -2
- package/dist/starter-kit/navigation.d.ts +6 -1
- package/dist/starter-kit/navigation.d.ts.map +1 -1
- package/dist/starter-kit/navigation.js +3 -2
- package/dist/starter-kit/navigation.js.map +1 -1
- package/dist/starter-kit/starter-kit.d.ts +9 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +22 -3
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/styles.css +2 -1
- package/dist/themes.css +4 -0
- package/package.json +2 -2
- package/src/components/actions/action-button.tsx +15 -1
- package/src/components/dev-tools/dev-tools-inspector.tsx +7 -2
- package/src/components/fields/checkbox-list.tsx +2 -2
- package/src/components/fields/money-input.tsx +3 -14
- package/src/components/fields/rich-editor-extensions.tsx +24 -2
- package/src/components/file-manager/file-manager.tsx +3 -1
- package/src/components/layout/elements.tsx +27 -4
- package/src/components/layout/mixed-content.tsx +8 -4
- package/src/components/layout/section.tsx +2 -2
- package/src/components/notifications/notifications-menu.tsx +42 -1
- package/src/components/panel/lazy-placeholder.tsx +41 -1
- package/src/components/panel/shortcuts-overlay.tsx +29 -24
- package/src/components/table/cells.tsx +3 -1
- package/src/components/table/column-manager.tsx +6 -4
- package/src/components/table/spreadsheet.tsx +18 -7
- package/src/components/translation-manager/translation-manager.tsx +1 -1
- package/src/components/ui/calendar.tsx +8 -0
- package/src/components/widgets/dashboard-customizer.tsx +2 -2
- package/src/components/widgets/stats-overview.tsx +6 -3
- package/src/components/widgets/widgets-block.tsx +2 -2
- package/src/create-alfakit.tsx +9 -1
- package/src/pages/page.tsx +7 -3
- package/src/standalone/alfakit-component.tsx +9 -4
- package/src/standalone/runtime.ts +3 -0
- package/src/starter-kit/index.ts +1 -0
- package/src/starter-kit/navigation.ts +12 -1
- package/src/starter-kit/starter-kit.tsx +25 -2
- package/src/styles.css +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elements.js","names":[],"sources":["../../../src/components/layout/elements.tsx"],"sourcesContent":["import type {\n CalloutSchema,\n EmptyStateComponentSchema,\n FlexSchema,\n IconSchema,\n ImageSchema,\n SchemaComponent,\n UnorderedListSchema,\n} from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n flexItemClasses,\n flexLayoutClasses,\n schemaGapClass,\n} from '@alfakit/core'\nimport type { ReactElement } from 'react'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n iconSizeClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { RenderComponent } from '../schema/schema-renderer'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\nimport { MixedContent } from './mixed-content'\n\nexport function FlexComponent({ component }: { component: FlexSchema }) {\n return (\n <div\n data-slot=\"flex\"\n {...extraProps(\n component.extraAttributes,\n cn(flexLayoutClasses(component), schemaGapClass(component)),\n )}\n >\n {(component.schema ?? []).map((child: SchemaComponent) => (\n <div key={child.key} data-slot=\"flex-item\" className={flexItemClasses(child)}>\n <RenderComponent component={child} />\n </div>\n ))}\n </div>\n )\n}\n\nconst CALLOUT_COLORS: Record<string, string> = {\n primary: 'border-primary/30 bg-primary/5 [&_[data-slot=callout-icon]]:text-primary',\n secondary: 'border-border bg-secondary [&_[data-slot=callout-icon]]:text-secondary-foreground',\n success: 'border-success/30 bg-success/5 [&_[data-slot=callout-icon]]:text-success',\n warning: 'border-warning/40 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning',\n danger: 'border-destructive/30 bg-destructive/5 [&_[data-slot=callout-icon]]:text-destructive',\n info: 'border-info/30 bg-info/5 [&_[data-slot=callout-icon]]:text-info',\n gray: 'border-border bg-muted/40 [&_[data-slot=callout-icon]]:text-muted-foreground',\n}\n\nconst CALLOUT_ICON_SIZES: Record<string, string> = {\n xs: 'size-4',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-7',\n '2xl': 'size-8',\n}\n\nexport function CalloutComponent({ component }: { component: CalloutSchema }) {\n const context = usePageActionContext()\n const tone = component.color ?? component.status\n // `iconColor()`, or the status colour once `color(null)` removed the background.\n const iconColor = component.iconColor ?? (component.color ? undefined : component.status)\n\n return (\n <div\n data-slot=\"callout\"\n data-color={component.color ?? 'gray'}\n data-status={component.status}\n role={tone === 'danger' || tone === 'warning' ? 'alert' : 'note'}\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex items-start gap-3 rounded-lg border p-4 text-sm',\n colorClass(CALLOUT_COLORS, component.color),\n ),\n )}\n >\n {component.icon && (\n <span data-slot=\"callout-icon\" className=\"mt-0.5 shrink-0\">\n <Icon\n name={component.icon}\n className={cn(\n (component.iconSize && CALLOUT_ICON_SIZES[component.iconSize]) || 'size-5',\n iconColor && colorClass(textColorClasses, iconColor),\n )}\n />\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {component.heading && <p className=\"leading-6 font-semibold\">{component.heading}</p>}\n {component.description && (\n <div\n className=\"text-muted-foreground alfakit-prose\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList\n actions={component.actions}\n context={context}\n size=\"sm\"\n className={cn('mt-2', actionsAlignmentClass(component.footerActionsAlignment))}\n />\n )}\n <MixedContent items={component.footer} name=\"callout-footer\" className=\"mt-2\" />\n </div>\n <MixedContent\n items={component.controls}\n name=\"callout-controls\"\n className=\"-me-1 -mt-1 shrink-0 gap-1\"\n />\n </div>\n )\n}\n\nexport function EmptyStateComponent({ component }: { component: EmptyStateComponentSchema }) {\n const context = usePageActionContext()\n\n return (\n <div\n data-slot=\"empty-state\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex flex-col items-center justify-center gap-3 px-6 py-10 text-center',\n component.contained && 'rounded-xl border border-dashed',\n ),\n )}\n >\n {component.icon && (\n <div\n className={cn(\n 'flex size-12 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, component.color),\n )}\n >\n <Icon name={component.icon} className=\"size-6\" />\n </div>\n )}\n {component.heading && <h3 className=\"text-base font-semibold\">{component.heading}</h3>}\n {component.description && (\n <div\n className=\"text-muted-foreground max-w-md text-sm\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList actions={component.actions} context={context} className=\"mt-2 justify-center\" />\n )}\n <MixedContent items={component.footer} name=\"empty-state-footer\" className=\"justify-center\" />\n </div>\n )\n}\n\nfunction withTooltip(element: ReactElement, tooltip?: string): ReactElement {\n if (!tooltip) return element\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{element}</TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n )\n}\n\nexport function IconComponent({ component }: { component: IconSchema }) {\n return withTooltip(\n <span\n data-slot=\"icon\"\n {...extraProps(component.extraAttributes, 'inline-flex')}\n aria-label={component.tooltip}\n role={component.tooltip ? 'img' : undefined}\n >\n <Icon\n name={component.icon}\n aria-hidden\n className={cn(\n iconSizeClasses[component.size ?? 'md'] ?? 'size-5',\n component.color && textColorClasses[component.color],\n )}\n />\n </span>,\n component.tooltip,\n )\n}\n\nconst IMAGE_ALIGNMENT: Record<string, string> = {\n start: 'justify-start',\n center: 'justify-center',\n end: 'justify-end',\n}\n\nexport function ImageComponent({ component }: { component: ImageSchema }) {\n return (\n <div\n data-slot=\"image\"\n {...extraProps(\n component.extraAttributes,\n cn('flex', IMAGE_ALIGNMENT[component.alignment ?? 'start'] ?? 'justify-start'),\n )}\n >\n {withTooltip(\n <img\n src={component.url}\n alt={component.alt}\n loading=\"lazy\"\n className={cn(\n 'max-w-full object-cover',\n component.circular ? 'rounded-full' : 'rounded-md',\n )}\n style={{\n width: component.imageWidth ?? undefined,\n height: component.imageHeight ?? undefined,\n }}\n />,\n component.tooltip,\n )}\n </div>\n )\n}\n\nconst LIST_SIZES: Record<string, string> = {\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n}\n\nexport function UnorderedListComponent({ component }: { component: UnorderedListSchema }) {\n return (\n <ul\n data-slot=\"unordered-list\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'marker:text-muted-foreground flex list-disc flex-col gap-1 ps-5',\n LIST_SIZES[component.size ?? 'sm'] ?? 'text-sm',\n ),\n )}\n >\n {(component.schema ?? []).map((child) => (\n <li key={child.key}>\n <RenderComponent component={child} />\n </li>\n ))}\n </ul>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"elements.js","names":[],"sources":["../../../src/components/layout/elements.tsx"],"sourcesContent":["import type {\n CalloutSchema,\n EmptyStateComponentSchema,\n FlexSchema,\n IconSchema,\n ImageSchema,\n SchemaComponent,\n UnorderedListSchema,\n} from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n flexItemClasses,\n flexLayoutClasses,\n schemaGapClass,\n} from '@alfakit/core'\nimport type { ReactElement } from 'react'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n iconSizeClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { RenderComponent } from '../schema/schema-renderer'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\nimport { MixedContent } from './mixed-content'\n\nexport function FlexComponent({ component }: { component: FlexSchema }) {\n return (\n <div\n data-slot=\"flex\"\n {...extraProps(\n component.extraAttributes,\n cn(flexLayoutClasses(component), schemaGapClass(component)),\n )}\n >\n {(component.schema ?? []).map((child: SchemaComponent) => (\n <div key={child.key} data-slot=\"flex-item\" className={flexItemClasses(child)}>\n <RenderComponent component={child} />\n </div>\n ))}\n </div>\n )\n}\n\nconst CALLOUT_COLORS: Record<string, string> = {\n primary: 'border-primary/30 bg-primary/5 [&_[data-slot=callout-icon]]:text-primary',\n secondary: 'border-border bg-secondary [&_[data-slot=callout-icon]]:text-secondary-foreground',\n success: 'border-success/30 bg-success/5 [&_[data-slot=callout-icon]]:text-success',\n warning: 'border-warning/40 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning',\n danger: 'border-destructive/30 bg-destructive/5 [&_[data-slot=callout-icon]]:text-destructive',\n info: 'border-info/30 bg-info/5 [&_[data-slot=callout-icon]]:text-info',\n gray: 'border-border bg-muted/40 [&_[data-slot=callout-icon]]:text-muted-foreground',\n}\n\nconst CALLOUT_ICON_SIZES: Record<string, string> = {\n xs: 'size-4',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-7',\n '2xl': 'size-8',\n}\n\nexport function CalloutComponent({ component }: { component: CalloutSchema }) {\n // Inside a form the actions run as field actions of this component, so `$get` / `$set`\n // reach the form (like a section's header actions).\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n const tone = component.color ?? component.status\n // `iconColor()`, or the status colour once `color(null)` removed the background.\n const iconColor = component.iconColor ?? (component.color ? undefined : component.status)\n\n return (\n <div\n data-slot=\"callout\"\n data-color={component.color ?? 'gray'}\n data-status={component.status}\n role={tone === 'danger' || tone === 'warning' ? 'alert' : 'note'}\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex items-start gap-3 rounded-lg border p-4 text-sm',\n colorClass(CALLOUT_COLORS, component.color),\n ),\n )}\n >\n {component.icon && (\n <span data-slot=\"callout-icon\" className=\"mt-0.5 shrink-0\">\n <Icon\n name={component.icon}\n className={cn(\n (component.iconSize && CALLOUT_ICON_SIZES[component.iconSize]) || 'size-5',\n iconColor && colorClass(textColorClasses, iconColor),\n )}\n />\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {component.heading && <p className=\"leading-6 font-semibold\">{component.heading}</p>}\n {component.description && (\n <div\n className=\"text-muted-foreground alfakit-prose\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList\n actions={component.actions}\n context={context}\n size=\"sm\"\n className={cn('mt-2', actionsAlignmentClass(component.footerActionsAlignment))}\n />\n )}\n <MixedContent\n items={component.footer}\n name=\"callout-footer\"\n className=\"mt-2\"\n context={context}\n />\n </div>\n <MixedContent\n items={component.controls}\n name=\"callout-controls\"\n className=\"-me-1 -mt-1 shrink-0 gap-1\"\n context={context}\n />\n </div>\n )\n}\n\nexport function EmptyStateComponent({ component }: { component: EmptyStateComponentSchema }) {\n // Inside a form the actions run as field actions of this component, so `$get` / `$set`\n // reach the form (like a section's header actions).\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n\n return (\n <div\n data-slot=\"empty-state\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex flex-col items-center justify-center gap-3 px-6 py-10 text-center',\n component.contained && 'rounded-xl border border-dashed',\n ),\n )}\n >\n {component.icon && (\n <div\n className={cn(\n 'flex size-12 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, component.color),\n )}\n >\n <Icon name={component.icon} className=\"size-6\" />\n </div>\n )}\n {component.heading && <h3 className=\"text-base font-semibold\">{component.heading}</h3>}\n {component.description && (\n <div\n className=\"text-muted-foreground max-w-md text-sm\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList actions={component.actions} context={context} className=\"mt-2 justify-center\" />\n )}\n <MixedContent\n items={component.footer}\n name=\"empty-state-footer\"\n className=\"justify-center\"\n context={context}\n />\n </div>\n )\n}\n\nfunction withTooltip(element: ReactElement, tooltip?: string): ReactElement {\n if (!tooltip) return element\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{element}</TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n )\n}\n\nexport function IconComponent({ component }: { component: IconSchema }) {\n return withTooltip(\n <span\n data-slot=\"icon\"\n {...extraProps(component.extraAttributes, 'inline-flex')}\n aria-label={component.tooltip}\n role={component.tooltip ? 'img' : undefined}\n >\n <Icon\n name={component.icon}\n aria-hidden\n className={cn(\n iconSizeClasses[component.size ?? 'md'] ?? 'size-5',\n component.color && textColorClasses[component.color],\n )}\n />\n </span>,\n component.tooltip,\n )\n}\n\nconst IMAGE_ALIGNMENT: Record<string, string> = {\n start: 'justify-start',\n center: 'justify-center',\n end: 'justify-end',\n}\n\nexport function ImageComponent({ component }: { component: ImageSchema }) {\n return (\n <div\n data-slot=\"image\"\n {...extraProps(\n component.extraAttributes,\n cn('flex', IMAGE_ALIGNMENT[component.alignment ?? 'start'] ?? 'justify-start'),\n )}\n >\n {withTooltip(\n <img\n src={component.url}\n alt={component.alt}\n loading=\"lazy\"\n className={cn(\n 'max-w-full object-cover',\n component.circular ? 'rounded-full' : 'rounded-md',\n )}\n style={{\n width: component.imageWidth ?? undefined,\n height: component.imageHeight ?? undefined,\n }}\n />,\n component.tooltip,\n )}\n </div>\n )\n}\n\nconst LIST_SIZES: Record<string, string> = {\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n}\n\nexport function UnorderedListComponent({ component }: { component: UnorderedListSchema }) {\n return (\n <ul\n data-slot=\"unordered-list\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'marker:text-muted-foreground flex list-disc flex-col gap-1 ps-5',\n LIST_SIZES[component.size ?? 'sm'] ?? 'text-sm',\n ),\n )}\n >\n {(component.schema ?? []).map((child) => (\n <li key={child.key}>\n <RenderComponent component={child} />\n </li>\n ))}\n </ul>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { ContentSlotItem } from "@alfakit/core";
|
|
1
|
+
import { ActionContext, ContentSlotItem } from "@alfakit/core";
|
|
2
2
|
//#region src/components/layout/mixed-content.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Actions and components placed in a layout component's fixed spot (a section's
|
|
5
|
-
* `afterHeader()`, a callout's `controls()`). Actions run in the
|
|
6
|
-
*
|
|
5
|
+
* `afterHeader()`, a callout's `controls()`). Actions run in the owner's action context
|
|
6
|
+
* (`context`: inside a form, the owner's field context, so `$get` / `$set` work), else the
|
|
7
|
+
* page's.
|
|
7
8
|
*/
|
|
8
|
-
export declare function MixedContent({ items, name, className }: {
|
|
9
|
+
export declare function MixedContent({ items, name, className, context: ownerContext }: {
|
|
9
10
|
items: ContentSlotItem[] | undefined;
|
|
10
11
|
name: string;
|
|
11
12
|
className?: string;
|
|
13
|
+
context?: ActionContext;
|
|
12
14
|
}): import("react").JSX.Element | null;
|
|
13
15
|
//#endregion
|
|
14
16
|
//# sourceMappingURL=mixed-content.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mixed-content.d.ts","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"mixed-content.d.ts","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"mappings":";;;;;;;;wBAagB,eACd,OACA,MACA,WACA,SAAS;EAET,OAAO;EACP;EACA;EACA,UAAU;oBACX,IAAA"}
|
|
@@ -6,11 +6,13 @@ import { jsx } from "react/jsx-runtime";
|
|
|
6
6
|
//#region src/components/layout/mixed-content.tsx
|
|
7
7
|
/**
|
|
8
8
|
* Actions and components placed in a layout component's fixed spot (a section's
|
|
9
|
-
* `afterHeader()`, a callout's `controls()`). Actions run in the
|
|
10
|
-
*
|
|
9
|
+
* `afterHeader()`, a callout's `controls()`). Actions run in the owner's action context
|
|
10
|
+
* (`context`: inside a form, the owner's field context, so `$get` / `$set` work), else the
|
|
11
|
+
* page's.
|
|
11
12
|
*/
|
|
12
|
-
function MixedContent({ items, name, className }) {
|
|
13
|
-
const
|
|
13
|
+
function MixedContent({ items, name, className, context: ownerContext }) {
|
|
14
|
+
const pageContext = usePageActionContext();
|
|
15
|
+
const context = ownerContext ?? pageContext;
|
|
14
16
|
if (!items?.length) return null;
|
|
15
17
|
const nodes = [];
|
|
16
18
|
let actions = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mixed-content.js","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"sourcesContent":["import type { AnyActionSchema, ContentSlotItem } from '@alfakit/core'\nimport type { ReactNode } from 'react'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { RenderComponent } from '../schema/schema-renderer'\n\n/**\n * Actions and components placed in a layout component's fixed spot (a section's\n * `afterHeader()`, a callout's `controls()`). Actions run in the
|
|
1
|
+
{"version":3,"file":"mixed-content.js","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"sourcesContent":["import type { ActionContext, AnyActionSchema, ContentSlotItem } from '@alfakit/core'\nimport type { ReactNode } from 'react'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { RenderComponent } from '../schema/schema-renderer'\n\n/**\n * Actions and components placed in a layout component's fixed spot (a section's\n * `afterHeader()`, a callout's `controls()`). Actions run in the owner's action context\n * (`context`: inside a form, the owner's field context, so `$get` / `$set` work), else the\n * page's.\n */\nexport function MixedContent({\n items,\n name,\n className,\n context: ownerContext,\n}: {\n items: ContentSlotItem[] | undefined\n name: string\n className?: string\n context?: ActionContext\n}) {\n const pageContext = usePageActionContext()\n const context = ownerContext ?? pageContext\n\n if (!items?.length) {\n return null\n }\n\n // Consecutive actions share one action list so they sit together.\n const nodes: ReactNode[] = []\n let actions: AnyActionSchema[] = []\n\n const flush = () => {\n if (actions.length === 0) return\n\n nodes.push(\n <ActionList key={`actions-${nodes.length}`} actions={actions} context={context} size=\"sm\" />,\n )\n actions = []\n }\n\n for (const item of items) {\n if (item.action) {\n actions.push(item.action)\n\n continue\n }\n\n flush()\n nodes.push(<RenderComponent key={item.component.key} component={item.component} />)\n }\n\n flush()\n\n return (\n <div data-slot={name} className={cn('flex flex-wrap items-center gap-2', className)}>\n {nodes}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,aAAa,EAC3B,OACA,MACA,WACA,SAAS,gBAMR;CACD,MAAM,cAAc,qBAAqB;CACzC,MAAM,UAAU,gBAAgB;CAEhC,IAAI,CAAC,OAAO,QACV,OAAO;CAIT,MAAM,QAAqB,CAAC;CAC5B,IAAI,UAA6B,CAAC;CAElC,MAAM,cAAc;EAClB,IAAI,QAAQ,WAAW,GAAG;EAE1B,MAAM,KACJ,oBAAC,YAAD;GAAqD;GAAkB;GAAS,MAAK;EAAM,GAA1E,WAAW,MAAM,QAAyD,CAC7F;EACA,UAAU,CAAC;CACb;CAEA,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,QAAQ;GACf,QAAQ,KAAK,KAAK,MAAM;GAExB;EACF;EAEA,MAAM;EACN,MAAM,KAAK,oBAAC,iBAAD,EAA0C,WAAW,KAAK,UAAY,GAAhD,KAAK,UAAU,GAAiC,CAAC;CACpF;CAEA,MAAM;CAEN,OACE,oBAAC,OAAD;EAAK,aAAW;EAAM,WAAW,GAAG,qCAAqC,SAAS;EAC/E,UAAA;CACE,CAAA;AAET"}
|
|
@@ -61,7 +61,8 @@ function SectionComponent({ component }) {
|
|
|
61
61
|
}),
|
|
62
62
|
/* @__PURE__ */ jsx(MixedContent, {
|
|
63
63
|
items: component.afterHeader,
|
|
64
|
-
name: "section-after-header"
|
|
64
|
+
name: "section-after-header",
|
|
65
|
+
context
|
|
65
66
|
}),
|
|
66
67
|
component.collapsible && /* @__PURE__ */ jsx(CollapsibleTrigger, {
|
|
67
68
|
asChild: true,
|
|
@@ -89,7 +90,8 @@ function SectionComponent({ component }) {
|
|
|
89
90
|
className: actionsAlignmentClass(component.footerActionsAlignment) || void 0
|
|
90
91
|
}), /* @__PURE__ */ jsx(MixedContent, {
|
|
91
92
|
items: component.footer,
|
|
92
|
-
name: "section-footer-content"
|
|
93
|
+
name: "section-footer-content",
|
|
94
|
+
context
|
|
93
95
|
})]
|
|
94
96
|
}) : null;
|
|
95
97
|
if (component.aside) return /* @__PURE__ */ jsx(Collapsible, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section.js","names":[],"sources":["../../../src/components/layout/section.tsx"],"sourcesContent":["import type { SectionSchema } from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n headingTag,\n readPersisted,\n schemaGapClass,\n writePersisted,\n} from '@alfakit/core'\nimport { ChevronDownIcon } from 'lucide-react'\nimport { createElement, useState } from 'react'\nimport { useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport { DeferredSchema } from './deferred'\nimport { MixedContent } from './mixed-content'\n\nexport function SectionComponent({ component }: { component: SectionSchema }) {\n const t = useT()\n const [choice, setChoice] = useState<boolean | null>(null)\n // Inside a form the section's actions run as field actions, so `$get` / `$set` work.\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n const persistKey = component.persistCollapsed\n const hydrated = useHydrated()\n // Read only after hydration, so the server render and hydration use the schema's default.\n const stored = hydrated && persistKey ? readPersisted(persistKey) : null\n const open = choice ?? (stored !== null ? stored !== 'collapsed' : !component.collapsed)\n const hasHeader =\n component.heading ||\n component.description ||\n component.icon ||\n component.headerActions?.length ||\n component.afterHeader?.length\n\n const setOpen = (next: boolean) => {\n setChoice(next)\n\n if (persistKey) writePersisted(persistKey, next ? 'expanded' : 'collapsed')\n }\n\n const content = (\n <DeferredSchema component={component}>\n {(loaded) => (\n <SchemaRenderer\n components={loaded.schema ?? []}\n columns={loaded.columns}\n className={schemaGapClass(loaded)}\n />\n )}\n </DeferredSchema>\n )\n\n const header = hasHeader ? (\n <div\n className={cn(\n 'flex flex-wrap items-start gap-3',\n component.aside ? 'lg:col-span-1' : 'border-b px-6 py-4',\n component.compact && !component.aside && 'px-4 py-3',\n )}\n >\n {component.icon && (\n <Icon name={component.icon} className=\"text-muted-foreground mt-0.5 size-5\" />\n )}\n <div className=\"min-w-0 flex-1 basis-48\">\n {component.heading &&\n createElement(\n headingTag(component.headingLevel),\n { className: 'text-base leading-6 font-semibold break-words' },\n component.heading,\n )}\n {component.description && (\n <p className=\"text-muted-foreground mt-1 text-sm break-words\">{component.description}</p>\n )}\n </div>\n {component.headerActions && component.headerActions.length > 0 && (\n <ActionList actions={component.headerActions} context={context} size=\"sm\" />\n )}\n <MixedContent items={component.afterHeader} name=\"section-after-header\" />\n {component.collapsible && (\n <CollapsibleTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('toggle_section', {}, 'Toggle section')}\n >\n <Icon\n name=\"alfakit::schemas.section.collapse-button\"\n fallback={ChevronDownIcon}\n className={cn('transition-transform', open && 'rotate-180')}\n />\n </Button>\n </CollapsibleTrigger>\n )}\n </div>\n ) : null\n\n const hasFooterActions = Boolean(component.footerActions?.length)\n const footer =\n hasFooterActions || component.footer?.length ? (\n <div\n data-slot=\"section-footer\"\n className={cn(\n 'flex flex-wrap items-center gap-3 border-t px-6 py-4',\n component.compact && 'px-4 py-3',\n )}\n >\n {hasFooterActions && (\n <ActionList\n actions={component.footerActions ?? []}\n context={context}\n className={actionsAlignmentClass(component.footerActionsAlignment) || undefined}\n />\n )}\n <MixedContent items={component.footer} name=\"section-footer-content\" />\n </div>\n ) : null\n\n if (component.aside) {\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n {...extraProps(component.extraAttributes, 'grid gap-6 lg:grid-cols-3')}\n >\n {header}\n <CollapsibleContent asChild>\n <div\n data-surface=\"section\"\n className={cn(\n 'bg-card rounded-xl border shadow-xs lg:col-span-2',\n component.secondary && 'bg-muted/40',\n )}\n >\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </div>\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n }\n\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n data-surface=\"section\"\n {...extraProps(\n component.extraAttributes,\n cn('bg-card rounded-xl border shadow-xs', component.secondary && 'bg-muted/40'),\n )}\n >\n {header}\n <CollapsibleContent>\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,iBAAiB,EAAE,aAA2C;CAC5E,MAAM,IAAI,KAAK;CACf,MAAM,CAAC,QAAQ,aAAa,SAAyB,IAAI;CAEzD,MAAM,OAAO,qBAAqB;CAClC,MAAM,cAAc,qBAAqB;CACzC,MAAM,eAAe,sBAAsB,SAAS;CACpD,MAAM,UAAU,OAAO,eAAe;CACtC,MAAM,aAAa,UAAU;CAG7B,MAAM,SAFW,YAEK,KAAK,aAAa,cAAc,UAAU,IAAI;CACpE,MAAM,OAAO,WAAW,WAAW,OAAO,WAAW,cAAc,CAAC,UAAU;CAC9E,MAAM,YACJ,UAAU,WACV,UAAU,eACV,UAAU,QACV,UAAU,eAAe,UACzB,UAAU,aAAa;CAEzB,MAAM,WAAW,SAAkB;EACjC,UAAU,IAAI;EAEd,IAAI,YAAY,eAAe,YAAY,OAAO,aAAa,WAAW;CAC5E;CAEA,MAAM,UACJ,oBAAC,gBAAD;EAA2B;EACvB,WAAA,WACA,oBAAC,gBAAD;GACE,YAAY,OAAO,UAAU,CAAC;GAC9B,SAAS,OAAO;GAChB,WAAW,eAAe,MAAM;EACjC,CAAA;CAEW,CAAA;CAGlB,MAAM,SAAS,YACb,qBAAC,OAAD;EACE,WAAW,GACT,oCACA,UAAU,QAAQ,kBAAkB,sBACpC,UAAU,WAAW,CAAC,UAAU,SAAS,WAC3C;EALF,UAAA;GAOG,UAAU,QACT,oBAAC,MAAD;IAAM,MAAM,UAAU;IAAM,WAAU;GAAuC,CAAA;GAE/E,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,UAAU,WACT,cACE,WAAW,UAAU,YAAY,GACjC,EAAE,WAAW,gDAAgD,GAC7D,UAAU,OACZ,GACD,UAAU,eACT,oBAAC,KAAD;KAAG,WAAU;KAAkD,UAAA,UAAU;IAAe,CAAA,CAEvF;;GACJ,UAAU,iBAAiB,UAAU,cAAc,SAAS,KAC3D,oBAAC,YAAD;IAAY,SAAS,UAAU;IAAwB;IAAS,MAAK;GAAM,CAAA;GAE7E,oBAAC,cAAD;IAAc,OAAO,UAAU;IAAa,MAAK;
|
|
1
|
+
{"version":3,"file":"section.js","names":[],"sources":["../../../src/components/layout/section.tsx"],"sourcesContent":["import type { SectionSchema } from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n headingTag,\n readPersisted,\n schemaGapClass,\n writePersisted,\n} from '@alfakit/core'\nimport { ChevronDownIcon } from 'lucide-react'\nimport { createElement, useState } from 'react'\nimport { useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport { DeferredSchema } from './deferred'\nimport { MixedContent } from './mixed-content'\n\nexport function SectionComponent({ component }: { component: SectionSchema }) {\n const t = useT()\n const [choice, setChoice] = useState<boolean | null>(null)\n // Inside a form the section's actions run as field actions, so `$get` / `$set` work.\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n const persistKey = component.persistCollapsed\n const hydrated = useHydrated()\n // Read only after hydration, so the server render and hydration use the schema's default.\n const stored = hydrated && persistKey ? readPersisted(persistKey) : null\n const open = choice ?? (stored !== null ? stored !== 'collapsed' : !component.collapsed)\n const hasHeader =\n component.heading ||\n component.description ||\n component.icon ||\n component.headerActions?.length ||\n component.afterHeader?.length\n\n const setOpen = (next: boolean) => {\n setChoice(next)\n\n if (persistKey) writePersisted(persistKey, next ? 'expanded' : 'collapsed')\n }\n\n const content = (\n <DeferredSchema component={component}>\n {(loaded) => (\n <SchemaRenderer\n components={loaded.schema ?? []}\n columns={loaded.columns}\n className={schemaGapClass(loaded)}\n />\n )}\n </DeferredSchema>\n )\n\n const header = hasHeader ? (\n <div\n className={cn(\n 'flex flex-wrap items-start gap-3',\n component.aside ? 'lg:col-span-1' : 'border-b px-6 py-4',\n component.compact && !component.aside && 'px-4 py-3',\n )}\n >\n {component.icon && (\n <Icon name={component.icon} className=\"text-muted-foreground mt-0.5 size-5\" />\n )}\n <div className=\"min-w-0 flex-1 basis-48\">\n {component.heading &&\n createElement(\n headingTag(component.headingLevel),\n { className: 'text-base leading-6 font-semibold break-words' },\n component.heading,\n )}\n {component.description && (\n <p className=\"text-muted-foreground mt-1 text-sm break-words\">{component.description}</p>\n )}\n </div>\n {component.headerActions && component.headerActions.length > 0 && (\n <ActionList actions={component.headerActions} context={context} size=\"sm\" />\n )}\n <MixedContent items={component.afterHeader} name=\"section-after-header\" context={context} />\n {component.collapsible && (\n <CollapsibleTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('toggle_section', {}, 'Toggle section')}\n >\n <Icon\n name=\"alfakit::schemas.section.collapse-button\"\n fallback={ChevronDownIcon}\n className={cn('transition-transform', open && 'rotate-180')}\n />\n </Button>\n </CollapsibleTrigger>\n )}\n </div>\n ) : null\n\n const hasFooterActions = Boolean(component.footerActions?.length)\n const footer =\n hasFooterActions || component.footer?.length ? (\n <div\n data-slot=\"section-footer\"\n className={cn(\n 'flex flex-wrap items-center gap-3 border-t px-6 py-4',\n component.compact && 'px-4 py-3',\n )}\n >\n {hasFooterActions && (\n <ActionList\n actions={component.footerActions ?? []}\n context={context}\n className={actionsAlignmentClass(component.footerActionsAlignment) || undefined}\n />\n )}\n <MixedContent items={component.footer} name=\"section-footer-content\" context={context} />\n </div>\n ) : null\n\n if (component.aside) {\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n {...extraProps(component.extraAttributes, 'grid gap-6 lg:grid-cols-3')}\n >\n {header}\n <CollapsibleContent asChild>\n <div\n data-surface=\"section\"\n className={cn(\n 'bg-card rounded-xl border shadow-xs lg:col-span-2',\n component.secondary && 'bg-muted/40',\n )}\n >\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </div>\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n }\n\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n data-surface=\"section\"\n {...extraProps(\n component.extraAttributes,\n cn('bg-card rounded-xl border shadow-xs', component.secondary && 'bg-muted/40'),\n )}\n >\n {header}\n <CollapsibleContent>\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,iBAAiB,EAAE,aAA2C;CAC5E,MAAM,IAAI,KAAK;CACf,MAAM,CAAC,QAAQ,aAAa,SAAyB,IAAI;CAEzD,MAAM,OAAO,qBAAqB;CAClC,MAAM,cAAc,qBAAqB;CACzC,MAAM,eAAe,sBAAsB,SAAS;CACpD,MAAM,UAAU,OAAO,eAAe;CACtC,MAAM,aAAa,UAAU;CAG7B,MAAM,SAFW,YAEK,KAAK,aAAa,cAAc,UAAU,IAAI;CACpE,MAAM,OAAO,WAAW,WAAW,OAAO,WAAW,cAAc,CAAC,UAAU;CAC9E,MAAM,YACJ,UAAU,WACV,UAAU,eACV,UAAU,QACV,UAAU,eAAe,UACzB,UAAU,aAAa;CAEzB,MAAM,WAAW,SAAkB;EACjC,UAAU,IAAI;EAEd,IAAI,YAAY,eAAe,YAAY,OAAO,aAAa,WAAW;CAC5E;CAEA,MAAM,UACJ,oBAAC,gBAAD;EAA2B;EACvB,WAAA,WACA,oBAAC,gBAAD;GACE,YAAY,OAAO,UAAU,CAAC;GAC9B,SAAS,OAAO;GAChB,WAAW,eAAe,MAAM;EACjC,CAAA;CAEW,CAAA;CAGlB,MAAM,SAAS,YACb,qBAAC,OAAD;EACE,WAAW,GACT,oCACA,UAAU,QAAQ,kBAAkB,sBACpC,UAAU,WAAW,CAAC,UAAU,SAAS,WAC3C;EALF,UAAA;GAOG,UAAU,QACT,oBAAC,MAAD;IAAM,MAAM,UAAU;IAAM,WAAU;GAAuC,CAAA;GAE/E,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,UAAU,WACT,cACE,WAAW,UAAU,YAAY,GACjC,EAAE,WAAW,gDAAgD,GAC7D,UAAU,OACZ,GACD,UAAU,eACT,oBAAC,KAAD;KAAG,WAAU;KAAkD,UAAA,UAAU;IAAe,CAAA,CAEvF;;GACJ,UAAU,iBAAiB,UAAU,cAAc,SAAS,KAC3D,oBAAC,YAAD;IAAY,SAAS,UAAU;IAAwB;IAAS,MAAK;GAAM,CAAA;GAE7E,oBAAC,cAAD;IAAc,OAAO,UAAU;IAAa,MAAK;IAAgC;GAAU,CAAA;GAC1F,UAAU,eACT,oBAAC,oBAAD;IAAoB,SAAA;IAClB,UAAA,oBAAC,QAAD;KACE,MAAK;KACL,SAAQ;KACR,MAAK;KACL,cAAY,EAAE,kBAAkB,CAAC,GAAG,gBAAgB;KAEpD,UAAA,oBAAC,MAAD;MACE,MAAK;MACL,UAAU;MACV,WAAW,GAAG,wBAAwB,QAAQ,YAAY;KAC3D,CAAA;IACK,CAAA;GACU,CAAA;EAEnB;CACH,CAAA,IAAA;CAEJ,MAAM,mBAAmB,QAAQ,UAAU,eAAe,MAAM;CAChE,MAAM,SACJ,oBAAoB,UAAU,QAAQ,SACpC,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,wDACA,UAAU,WAAW,WACvB;EALF,UAAA,CAOG,oBACC,oBAAC,YAAD;GACE,SAAS,UAAU,iBAAiB,CAAC;GAC5B;GACT,WAAW,sBAAsB,UAAU,sBAAsB,KAAK,KAAA;EACvE,CAAA,GAEH,oBAAC,cAAD;GAAc,OAAO,UAAU;GAAQ,MAAK;GAAkC;EAAU,CAAA,CACrF;CACH,CAAA,IAAA;CAEN,IAAI,UAAU,OACZ,OACE,oBAAC,aAAD;EAAa,MAAM,CAAC,UAAU,eAAe;EAAM,cAAc;EAAS,SAAA;EACxE,UAAA,qBAAC,WAAD;GACE,aAAU;GACV,GAAI,WAAW,UAAU,iBAAiB,2BAA2B;GAFvE,UAAA,CAIG,QACD,oBAAC,oBAAD;IAAoB,SAAA;IAClB,UAAA,qBAAC,OAAD;KACE,gBAAa;KACb,WAAW,GACT,qDACA,UAAU,aAAa,aACzB;KALF,UAAA,CAOE,oBAAC,OAAD;MAAK,WAAW,GAAG,OAAO,UAAU,WAAW,KAAK;MAAI,UAAA;KAAa,CAAA,GACpE,MACE;;GACa,CAAA,CACb;;CACE,CAAA;CAIjB,OACE,oBAAC,aAAD;EAAa,MAAM,CAAC,UAAU,eAAe;EAAM,cAAc;EAAS,SAAA;EACxE,UAAA,qBAAC,WAAD;GACE,aAAU;GACV,gBAAa;GACb,GAAI,WACF,UAAU,iBACV,GAAG,uCAAuC,UAAU,aAAa,aAAa,CAChF;GANF,UAAA,CAQG,QACD,qBAAC,oBAAD,EAAA,UAAA,CACE,oBAAC,OAAD;IAAK,WAAW,GAAG,OAAO,UAAU,WAAW,KAAK;IAAI,UAAA;GAAa,CAAA,GACpE,MACiB,EAAA,CAAA,CACb;;CACE,CAAA;AAEjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications-menu.d.ts","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"mappings":";
|
|
1
|
+
{"version":3,"file":"notifications-menu.d.ts","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"mappings":";iBAuBiB;;;;;EAKf;;;;;;wBAOc,oBAAoB,YAAsB,yCAA2B,IAAA"}
|
|
@@ -8,10 +8,11 @@ import { Spinner } from "../ui/spinner.js";
|
|
|
8
8
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
|
|
9
9
|
import { NotificationActionButton } from "./flash-toasts.js";
|
|
10
10
|
import { ScrollArea } from "../ui/scroll-area.js";
|
|
11
|
-
import { NotificationCenter, notificationTintStyles, onDatabaseNotificationsSent, onOpenDatabaseNotifications } from "@alfakit/core";
|
|
11
|
+
import { NotificationCenter, notificationTintStyles, onDatabaseNotificationsSent, onOpenDatabaseNotifications, pwaStore, startPwa, togglePushNotifications } from "@alfakit/core";
|
|
12
12
|
import { useEffect, useMemo, useState } from "react";
|
|
13
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
-
import { BellIcon, CheckCheckIcon, Trash2Icon } from "lucide-react";
|
|
14
|
+
import { BellIcon, BellOffIcon, BellRingIcon, CheckCheckIcon, Trash2Icon } from "lucide-react";
|
|
15
|
+
import { toast } from "sonner";
|
|
15
16
|
//#region src/components/notifications/notifications-menu.tsx
|
|
16
17
|
/**
|
|
17
18
|
* Bell menu for database notifications. It also opens when something calls
|
|
@@ -55,6 +56,19 @@ function NotificationsMenu({ variant = "topbar" } = {}) {
|
|
|
55
56
|
}, [open, center]);
|
|
56
57
|
useEffect(() => onOpenDatabaseNotifications(() => setOpen(true)), []);
|
|
57
58
|
useEffect(() => center ? onDatabaseNotificationsSent(() => void center.load()) : void 0, [center]);
|
|
59
|
+
const pwa = useStore(pwaStore);
|
|
60
|
+
const kitPush = Boolean(panel.starterKit && panel.pwa?.push);
|
|
61
|
+
const pushToggle = kitPush && pwa.push !== "unsupported";
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (kitPush) startPwa(panel.pwa);
|
|
64
|
+
}, [kitPush, panel.pwa]);
|
|
65
|
+
const togglePush = async () => {
|
|
66
|
+
const { message } = await togglePushNotifications();
|
|
67
|
+
const text = t(message.key, {}, message.fallback);
|
|
68
|
+
if (message.error) toast.error(text);
|
|
69
|
+
else toast.success(text);
|
|
70
|
+
};
|
|
71
|
+
const pushLabel = pwa.push === "subscribed" ? t("pwa.notifications.disable", {}, "Turn off notifications") : t("pwa.notifications.enable", {}, "Turn on notifications");
|
|
58
72
|
if (!config || !center) return null;
|
|
59
73
|
return /* @__PURE__ */ jsxs(Popover, {
|
|
60
74
|
open,
|
|
@@ -110,21 +124,35 @@ function NotificationsMenu({ variant = "topbar" } = {}) {
|
|
|
110
124
|
children: t("notifications", {}, "Notifications")
|
|
111
125
|
}), /* @__PURE__ */ jsxs("div", {
|
|
112
126
|
className: "flex items-center gap-1",
|
|
113
|
-
children: [
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
children: [
|
|
128
|
+
pushToggle && /* @__PURE__ */ jsx(Button, {
|
|
129
|
+
type: "button",
|
|
130
|
+
variant: "ghost",
|
|
131
|
+
size: "icon-sm",
|
|
132
|
+
"data-slot": "pwa-push",
|
|
133
|
+
disabled: pwa.pushBusy || pwa.push === "denied",
|
|
134
|
+
title: pushLabel,
|
|
135
|
+
"aria-label": pushLabel,
|
|
136
|
+
onClick: () => void togglePush(),
|
|
137
|
+
children: pwa.push === "subscribed" ? /* @__PURE__ */ jsx(BellOffIcon, {}) : /* @__PURE__ */ jsx(BellRingIcon, {})
|
|
138
|
+
}),
|
|
139
|
+
state.unreadCount > 0 && /* @__PURE__ */ jsx(Button, {
|
|
140
|
+
type: "button",
|
|
141
|
+
variant: "ghost",
|
|
142
|
+
size: "icon-sm",
|
|
143
|
+
title: t("mark_all_read", {}, "Mark all as read"),
|
|
144
|
+
onClick: () => void center.markAllAsRead(),
|
|
145
|
+
children: /* @__PURE__ */ jsx(CheckCheckIcon, {})
|
|
146
|
+
}),
|
|
147
|
+
state.notifications.length > 0 && /* @__PURE__ */ jsx(Button, {
|
|
148
|
+
type: "button",
|
|
149
|
+
variant: "ghost",
|
|
150
|
+
size: "icon-sm",
|
|
151
|
+
title: t("clear_notifications", {}, "Clear notifications"),
|
|
152
|
+
onClick: () => void center.clear(),
|
|
153
|
+
children: /* @__PURE__ */ jsx(Trash2Icon, {})
|
|
154
|
+
})
|
|
155
|
+
]
|
|
128
156
|
})]
|
|
129
157
|
}), /* @__PURE__ */ jsxs(ScrollArea, {
|
|
130
158
|
className: "max-h-96",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications-menu.js","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"sourcesContent":["import {\n NotificationCenter,\n notificationTintStyles,\n onDatabaseNotificationsSent,\n onOpenDatabaseNotifications,\n} from '@alfakit/core'\nimport { BellIcon, CheckCheckIcon, Trash2Icon } from 'lucide-react'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useDirection, useFormatter, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cssColor, textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../ui/button'\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'\nimport { ScrollArea } from '../ui/scroll-area'\nimport { Spinner } from '../ui/spinner'\nimport { NotificationActionButton } from './flash-toasts'\n\nexport interface NotificationsMenuProps {\n /**\n * Where the trigger sits: `topbar` (an icon button, default) or `sidebar` (a full-width\n * row with a label, for layouts that move the bell into the sidebar).\n */\n variant?: 'topbar' | 'sidebar'\n}\n\n/**\n * Bell menu for database notifications. It also opens when something calls\n * `openDatabaseNotifications()` (or an action with `->openDatabaseNotifications()`).\n */\nexport function NotificationsMenu({ variant = 'topbar' }: NotificationsMenuProps = {}) {\n const t = useT()\n const direction = useDirection()\n const format = useFormatter()\n const panel = usePanel()\n const config = panel.databaseNotifications\n const endpoints = panel.endpoints\n\n const center = useMemo(\n () =>\n endpoints.notifications && endpoints.notificationsReadAll && endpoints.notificationsDestroyAll\n ? new NotificationCenter(\n {\n notifications: endpoints.notifications,\n notificationsReadAll: endpoints.notificationsReadAll,\n notificationsDestroyAll: endpoints.notificationsDestroyAll,\n },\n config?.unreadCount ?? 0,\n )\n : null,\n [\n endpoints.notifications,\n endpoints.notificationsReadAll,\n endpoints.notificationsDestroyAll,\n config?.unreadCount,\n ],\n )\n\n const [open, setOpen] = useState(false)\n const state = useStore(\n center?.store ?? {\n get: () => ({ notifications: [], unreadCount: 0, loading: false, hasMore: false, page: 0 }),\n subscribe: () => () => {},\n },\n )\n\n useEffect(() => {\n if (!center) return\n\n center.startPolling(config?.pollingInterval)\n\n return () => center.stopPolling()\n }, [center, config?.pollingInterval])\n\n useEffect(() => {\n if (open) void center?.load()\n }, [open, center])\n\n // `openDatabaseNotifications()`: opened from code or an action.\n useEffect(() => onOpenDatabaseNotifications(() => setOpen(true)), [])\n\n // Realtime notifications announce stored ones, so the unread count updates right away.\n useEffect(\n () => (center ? onDatabaseNotificationsSent(() => void center.load()) : undefined),\n [center],\n )\n\n if (!config || !center) {\n return null\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n {variant === 'sidebar' ? (\n // In a collapsed (icon) sidebar only the bell and its badge stay visible.\n <Button\n type=\"button\"\n variant=\"ghost\"\n className=\"relative w-full justify-start gap-2 group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0\"\n data-slot=\"notifications-trigger\"\n data-variant=\"sidebar\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n <span className=\"flex-1 text-start group-data-[collapsible=icon]:sr-only\">\n {t('notifications', {}, 'Notifications')}\n </span>\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground flex h-5 min-w-5 items-center justify-center rounded-full px-1 text-xs font-medium group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:-end-1 group-data-[collapsible=icon]:-top-1 group-data-[collapsible=icon]:h-4 group-data-[collapsible=icon]:min-w-4 group-data-[collapsible=icon]:text-[10px]\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n <span className=\"sr-only\"> {t('unread_notifications', {}, 'unread')}</span>\n </span>\n )}\n </Button>\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative\"\n aria-label={t('notifications', {}, 'Notifications')}\n data-slot=\"notifications-trigger\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground absolute -end-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n </span>\n )}\n </Button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align={variant === 'sidebar' ? 'start' : 'end'}\n side={variant === 'sidebar' ? (direction === 'rtl' ? 'left' : 'right') : 'bottom'}\n className=\"w-80 p-0 sm:w-96\"\n >\n <div className=\"flex items-center justify-between border-b px-3 py-2\">\n <span className=\"text-sm font-semibold\">{t('notifications', {}, 'Notifications')}</span>\n <div className=\"flex items-center gap-1\">\n {state.unreadCount > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('mark_all_read', {}, 'Mark all as read')}\n onClick={() => void center.markAllAsRead()}\n >\n <CheckCheckIcon />\n </Button>\n )}\n {state.notifications.length > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('clear_notifications', {}, 'Clear notifications')}\n onClick={() => void center.clear()}\n >\n <Trash2Icon />\n </Button>\n )}\n </div>\n </div>\n <ScrollArea className=\"max-h-96\">\n {state.loading && state.notifications.length === 0 ? (\n <div className=\"flex justify-center py-8\">\n <Spinner />\n </div>\n ) : state.notifications.length === 0 ? (\n <p className=\"text-muted-foreground px-3 py-8 text-center text-sm\">\n {t('no_notifications', {}, 'You have no notifications.')}\n </p>\n ) : (\n <ul className=\"divide-y\">\n {state.notifications.map((notification) => (\n <li\n key={notification.id}\n className={cn('flex gap-3 px-3 py-3', !notification.readAt && 'bg-primary/5')}\n style={\n notification.color\n ? notificationTintStyles(cssColor(notification.color)).item\n : undefined\n }\n >\n {notification.icon && (\n <Icon\n name={notification.icon}\n className={cn(\n 'mt-0.5 size-5 shrink-0',\n notification.iconColor && textColorClasses[notification.iconColor],\n )}\n />\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-medium\">{notification.title}</p>\n {!notification.readAt && (\n <button\n type=\"button\"\n className=\"focus-visible:ring-ring/50 -me-2 -mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full outline-none focus-visible:ring-[3px]\"\n aria-label={t('mark_as_read', {}, 'Mark as read')}\n onClick={() => void center.markAsRead(notification.id)}\n >\n <span className=\"bg-primary size-2 rounded-full\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n {notification.body && (\n <p\n className=\"text-muted-foreground text-sm\"\n dangerouslySetInnerHTML={{ __html: notification.body }}\n />\n )}\n {notification.date && (\n <time className=\"text-muted-foreground text-xs\">\n {format.relative(notification.date)}\n </time>\n )}\n {notification.actions && (\n <div className=\"flex flex-wrap gap-1\">\n {notification.actions.map((action) => (\n <NotificationActionButton\n key={action.name}\n action={action}\n onMarkAsRead={() => void center.markAsRead(notification.id)}\n onMarkAsUnread={() => void center.markAsUnread(notification.id)}\n />\n ))}\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n {state.hasMore && state.notifications.length > 0 && (\n <div className=\"flex justify-center border-t p-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={state.loading}\n onClick={() => void center.loadMore()}\n >\n {state.loading && <Spinner />}\n {t('load_more', {}, 'Load more')}\n </Button>\n </div>\n )}\n </ScrollArea>\n </PopoverContent>\n </Popover>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,kBAAkB,EAAE,UAAU,aAAqC,CAAC,GAAG;CACrF,MAAM,IAAI,KAAK;CACf,MAAM,YAAY,aAAa;CAC/B,MAAM,SAAS,aAAa;CAC5B,MAAM,QAAQ,SAAS;CACvB,MAAM,SAAS,MAAM;CACrB,MAAM,YAAY,MAAM;CAExB,MAAM,SAAS,cAEX,UAAU,iBAAiB,UAAU,wBAAwB,UAAU,0BACnE,IAAI,mBACF;EACE,eAAe,UAAU;EACzB,sBAAsB,UAAU;EAChC,yBAAyB,UAAU;CACrC,GACA,QAAQ,eAAe,CACzB,IACA,MACN;EACE,UAAU;EACV,UAAU;EACV,UAAU;EACV,QAAQ;CACV,CACF;CAEA,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,QAAQ,SACZ,QAAQ,SAAS;EACf,YAAY;GAAE,eAAe,CAAC;GAAG,aAAa;GAAG,SAAS;GAAO,SAAS;GAAO,MAAM;EAAE;EACzF,uBAAuB,CAAC;CAC1B,CACF;CAEA,gBAAgB;EACd,IAAI,CAAC,QAAQ;EAEb,OAAO,aAAa,QAAQ,eAAe;EAE3C,aAAa,OAAO,YAAY;CAClC,GAAG,CAAC,QAAQ,QAAQ,eAAe,CAAC;CAEpC,gBAAgB;EACd,IAAI,MAAM,QAAa,KAAK;CAC9B,GAAG,CAAC,MAAM,MAAM,CAAC;CAGjB,gBAAgB,kCAAkC,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC;CAGpE,gBACS,SAAS,kCAAkC,KAAK,OAAO,KAAK,CAAC,IAAI,KAAA,GACxE,CAAC,MAAM,CACT;CAEA,IAAI,CAAC,UAAU,CAAC,QACd,OAAO;CAGT,OACE,qBAAC,SAAD;EAAe;EAAM,cAAc;EAAnC,UAAA,CACE,oBAAC,gBAAD;GAAgB,SAAA;GACb,UAAA,YAAY,YAEX,qBAAC,QAAD;IACE,MAAK;IACL,SAAQ;IACR,WAAU;IACV,aAAU;IACV,gBAAa;IALf,UAAA;KAOE,oBAAC,MAAD;MACE,MAAK;MACL,UAAU;KACX,CAAA;KACD,oBAAC,QAAD;MAAM,WAAU;MACb,UAAA,EAAE,iBAAiB,CAAC,GAAG,eAAe;KACnC,CAAA;KACL,MAAM,cAAc,KACnB,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACG,MAAM,cAAc,KAAK,QAAQ,MAAM,aACxC,qBAAC,QAAD;OAAM,WAAU;OAAhB,UAAA,CAA0B,KAAE,EAAE,wBAAwB,CAAC,GAAG,QAAQ,CAAQ;MACtE,CAAA,CAAA;;IAEF;GAER,CAAA,IAAA,qBAAC,QAAD;IACE,MAAK;IACL,SAAQ;IACR,MAAK;IACL,WAAU;IACV,cAAY,EAAE,iBAAiB,CAAC,GAAG,eAAe;IAClD,aAAU;IANZ,UAAA,CAQE,oBAAC,MAAD;KACE,MAAK;KACL,UAAU;IACX,CAAA,GACA,MAAM,cAAc,KACnB,oBAAC,QAAD;KAAM,WAAU;KACb,UAAA,MAAM,cAAc,KAAK,QAAQ,MAAM;IACpC,CAAA,CAEF;;EAEI,CAAA,GAChB,qBAAC,gBAAD;GACE,OAAO,YAAY,YAAY,UAAU;GACzC,MAAM,YAAY,YAAa,cAAc,QAAQ,SAAS,UAAW;GACzE,WAAU;GAHZ,UAAA,CAKE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,QAAD;KAAM,WAAU;KAAyB,UAAA,EAAE,iBAAiB,CAAC,GAAG,eAAe;IAAQ,CAAA,GACvF,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,MAAM,cAAc,KACnB,oBAAC,QAAD;MACE,MAAK;MACL,SAAQ;MACR,MAAK;MACL,OAAO,EAAE,iBAAiB,CAAC,GAAG,kBAAkB;MAChD,eAAe,KAAK,OAAO,cAAc;MAEzC,UAAA,oBAAC,gBAAD,CAAiB,CAAA;KACX,CAAA,GAET,MAAM,cAAc,SAAS,KAC5B,oBAAC,QAAD;MACE,MAAK;MACL,SAAQ;MACR,MAAK;MACL,OAAO,EAAE,uBAAuB,CAAC,GAAG,qBAAqB;MACzD,eAAe,KAAK,OAAO,MAAM;MAEjC,UAAA,oBAAC,YAAD,CAAa,CAAA;KACP,CAAA,CAEP;IACF,CAAA,CAAA;GACL,CAAA,GAAA,qBAAC,YAAD;IAAY,WAAU;IAAtB,UAAA,CACG,MAAM,WAAW,MAAM,cAAc,WAAW,IAC/C,oBAAC,OAAD;KAAK,WAAU;KACb,UAAA,oBAAC,SAAD,CAAU,CAAA;IACP,CAAA,IACH,MAAM,cAAc,WAAW,IACjC,oBAAC,KAAD;KAAG,WAAU;KACV,UAAA,EAAE,oBAAoB,CAAC,GAAG,4BAA4B;IACtD,CAAA,IAEH,oBAAC,MAAD;KAAI,WAAU;KACX,UAAA,MAAM,cAAc,KAAK,iBACxB,qBAAC,MAAD;MAEE,WAAW,GAAG,wBAAwB,CAAC,aAAa,UAAU,cAAc;MAC5E,OACE,aAAa,QACT,uBAAuB,SAAS,aAAa,KAAK,CAAC,CAAC,CAAC,OACrD,KAAA;MANR,UAAA,CASG,aAAa,QACZ,oBAAC,MAAD;OACE,MAAM,aAAa;OACnB,WAAW,GACT,0BACA,aAAa,aAAa,iBAAiB,aAAa,UAC1D;MACD,CAAA,GAEH,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA;QACE,qBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,oBAAC,KAAD;UAAG,WAAU;UAAuB,UAAA,aAAa;SAAS,CAAA,GACzD,CAAC,aAAa,UACb,oBAAC,UAAD;UACE,MAAK;UACL,WAAU;UACV,cAAY,EAAE,gBAAgB,CAAC,GAAG,cAAc;UAChD,eAAe,KAAK,OAAO,WAAW,aAAa,EAAE;UAErD,UAAA,oBAAC,QAAD;WAAM,WAAU;WAAiC,eAAY;UAAQ,CAAA;SAC/D,CAAA,CAEP;;QACJ,aAAa,QACZ,oBAAC,KAAD;SACE,WAAU;SACV,yBAAyB,EAAE,QAAQ,aAAa,KAAK;QACtD,CAAA;QAEF,aAAa,QACZ,oBAAC,QAAD;SAAM,WAAU;SACb,UAAA,OAAO,SAAS,aAAa,IAAI;QAC9B,CAAA;QAEP,aAAa,WACZ,oBAAC,OAAD;SAAK,WAAU;SACZ,UAAA,aAAa,QAAQ,KAAK,WACzB,oBAAC,0BAAD;UAEU;UACR,oBAAoB,KAAK,OAAO,WAAW,aAAa,EAAE;UAC1D,sBAAsB,KAAK,OAAO,aAAa,aAAa,EAAE;SAC/D,GAJM,OAAO,IAIb,CACF;QACE,CAAA;OAEJ;MACH,CAAA,CAAA;KAvDG,GAAA,aAAa,EAuDhB,CACL;IACC,CAAA,GAEL,MAAM,WAAW,MAAM,cAAc,SAAS,KAC7C,oBAAC,OAAD;KAAK,WAAU;KACb,UAAA,qBAAC,QAAD;MACE,MAAK;MACL,SAAQ;MACR,MAAK;MACL,UAAU,MAAM;MAChB,eAAe,KAAK,OAAO,SAAS;MALtC,UAAA,CAOG,MAAM,WAAW,oBAAC,SAAD,CAAU,CAAA,GAC3B,EAAE,aAAa,CAAC,GAAG,WAAW,CACzB;;IACL,CAAA,CAEG;GACE,CAAA,CAAA;EACT,CAAA,CAAA;;AAEb"}
|
|
1
|
+
{"version":3,"file":"notifications-menu.js","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"sourcesContent":["import {\n NotificationCenter,\n notificationTintStyles,\n onDatabaseNotificationsSent,\n onOpenDatabaseNotifications,\n pwaStore,\n startPwa,\n togglePushNotifications,\n} from '@alfakit/core'\nimport { BellIcon, BellOffIcon, BellRingIcon, CheckCheckIcon, Trash2Icon } from 'lucide-react'\nimport { useEffect, useMemo, useState } from 'react'\nimport { toast } from 'sonner'\nimport { useDirection, useFormatter, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cssColor, textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../ui/button'\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'\nimport { ScrollArea } from '../ui/scroll-area'\nimport { Spinner } from '../ui/spinner'\nimport { NotificationActionButton } from './flash-toasts'\n\nexport interface NotificationsMenuProps {\n /**\n * Where the trigger sits: `topbar` (an icon button, default) or `sidebar` (a full-width\n * row with a label, for layouts that move the bell into the sidebar).\n */\n variant?: 'topbar' | 'sidebar'\n}\n\n/**\n * Bell menu for database notifications. It also opens when something calls\n * `openDatabaseNotifications()` (or an action with `->openDatabaseNotifications()`).\n */\nexport function NotificationsMenu({ variant = 'topbar' }: NotificationsMenuProps = {}) {\n const t = useT()\n const direction = useDirection()\n const format = useFormatter()\n const panel = usePanel()\n const config = panel.databaseNotifications\n const endpoints = panel.endpoints\n\n const center = useMemo(\n () =>\n endpoints.notifications && endpoints.notificationsReadAll && endpoints.notificationsDestroyAll\n ? new NotificationCenter(\n {\n notifications: endpoints.notifications,\n notificationsReadAll: endpoints.notificationsReadAll,\n notificationsDestroyAll: endpoints.notificationsDestroyAll,\n },\n config?.unreadCount ?? 0,\n )\n : null,\n [\n endpoints.notifications,\n endpoints.notificationsReadAll,\n endpoints.notificationsDestroyAll,\n config?.unreadCount,\n ],\n )\n\n const [open, setOpen] = useState(false)\n const state = useStore(\n center?.store ?? {\n get: () => ({ notifications: [], unreadCount: 0, loading: false, hasMore: false, page: 0 }),\n subscribe: () => () => {},\n },\n )\n\n useEffect(() => {\n if (!center) return\n\n center.startPolling(config?.pollingInterval)\n\n return () => center.stopPolling()\n }, [center, config?.pollingInterval])\n\n useEffect(() => {\n if (open) void center?.load()\n }, [open, center])\n\n // `openDatabaseNotifications()`: opened from code or an action.\n useEffect(() => onOpenDatabaseNotifications(() => setOpen(true)), [])\n\n // Realtime notifications announce stored ones, so the unread count updates right away.\n useEffect(\n () => (center ? onDatabaseNotificationsSent(() => void center.load()) : undefined),\n [center],\n )\n\n // `pwa(push: true)` in a starter kit: the app's user menu has no AlfaKit entries, so Web\n // Push is turned on or off from the bell's menu.\n const pwa = useStore(pwaStore)\n const kitPush = Boolean(panel.starterKit && panel.pwa?.push)\n const pushToggle = kitPush && pwa.push !== 'unsupported'\n\n useEffect(() => {\n if (kitPush) startPwa(panel.pwa)\n }, [kitPush, panel.pwa])\n\n const togglePush = async () => {\n const { message } = await togglePushNotifications()\n const text = t(message.key, {}, message.fallback)\n\n if (message.error) toast.error(text)\n else toast.success(text)\n }\n\n const pushLabel =\n pwa.push === 'subscribed'\n ? t('pwa.notifications.disable', {}, 'Turn off notifications')\n : t('pwa.notifications.enable', {}, 'Turn on notifications')\n\n if (!config || !center) {\n return null\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n {variant === 'sidebar' ? (\n // In a collapsed (icon) sidebar only the bell and its badge stay visible.\n <Button\n type=\"button\"\n variant=\"ghost\"\n className=\"relative w-full justify-start gap-2 group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0\"\n data-slot=\"notifications-trigger\"\n data-variant=\"sidebar\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n <span className=\"flex-1 text-start group-data-[collapsible=icon]:sr-only\">\n {t('notifications', {}, 'Notifications')}\n </span>\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground flex h-5 min-w-5 items-center justify-center rounded-full px-1 text-xs font-medium group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:-end-1 group-data-[collapsible=icon]:-top-1 group-data-[collapsible=icon]:h-4 group-data-[collapsible=icon]:min-w-4 group-data-[collapsible=icon]:text-[10px]\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n <span className=\"sr-only\"> {t('unread_notifications', {}, 'unread')}</span>\n </span>\n )}\n </Button>\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative\"\n aria-label={t('notifications', {}, 'Notifications')}\n data-slot=\"notifications-trigger\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground absolute -end-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n </span>\n )}\n </Button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align={variant === 'sidebar' ? 'start' : 'end'}\n side={variant === 'sidebar' ? (direction === 'rtl' ? 'left' : 'right') : 'bottom'}\n className=\"w-80 p-0 sm:w-96\"\n >\n <div className=\"flex items-center justify-between border-b px-3 py-2\">\n <span className=\"text-sm font-semibold\">{t('notifications', {}, 'Notifications')}</span>\n <div className=\"flex items-center gap-1\">\n {pushToggle && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n data-slot=\"pwa-push\"\n disabled={pwa.pushBusy || pwa.push === 'denied'}\n title={pushLabel}\n aria-label={pushLabel}\n onClick={() => void togglePush()}\n >\n {pwa.push === 'subscribed' ? <BellOffIcon /> : <BellRingIcon />}\n </Button>\n )}\n {state.unreadCount > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('mark_all_read', {}, 'Mark all as read')}\n onClick={() => void center.markAllAsRead()}\n >\n <CheckCheckIcon />\n </Button>\n )}\n {state.notifications.length > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('clear_notifications', {}, 'Clear notifications')}\n onClick={() => void center.clear()}\n >\n <Trash2Icon />\n </Button>\n )}\n </div>\n </div>\n <ScrollArea className=\"max-h-96\">\n {state.loading && state.notifications.length === 0 ? (\n <div className=\"flex justify-center py-8\">\n <Spinner />\n </div>\n ) : state.notifications.length === 0 ? (\n <p className=\"text-muted-foreground px-3 py-8 text-center text-sm\">\n {t('no_notifications', {}, 'You have no notifications.')}\n </p>\n ) : (\n <ul className=\"divide-y\">\n {state.notifications.map((notification) => (\n <li\n key={notification.id}\n className={cn('flex gap-3 px-3 py-3', !notification.readAt && 'bg-primary/5')}\n style={\n notification.color\n ? notificationTintStyles(cssColor(notification.color)).item\n : undefined\n }\n >\n {notification.icon && (\n <Icon\n name={notification.icon}\n className={cn(\n 'mt-0.5 size-5 shrink-0',\n notification.iconColor && textColorClasses[notification.iconColor],\n )}\n />\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-medium\">{notification.title}</p>\n {!notification.readAt && (\n <button\n type=\"button\"\n className=\"focus-visible:ring-ring/50 -me-2 -mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full outline-none focus-visible:ring-[3px]\"\n aria-label={t('mark_as_read', {}, 'Mark as read')}\n onClick={() => void center.markAsRead(notification.id)}\n >\n <span className=\"bg-primary size-2 rounded-full\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n {notification.body && (\n <p\n className=\"text-muted-foreground text-sm\"\n dangerouslySetInnerHTML={{ __html: notification.body }}\n />\n )}\n {notification.date && (\n <time className=\"text-muted-foreground text-xs\">\n {format.relative(notification.date)}\n </time>\n )}\n {notification.actions && (\n <div className=\"flex flex-wrap gap-1\">\n {notification.actions.map((action) => (\n <NotificationActionButton\n key={action.name}\n action={action}\n onMarkAsRead={() => void center.markAsRead(notification.id)}\n onMarkAsUnread={() => void center.markAsUnread(notification.id)}\n />\n ))}\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n {state.hasMore && state.notifications.length > 0 && (\n <div className=\"flex justify-center border-t p-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={state.loading}\n onClick={() => void center.loadMore()}\n >\n {state.loading && <Spinner />}\n {t('load_more', {}, 'Load more')}\n </Button>\n </div>\n )}\n </ScrollArea>\n </PopoverContent>\n </Popover>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-placeholder.d.ts","names":[],"sources":["../../../src/components/panel/lazy-placeholder.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"lazy-placeholder.d.ts","names":[],"sources":["../../../src/components/panel/lazy-placeholder.tsx"],"mappings":";;;;;;wBAWgB,kBACd,SACA;EAEA;EACA;oBACD,IAAA"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { useT } from "../../context.js";
|
|
1
|
+
import { useRefreshComponents, useT } from "../../context.js";
|
|
2
2
|
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { Button } from "../ui/button.js";
|
|
3
4
|
import { Skeleton } from "../ui/skeleton.js";
|
|
4
|
-
import {
|
|
5
|
+
import { widgetPollTarget } from "@alfakit/core";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
8
|
//#region src/components/panel/lazy-placeholder.tsx
|
|
6
9
|
/**
|
|
7
10
|
* Stands in for a lazy widget or relation manager until the `alfakitLazy` deferred prop
|
|
@@ -21,7 +24,7 @@ function LazyPlaceholder({ variant = "widget", className }) {
|
|
|
21
24
|
children: t("loading", {}, "Loading…")
|
|
22
25
|
}),
|
|
23
26
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-1/3" }),
|
|
24
|
-
variant === "table" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27
|
+
variant === "table" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
25
28
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }),
|
|
26
29
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }),
|
|
27
30
|
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" })
|
|
@@ -29,7 +32,38 @@ function LazyPlaceholder({ variant = "widget", className }) {
|
|
|
29
32
|
]
|
|
30
33
|
});
|
|
31
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* A lazy widget's cell: the skeleton while its schema loads, or, when the request failed
|
|
37
|
+
* (outside panels: a refused or failed fetch), a message with a retry.
|
|
38
|
+
*/
|
|
39
|
+
function LazyWidgetPlaceholder({ widget }) {
|
|
40
|
+
const t = useT();
|
|
41
|
+
const refresh = useRefreshComponents();
|
|
42
|
+
const [retrying, setRetrying] = useState(false);
|
|
43
|
+
const variant = widget.type === "TableWidget" ? "table" : "widget";
|
|
44
|
+
if (!widget.failed) return /* @__PURE__ */ jsx(LazyPlaceholder, { variant });
|
|
45
|
+
const retry = () => {
|
|
46
|
+
if (!refresh) return;
|
|
47
|
+
setRetrying(true);
|
|
48
|
+
Promise.resolve(refresh([widgetPollTarget(widget)])).finally(() => setRetrying(false));
|
|
49
|
+
};
|
|
50
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
51
|
+
"data-slot": "lazy-placeholder",
|
|
52
|
+
"data-variant": variant,
|
|
53
|
+
"data-failed": "",
|
|
54
|
+
role: "alert",
|
|
55
|
+
className: "bg-card text-muted-foreground flex min-h-28 flex-col items-center justify-center gap-3 rounded-xl border border-dashed p-6 text-center text-sm",
|
|
56
|
+
children: [/* @__PURE__ */ jsx("p", { children: t("widget_failed", {}, "This widget could not be loaded.") }), refresh && /* @__PURE__ */ jsx(Button, {
|
|
57
|
+
type: "button",
|
|
58
|
+
variant: "outline",
|
|
59
|
+
size: "sm",
|
|
60
|
+
disabled: retrying,
|
|
61
|
+
onClick: retry,
|
|
62
|
+
children: t("retry", {}, "Retry")
|
|
63
|
+
})]
|
|
64
|
+
});
|
|
65
|
+
}
|
|
32
66
|
//#endregion
|
|
33
|
-
export { LazyPlaceholder };
|
|
67
|
+
export { LazyPlaceholder, LazyWidgetPlaceholder };
|
|
34
68
|
|
|
35
69
|
//# sourceMappingURL=lazy-placeholder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-placeholder.js","names":[],"sources":["../../../src/components/panel/lazy-placeholder.tsx"],"sourcesContent":["import { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { Skeleton } from '../ui/skeleton'\n\n/**\n * Stands in for a lazy widget or relation manager until the `alfakitLazy` deferred prop\n * brings its schema. `table` mimics a table (relation managers, table widgets).\n */\nexport function LazyPlaceholder({\n variant = 'widget',\n className,\n}: {\n variant?: 'widget' | 'table'\n className?: string\n}) {\n const t = useT()\n\n return (\n <div\n data-slot=\"lazy-placeholder\"\n data-variant={variant}\n role=\"status\"\n aria-busy=\"true\"\n className={cn('bg-card flex flex-col gap-3 rounded-xl border p-6', className)}\n >\n <span className=\"sr-only\">{t('loading', {}, 'Loading…')}</span>\n <Skeleton className=\"h-5 w-1/3\" />\n {variant === 'table' ? (\n <>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-9 w-full\" />\n </>\n ) : (\n <Skeleton className=\"h-20 w-full\" />\n )}\n </div>\n )\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"lazy-placeholder.js","names":[],"sources":["../../../src/components/panel/lazy-placeholder.tsx"],"sourcesContent":["import { widgetPollTarget, type LazyWidgetSchema } from '@alfakit/core'\nimport { useState } from 'react'\nimport { useRefreshComponents, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../ui/button'\nimport { Skeleton } from '../ui/skeleton'\n\n/**\n * Stands in for a lazy widget or relation manager until the `alfakitLazy` deferred prop\n * brings its schema. `table` mimics a table (relation managers, table widgets).\n */\nexport function LazyPlaceholder({\n variant = 'widget',\n className,\n}: {\n variant?: 'widget' | 'table'\n className?: string\n}) {\n const t = useT()\n\n return (\n <div\n data-slot=\"lazy-placeholder\"\n data-variant={variant}\n role=\"status\"\n aria-busy=\"true\"\n className={cn('bg-card flex flex-col gap-3 rounded-xl border p-6', className)}\n >\n <span className=\"sr-only\">{t('loading', {}, 'Loading…')}</span>\n <Skeleton className=\"h-5 w-1/3\" />\n {variant === 'table' ? (\n <>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-9 w-full\" />\n </>\n ) : (\n <Skeleton className=\"h-20 w-full\" />\n )}\n </div>\n )\n}\n\n/**\n * A lazy widget's cell: the skeleton while its schema loads, or, when the request failed\n * (outside panels: a refused or failed fetch), a message with a retry.\n */\nexport function LazyWidgetPlaceholder({ widget }: { widget: LazyWidgetSchema }) {\n const t = useT()\n const refresh = useRefreshComponents()\n const [retrying, setRetrying] = useState(false)\n const variant = widget.type === 'TableWidget' ? 'table' : 'widget'\n\n if (!widget.failed) return <LazyPlaceholder variant={variant} />\n\n const retry = () => {\n if (!refresh) return\n\n setRetrying(true)\n void Promise.resolve(refresh([widgetPollTarget(widget)])).finally(() => setRetrying(false))\n }\n\n return (\n <div\n data-slot=\"lazy-placeholder\"\n data-variant={variant}\n data-failed=\"\"\n role=\"alert\"\n className=\"bg-card text-muted-foreground flex min-h-28 flex-col items-center justify-center gap-3 rounded-xl border border-dashed p-6 text-center text-sm\"\n >\n <p>{t('widget_failed', {}, 'This widget could not be loaded.')}</p>\n {refresh && (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={retrying} onClick={retry}>\n {t('retry', {}, 'Retry')}\n </Button>\n )}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAWA,SAAgB,gBAAgB,EAC9B,UAAU,UACV,aAIC;CACD,MAAM,IAAI,KAAK;CAEf,OACE,qBAAC,OAAD;EACE,aAAU;EACV,gBAAc;EACd,MAAK;EACL,aAAU;EACV,WAAW,GAAG,qDAAqD,SAAS;EAL9E,UAAA;GAOE,oBAAC,QAAD;IAAM,WAAU;IAAW,UAAA,EAAE,WAAW,CAAC,GAAG,UAAU;GAAQ,CAAA;GAC9D,oBAAC,UAAD,EAAU,WAAU,YAAa,CAAA;GAChC,YAAY,UACX,qBAAA,YAAA,EAAA,UAAA;IACE,oBAAC,UAAD,EAAU,WAAU,aAAc,CAAA;IAClC,oBAAC,UAAD,EAAU,WAAU,aAAc,CAAA;IAClC,oBAAC,UAAD,EAAU,WAAU,aAAc,CAAA;GAClC,EAAA,CAAA,IAEF,oBAAC,UAAD,EAAU,WAAU,cAAe,CAAA;EAElC;;AAET;;;;;AAMA,SAAgB,sBAAsB,EAAE,UAAwC;CAC9E,MAAM,IAAI,KAAK;CACf,MAAM,UAAU,qBAAqB;CACrC,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,UAAU,OAAO,SAAS,gBAAgB,UAAU;CAE1D,IAAI,CAAC,OAAO,QAAQ,OAAO,oBAAC,iBAAD,EAA0B,QAAU,CAAA;CAE/D,MAAM,cAAc;EAClB,IAAI,CAAC,SAAS;EAEd,YAAY,IAAI;EAChB,QAAa,QAAQ,QAAQ,CAAC,iBAAiB,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,YAAY,KAAK,CAAC;CAC5F;CAEA,OACE,qBAAC,OAAD;EACE,aAAU;EACV,gBAAc;EACd,eAAY;EACZ,MAAK;EACL,WAAU;EALZ,UAAA,CAOE,oBAAC,KAAD,EAAA,UAAI,EAAE,iBAAiB,CAAC,GAAG,kCAAkC,EAAK,CAAA,GACjE,WACC,oBAAC,QAAD;GAAQ,MAAK;GAAS,SAAQ;GAAU,MAAK;GAAK,UAAU;GAAU,SAAS;GAC5E,UAAA,EAAE,SAAS,CAAC,GAAG,OAAO;EACjB,CAAA,CAEP;;AAET"}
|
|
@@ -2,7 +2,7 @@ import { usePageSchema, useT } from "../../context.js";
|
|
|
2
2
|
import { Button } from "../ui/button.js";
|
|
3
3
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../ui/dialog.js";
|
|
4
4
|
import { ShortcutKeys } from "./shortcut-keys.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ShortcutRecorder, flattenActionGroup, saveShortcutOverrides, shortcutOverlayGroups, shortcuts } from "@alfakit/core";
|
|
6
6
|
import { useEffect, useState, useSyncExternalStore } from "react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/panel/shortcuts-overlay.tsx
|
|
@@ -31,26 +31,29 @@ function ShortcutsOverlay({ open, onOpenChange, customizable, endpoint }) {
|
|
|
31
31
|
useEffect(() => {
|
|
32
32
|
if (!recording) return;
|
|
33
33
|
shortcuts.setPaused(true);
|
|
34
|
+
const recorder = new ShortcutRecorder({ onDone: (binding) => {
|
|
35
|
+
setRecording(null);
|
|
36
|
+
const conflict = shortcuts.conflict([binding], recording);
|
|
37
|
+
if (conflict) {
|
|
38
|
+
setMessage(t("shortcuts.conflict", { label: conflict.label }, `Already used by “${conflict.label}”.`));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
save(recording, [binding]);
|
|
42
|
+
} });
|
|
34
43
|
const listener = (event) => {
|
|
35
44
|
event.preventDefault();
|
|
36
45
|
event.stopPropagation();
|
|
37
46
|
if (event.key === "Escape") {
|
|
47
|
+
recorder.cancel();
|
|
38
48
|
setRecording(null);
|
|
39
49
|
setMessage(t("shortcuts.cancelled", {}, "Change cancelled."));
|
|
40
50
|
return;
|
|
41
51
|
}
|
|
42
|
-
|
|
43
|
-
if (!binding) return;
|
|
44
|
-
const conflict = shortcuts.conflict([binding], recording);
|
|
45
|
-
setRecording(null);
|
|
46
|
-
if (conflict) {
|
|
47
|
-
setMessage(t("shortcuts.conflict", { label: conflict.label }, `Already used by “${conflict.label}”.`));
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
save(recording, [binding]);
|
|
52
|
+
recorder.press(event);
|
|
51
53
|
};
|
|
52
54
|
window.addEventListener("keydown", listener, true);
|
|
53
55
|
return () => {
|
|
56
|
+
recorder.cancel();
|
|
54
57
|
window.removeEventListener("keydown", listener, true);
|
|
55
58
|
shortcuts.setPaused(false);
|
|
56
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shortcuts-overlay.js","names":[],"sources":["../../../src/components/panel/shortcuts-overlay.tsx"],"sourcesContent":["import type { ShortcutListGroup, ShortcutListItem } from '@alfakit/core'\nimport {\n bindingFromEvent,\n flattenActionGroup,\n saveShortcutOverrides,\n shortcutOverlayGroups,\n shortcuts,\n} from '@alfakit/core'\nimport { useEffect, useState, useSyncExternalStore } from 'react'\nimport { usePageSchema, useT } from '../../context'\nimport { Button } from '../ui/button'\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/dialog'\nimport { ShortcutKeys } from './shortcut-keys'\n\nconst subscribe = (listener: () => void) => shortcuts.subscribe(listener)\nconst version = () => shortcuts.getVersion()\n\n/**\n * The `?` overlay: every shortcut available on the page, grouped; users may change their\n * keys (stored on the server). `KeyboardShortcuts` loads it the first time it opens.\n */\nexport function ShortcutsOverlay({\n open,\n onOpenChange,\n customizable,\n endpoint,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n customizable: boolean\n endpoint?: string\n}) {\n const t = useT()\n const page = usePageSchema()\n useSyncExternalStore(subscribe, version, version)\n const [recording, setRecording] = useState<string | null>(null)\n const [message, setMessage] = useState('')\n\n const groups = shortcutOverlayGroups(\n shortcuts.list(),\n flattenActionGroup(page?.headerActions ?? []),\n )\n\n const save = async (id: string, keys: string[] | null) => {\n if (!endpoint) return\n\n try {\n shortcuts.setOverrides(await saveShortcutOverrides(endpoint, { [id]: keys }))\n setMessage(t('shortcuts.saved', {}, 'Shortcut saved.'))\n } catch {\n setMessage(t('shortcuts.save_failed', {}, 'The shortcut could not be saved.'))\n }\n }\n\n // While recording, the next key press is the new binding (Escape cancels). Listening in the\n // capture phase keeps the key from closing the dialog or running another shortcut.\n useEffect(() => {\n if (!recording) return\n\n shortcuts.setPaused(true)\n\n const listener = (event: KeyboardEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n if (event.key === 'Escape') {\n setRecording(null)\n setMessage(t('shortcuts.cancelled', {}, 'Change cancelled.'))\n\n return\n }\n\n const binding = bindingFromEvent(event)\n\n if (!binding) return\n\n const conflict = shortcuts.conflict([binding], recording)\n\n setRecording(null)\n\n if (conflict) {\n setMessage(\n t(\n 'shortcuts.conflict',\n { label: conflict.label },\n `Already used by “${conflict.label}”.`,\n ),\n )\n\n return\n }\n\n void save(recording, [binding])\n }\n\n window.addEventListener('keydown', listener, true)\n\n return () => {\n window.removeEventListener('keydown', listener, true)\n shortcuts.setPaused(false)\n }\n // `save` and `t` only change with the endpoint and the locale.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [recording])\n\n const groupLabel = (group: ShortcutListGroup['group']) =>\n ({\n general: t('shortcuts.groups.general', {}, 'General'),\n navigation: t('shortcuts.groups.navigation', {}, 'Navigation'),\n commands: t('shortcuts.groups.commands', {}, 'Commands'),\n actions: t('shortcuts.groups.actions', {}, 'Actions on this page'),\n })[group]\n\n const change = (item: ShortcutListItem) => {\n setMessage(\n t(\n 'shortcuts.recording',\n { label: item.label },\n `Press the new keys for “${item.label}”. Escape cancels.`,\n ),\n )\n setRecording(item.id)\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n setRecording(null)\n setMessage('')\n onOpenChange(next)\n }}\n >\n <DialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-lg\">\n <div data-slot=\"shortcuts-overlay\" className=\"flex flex-col gap-4\">\n <DialogHeader>\n <DialogTitle>{t('shortcuts.title', {}, 'Keyboard shortcuts')}</DialogTitle>\n <DialogDescription>\n {t('shortcuts.description', {}, 'Shortcuts available on this page.')}\n </DialogDescription>\n </DialogHeader>\n <div role=\"status\" aria-live=\"polite\" className=\"text-muted-foreground min-h-5 text-sm\">\n {message}\n </div>\n <div className=\"flex flex-col gap-5\">\n {groups.map((group) => (\n <section\n key={group.group}\n data-group={group.group}\n aria-labelledby={`shortcuts-${group.group}`}\n >\n <h3\n id={`shortcuts-${group.group}`}\n className=\"text-muted-foreground mb-2 text-xs font-medium uppercase\"\n >\n {groupLabel(group.group)}\n </h3>\n <ul className=\"flex flex-col divide-y rounded-md border\">\n {group.items.map((item) => (\n <li\n key={item.id}\n data-shortcut={item.id}\n className=\"flex items-center gap-3 px-3 py-2 text-sm\"\n >\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {recording === item.id ? (\n <span className=\"text-primary text-xs\">\n {t('shortcuts.press_keys', {}, 'Press keys…')}\n </span>\n ) : item.keys.length === 0 ? (\n <span className=\"text-muted-foreground text-xs\">\n {t('shortcuts.off', {}, 'Off')}\n </span>\n ) : (\n <span className=\"flex flex-wrap items-center justify-end gap-2\">\n {item.keys.map((key) => (\n <ShortcutKeys key={key} binding={key} />\n ))}\n </span>\n )}\n {customizable && item.customizable && (\n <span className=\"flex shrink-0 gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t(\n 'shortcuts.change_label',\n { label: item.label },\n `Change the shortcut for ${item.label}`,\n )}\n onClick={() => change(item)}\n >\n {t('shortcuts.change', {}, 'Change')}\n </Button>\n {item.keys.length > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t(\n 'shortcuts.turn_off_label',\n { label: item.label },\n `Turn off the shortcut for ${item.label}`,\n )}\n onClick={() => void save(item.id, [])}\n >\n {t('shortcuts.turn_off', {}, 'Turn off')}\n </Button>\n )}\n {item.customized && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t(\n 'shortcuts.reset_label',\n { label: item.label },\n `Restore the default shortcut for ${item.label}`,\n )}\n onClick={() => void save(item.id, null)}\n >\n {t('shortcuts.reset', {}, 'Reset')}\n </Button>\n )}\n </span>\n )}\n </li>\n ))}\n </ul>\n </section>\n ))}\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shortcuts-overlay.js","names":[],"sources":["../../../src/components/panel/shortcuts-overlay.tsx"],"sourcesContent":["import type { ShortcutListGroup, ShortcutListItem } from '@alfakit/core'\nimport {\n ShortcutRecorder,\n flattenActionGroup,\n saveShortcutOverrides,\n shortcutOverlayGroups,\n shortcuts,\n} from '@alfakit/core'\nimport { useEffect, useState, useSyncExternalStore } from 'react'\nimport { usePageSchema, useT } from '../../context'\nimport { Button } from '../ui/button'\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/dialog'\nimport { ShortcutKeys } from './shortcut-keys'\n\nconst subscribe = (listener: () => void) => shortcuts.subscribe(listener)\nconst version = () => shortcuts.getVersion()\n\n/**\n * The `?` overlay: every shortcut available on the page, grouped; users may change their\n * keys (stored on the server). `KeyboardShortcuts` loads it the first time it opens.\n */\nexport function ShortcutsOverlay({\n open,\n onOpenChange,\n customizable,\n endpoint,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n customizable: boolean\n endpoint?: string\n}) {\n const t = useT()\n const page = usePageSchema()\n useSyncExternalStore(subscribe, version, version)\n const [recording, setRecording] = useState<string | null>(null)\n const [message, setMessage] = useState('')\n\n const groups = shortcutOverlayGroups(\n shortcuts.list(),\n flattenActionGroup(page?.headerActions ?? []),\n )\n\n const save = async (id: string, keys: string[] | null) => {\n if (!endpoint) return\n\n try {\n shortcuts.setOverrides(await saveShortcutOverrides(endpoint, { [id]: keys }))\n setMessage(t('shortcuts.saved', {}, 'Shortcut saved.'))\n } catch {\n setMessage(t('shortcuts.save_failed', {}, 'The shortcut could not be saved.'))\n }\n }\n\n // While recording, the keys pressed are the new binding: one key (with modifiers) or a\n // sequence (\"g o\"), finished a second after the last key (Escape cancels). Listening in\n // the capture phase keeps the keys from closing the dialog or running another shortcut.\n useEffect(() => {\n if (!recording) return\n\n shortcuts.setPaused(true)\n\n const recorder = new ShortcutRecorder({\n onDone: (binding) => {\n setRecording(null)\n\n const conflict = shortcuts.conflict([binding], recording)\n\n if (conflict) {\n setMessage(\n t(\n 'shortcuts.conflict',\n { label: conflict.label },\n `Already used by “${conflict.label}”.`,\n ),\n )\n\n return\n }\n\n void save(recording, [binding])\n },\n })\n\n const listener = (event: KeyboardEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n if (event.key === 'Escape') {\n recorder.cancel()\n setRecording(null)\n setMessage(t('shortcuts.cancelled', {}, 'Change cancelled.'))\n\n return\n }\n\n recorder.press(event)\n }\n\n window.addEventListener('keydown', listener, true)\n\n return () => {\n recorder.cancel()\n window.removeEventListener('keydown', listener, true)\n shortcuts.setPaused(false)\n }\n // `save` and `t` only change with the endpoint and the locale.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [recording])\n\n const groupLabel = (group: ShortcutListGroup['group']) =>\n ({\n general: t('shortcuts.groups.general', {}, 'General'),\n navigation: t('shortcuts.groups.navigation', {}, 'Navigation'),\n commands: t('shortcuts.groups.commands', {}, 'Commands'),\n actions: t('shortcuts.groups.actions', {}, 'Actions on this page'),\n })[group]\n\n const change = (item: ShortcutListItem) => {\n setMessage(\n t(\n 'shortcuts.recording',\n { label: item.label },\n `Press the new keys for “${item.label}”. Escape cancels.`,\n ),\n )\n setRecording(item.id)\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n setRecording(null)\n setMessage('')\n onOpenChange(next)\n }}\n >\n <DialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-lg\">\n <div data-slot=\"shortcuts-overlay\" className=\"flex flex-col gap-4\">\n <DialogHeader>\n <DialogTitle>{t('shortcuts.title', {}, 'Keyboard shortcuts')}</DialogTitle>\n <DialogDescription>\n {t('shortcuts.description', {}, 'Shortcuts available on this page.')}\n </DialogDescription>\n </DialogHeader>\n <div role=\"status\" aria-live=\"polite\" className=\"text-muted-foreground min-h-5 text-sm\">\n {message}\n </div>\n <div className=\"flex flex-col gap-5\">\n {groups.map((group) => (\n <section\n key={group.group}\n data-group={group.group}\n aria-labelledby={`shortcuts-${group.group}`}\n >\n <h3\n id={`shortcuts-${group.group}`}\n className=\"text-muted-foreground mb-2 text-xs font-medium uppercase\"\n >\n {groupLabel(group.group)}\n </h3>\n <ul className=\"flex flex-col divide-y rounded-md border\">\n {group.items.map((item) => (\n <li\n key={item.id}\n data-shortcut={item.id}\n className=\"flex items-center gap-3 px-3 py-2 text-sm\"\n >\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {recording === item.id ? (\n <span className=\"text-primary text-xs\">\n {t('shortcuts.press_keys', {}, 'Press keys…')}\n </span>\n ) : item.keys.length === 0 ? (\n <span className=\"text-muted-foreground text-xs\">\n {t('shortcuts.off', {}, 'Off')}\n </span>\n ) : (\n <span className=\"flex flex-wrap items-center justify-end gap-2\">\n {item.keys.map((key) => (\n <ShortcutKeys key={key} binding={key} />\n ))}\n </span>\n )}\n {customizable && item.customizable && (\n <span className=\"flex shrink-0 gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t(\n 'shortcuts.change_label',\n { label: item.label },\n `Change the shortcut for ${item.label}`,\n )}\n onClick={() => change(item)}\n >\n {t('shortcuts.change', {}, 'Change')}\n </Button>\n {item.keys.length > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t(\n 'shortcuts.turn_off_label',\n { label: item.label },\n `Turn off the shortcut for ${item.label}`,\n )}\n onClick={() => void save(item.id, [])}\n >\n {t('shortcuts.turn_off', {}, 'Turn off')}\n </Button>\n )}\n {item.customized && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={t(\n 'shortcuts.reset_label',\n { label: item.label },\n `Restore the default shortcut for ${item.label}`,\n )}\n onClick={() => void save(item.id, null)}\n >\n {t('shortcuts.reset', {}, 'Reset')}\n </Button>\n )}\n </span>\n )}\n </li>\n ))}\n </ul>\n </section>\n ))}\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
|
|
@@ -117,7 +117,7 @@ function ImageCell({ column, cell }) {
|
|
|
117
117
|
className: cn("flex items-center", schema.wrap && "flex-wrap", schema.stacked ? usesDefaultImageStack(schema) && "-space-x-2" : "gap-1.5", schema.wrap && schema.stacked && "gap-y-1.5"),
|
|
118
118
|
children: [urls.slice(0, limit).map((url, index) => /* @__PURE__ */ jsx("img", {
|
|
119
119
|
src: url,
|
|
120
|
-
alt: cell.alt ?? "",
|
|
120
|
+
alt: cell.alt ?? (index === 0 ? column.label ?? "" : ""),
|
|
121
121
|
style: {
|
|
122
122
|
height: schema.height ?? size,
|
|
123
123
|
width: schema.width ?? (schema.square || schema.circular ? size : void 0),
|