@medalsocial/meda 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -109,6 +109,24 @@ Workspace shells also expose chrome-level composition slots:
109
109
 
110
110
  `workspace.menuItems`, `workspace.menuFooter`, and the theme toggle are available from the mobile Menu drawer. Use `mainLayout`/`mainClassName` when a workspace shell needs the same mobile chrome but a custom main scroll region, such as a full-bleed marketing page. `useCommands()` works from workspace descendants without manually mounting `CommandPalette`; lower-level primitive compositions can still mount `CommandPalette` directly.
111
111
 
112
+ ## Foundation primitives
113
+
114
+ Meda includes small foundation primitives for repeated loading, empty, and filtering surfaces:
115
+
116
+ ```tsx
117
+ import { EmptyState, FilterRail, Skeleton } from '@medalsocial/meda';
118
+ ```
119
+
120
+ `Skeleton` mirrors shadcn's simple loading placeholder shape. `EmptyState` standardizes zero/error states across panels and content areas. `FilterRail` provides a dense filter surface while leaving selected values, URL syncing, and query logic in the consuming app.
121
+
122
+ The same primitives are available from the shadcn-compatible registry when an app wants local source ownership:
123
+
124
+ ```bash
125
+ npx shadcn add https://meda.medalsocial.com/r/meda-skeleton.json
126
+ npx shadcn add https://meda.medalsocial.com/r/meda-empty-state.json
127
+ npx shadcn add https://meda.medalsocial.com/r/meda-filter-rail.json
128
+ ```
129
+
112
130
  For app-scoped brand tokens:
113
131
 
114
132
  ```ts
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './brand/public.js';
3
3
  export * from './chat/public.js';
4
4
  export * from './marketing/public.js';
5
5
  export * from './panel/public.js';
6
+ export * from './primitives/index.js';
6
7
  export * from './shell/index.js';
7
8
  export * from './theme/index.js';
8
9
  export * from './timeline/public.js';
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export * from './brand/public.js';
3
3
  export * from './chat/public.js';
4
4
  export * from './marketing/public.js';
5
5
  export * from './panel/public.js';
6
+ export * from './primitives/index.js';
6
7
  export * from './shell/index.js'; // v2 surface
7
8
  export * from './theme/index.js';
8
9
  export * from './timeline/public.js';
@@ -0,0 +1,11 @@
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
3
+ export type EmptyStateVariant = 'default' | 'panel' | 'inline';
4
+ export interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {
5
+ icon?: LucideIcon | ReactNode;
6
+ title: ReactNode;
7
+ description?: ReactNode;
8
+ action?: ReactNode;
9
+ variant?: EmptyStateVariant;
10
+ }
11
+ export declare function EmptyState({ icon, title, description, action, variant, className, ...props }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { isValidElement } from 'react';
4
+ import { cn } from '../lib/utils.js';
5
+ function isIconComponent(icon) {
6
+ if (typeof icon === 'function')
7
+ return true;
8
+ if (typeof icon !== 'object')
9
+ return false;
10
+ return '$$typeof' in icon && !isValidElement(icon);
11
+ }
12
+ function renderIcon(icon) {
13
+ if (!icon)
14
+ return null;
15
+ if (isIconComponent(icon)) {
16
+ const Icon = icon;
17
+ return _jsx(Icon, { "data-testid": "empty-state-icon", className: "size-10", "aria-hidden": "true" });
18
+ }
19
+ return (_jsx("span", { "data-testid": "empty-state-icon", "aria-hidden": "true", className: "inline-flex", children: icon }));
20
+ }
21
+ export function EmptyState({ icon, title, description, action, variant = 'default', className, ...props }) {
22
+ return (_jsxs("div", { "data-slot": "empty-state", "data-variant": variant, className: cn('flex flex-col items-center justify-center text-center', variant === 'default' && 'px-6 py-16', variant === 'panel' && 'px-4 py-10', variant === 'inline' && 'px-3 py-6', className), ...props, children: [icon ? (_jsx("div", { "data-slot": "empty-state-icon", className: cn('mb-4 inline-flex items-center justify-center rounded-md text-muted-foreground', variant === 'inline' ? 'size-9' : 'size-12'), children: renderIcon(icon) })) : null, _jsx("h3", { "data-slot": "empty-state-title", className: cn('font-semibold text-foreground', variant === 'default' && 'text-lg', variant === 'panel' && 'text-base', variant === 'inline' && 'text-sm'), children: title }), description ? (_jsx("p", { "data-slot": "empty-state-description", className: cn('mt-1 max-w-sm text-muted-foreground', variant === 'inline' ? 'text-xs' : 'text-sm'), children: description })) : null, action ? (_jsx("div", { "data-slot": "empty-state-action", className: "mt-5", children: action })) : null] }));
23
+ }
@@ -0,0 +1,20 @@
1
+ import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
2
+ export interface FilterRailProps extends Omit<ComponentPropsWithoutRef<'aside'>, 'title'> {
3
+ title?: ReactNode;
4
+ description?: ReactNode;
5
+ search?: ReactNode;
6
+ actions?: ReactNode;
7
+ footer?: ReactNode;
8
+ children?: ReactNode;
9
+ }
10
+ export interface FilterRailGroupProps extends Omit<ComponentPropsWithoutRef<'fieldset'>, 'title'> {
11
+ title?: ReactNode;
12
+ description?: ReactNode;
13
+ children?: ReactNode;
14
+ }
15
+ declare function FilterRailGroup({ title, description, children, className, ...props }: FilterRailGroupProps): import("react/jsx-runtime").JSX.Element;
16
+ declare function FilterRailRoot({ title, description, search, actions, footer, children, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: FilterRailProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare const FilterRail: typeof FilterRailRoot & {
18
+ Group: typeof FilterRailGroup;
19
+ };
20
+ export {};
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId } from 'react';
4
+ import { cn } from '../lib/utils.js';
5
+ function FilterRailGroup({ title, description, children, className, ...props }) {
6
+ return (_jsxs("fieldset", { "data-slot": "filter-rail-group", className: cn('space-y-2 border-0 p-0', className), ...props, children: [title ? (_jsx("legend", { "data-slot": "filter-rail-group-title", className: "text-xs font-semibold text-foreground", children: title })) : null, description ? (_jsx("p", { "data-slot": "filter-rail-group-description", className: "text-xs text-muted-foreground", children: description })) : null, _jsx("div", { "data-slot": "filter-rail-group-content", className: "space-y-1.5", children: children })] }));
7
+ }
8
+ function FilterRailRoot({ title, description, search, actions, footer, children, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }) {
9
+ const titleId = useId();
10
+ const label = ariaLabel ?? (ariaLabelledBy ? undefined : typeof title === 'string' ? title : undefined);
11
+ const labelledBy = ariaLabelledBy ?? (label ? undefined : title ? titleId : undefined);
12
+ return (_jsxs("aside", { "data-slot": "filter-rail", "aria-label": label, "aria-labelledby": labelledBy, className: cn('flex min-h-0 w-full flex-col border-border bg-card text-card-foreground', className), ...props, children: [title || description || actions ? (_jsxs("div", { "data-slot": "filter-rail-header", className: "flex shrink-0 items-start justify-between gap-3 border-b border-border px-4 py-3", children: [_jsxs("div", { className: "min-w-0", children: [title ? (_jsx("h2", { id: titleId, "data-slot": "filter-rail-title", className: "text-sm font-semibold text-foreground", children: title })) : null, description ? (_jsx("p", { "data-slot": "filter-rail-description", className: "mt-0.5 text-xs text-muted-foreground", children: description })) : null] }), actions ? (_jsx("div", { "data-slot": "filter-rail-actions", className: "shrink-0", children: actions })) : null] })) : null, search ? (_jsx("div", { "data-slot": "filter-rail-search", className: "shrink-0 border-b border-border p-3", children: search })) : null, _jsx("div", { "data-slot": "filter-rail-content", className: "min-h-0 flex-1 space-y-5 overflow-y-auto p-4", children: children }), footer ? (_jsx("div", { "data-slot": "filter-rail-footer", className: "shrink-0 border-t border-border p-3", children: footer })) : null] }));
13
+ }
14
+ export const FilterRail = Object.assign(FilterRailRoot, {
15
+ Group: FilterRailGroup,
16
+ });
@@ -0,0 +1,6 @@
1
+ export type { EmptyStateProps, EmptyStateVariant } from './empty-state.js';
2
+ export { EmptyState } from './empty-state.js';
3
+ export type { FilterRailGroupProps, FilterRailProps } from './filter-rail.js';
4
+ export { FilterRail } from './filter-rail.js';
5
+ export type { SkeletonProps } from './skeleton.js';
6
+ export { Skeleton } from './skeleton.js';
@@ -0,0 +1,3 @@
1
+ export { EmptyState } from './empty-state.js';
2
+ export { FilterRail } from './filter-rail.js';
3
+ export { Skeleton } from './skeleton.js';
@@ -0,0 +1,3 @@
1
+ import type { ComponentProps } from 'react';
2
+ export type SkeletonProps = ComponentProps<'div'>;
3
+ export declare function Skeleton({ className, 'aria-hidden': ariaHidden, ...props }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cn } from '../lib/utils.js';
4
+ export function Skeleton({ className, 'aria-hidden': ariaHidden = true, ...props }) {
5
+ return (_jsx("div", { "data-slot": "skeleton", "aria-hidden": ariaHidden, className: cn('animate-pulse rounded-md bg-muted', className), ...props }));
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medalsocial/meda",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Shared Meda UI shell and runtime package.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {