@medalsocial/meda 2.2.0 → 2.4.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.
@@ -1,14 +1,19 @@
1
+ export { MarketingAnnouncementBar } from './marketing-announcement-bar.js';
1
2
  export { MarketingBentoCard } from './marketing-bento-card.js';
2
3
  export { MarketingBentoGrid } from './marketing-bento-grid.js';
3
4
  export { MarketingCallout } from './marketing-callout.js';
4
5
  export { MarketingContact } from './marketing-contact.js';
6
+ export { MarketingCTA } from './marketing-cta.js';
5
7
  export { MarketingCtaList } from './marketing-cta-list.js';
6
8
  export { MarketingFAQ } from './marketing-faq.js';
7
9
  export { MarketingFooter } from './marketing-footer.js';
8
10
  export { MarketingHeader } from './marketing-header.js';
9
11
  export { MarketingHeaderLogo } from './marketing-header-logo.js';
12
+ export { MarketingHero } from './marketing-hero.js';
10
13
  export { MarketingLeadMagnet } from './marketing-lead-magnet.js';
14
+ export { MarketingMegaMenu } from './marketing-mega-menu.js';
11
15
  export { MarketingNavItem } from './marketing-nav-item.js';
16
+ export { MarketingNumberedFeatures } from './marketing-numbered-features.js';
12
17
  export { MarketingShell } from './marketing-shell.js';
13
18
  export { MarketingTrustBar } from './marketing-trust-bar.js';
14
- export type { MarketingAlign, MarketingBentoCardProps, MarketingBentoGridProps, MarketingCalloutProps, MarketingCalloutVariant, MarketingContactPerson, MarketingContactProps, MarketingCta, MarketingCtaListProps, MarketingCtaVariant, MarketingFAQItem, MarketingFAQProps, MarketingFooterColumn, MarketingFooterProps, MarketingHeaderProps, MarketingHeaderUser, MarketingLeadMagnetProps, MarketingLeadMagnetVariant, MarketingNavItemDescriptor, MarketingOffice, MarketingShellProps, MarketingTrustBarProps, MarketingTrustStat, } from './types.js';
19
+ export type { MarketingAlign, MarketingAnnouncementBarProps, MarketingBentoCardProps, MarketingBentoGridProps, MarketingCalloutProps, MarketingCalloutVariant, MarketingContactPerson, MarketingContactProps, MarketingCTAProps, MarketingCta, MarketingCtaListProps, MarketingCtaVariant, MarketingFAQItem, MarketingFAQProps, MarketingFooterColumn, MarketingFooterProps, MarketingHeaderProps, MarketingHeaderUser, MarketingHeroProps, MarketingLeadMagnetProps, MarketingLeadMagnetVariant, MarketingMegaMenuFeature, MarketingMegaMenuProps, MarketingNavItemDescriptor, MarketingNumberedFeature, MarketingNumberedFeaturesProps, MarketingOffice, MarketingShellProps, MarketingTrustBarProps, MarketingTrustStat, } from './types.js';
@@ -1,13 +1,18 @@
1
+ export { MarketingAnnouncementBar } from './marketing-announcement-bar.js';
1
2
  export { MarketingBentoCard } from './marketing-bento-card.js';
2
3
  export { MarketingBentoGrid } from './marketing-bento-grid.js';
3
4
  export { MarketingCallout } from './marketing-callout.js';
4
5
  export { MarketingContact } from './marketing-contact.js';
6
+ export { MarketingCTA } from './marketing-cta.js';
5
7
  export { MarketingCtaList } from './marketing-cta-list.js';
6
8
  export { MarketingFAQ } from './marketing-faq.js';
7
9
  export { MarketingFooter } from './marketing-footer.js';
8
10
  export { MarketingHeader } from './marketing-header.js';
9
11
  export { MarketingHeaderLogo } from './marketing-header-logo.js';
12
+ export { MarketingHero } from './marketing-hero.js';
10
13
  export { MarketingLeadMagnet } from './marketing-lead-magnet.js';
14
+ export { MarketingMegaMenu } from './marketing-mega-menu.js';
11
15
  export { MarketingNavItem } from './marketing-nav-item.js';
16
+ export { MarketingNumberedFeatures } from './marketing-numbered-features.js';
12
17
  export { MarketingShell } from './marketing-shell.js';
13
18
  export { MarketingTrustBar } from './marketing-trust-bar.js';
@@ -0,0 +1,2 @@
1
+ import type { MarketingAnnouncementBarProps } from './types.js';
2
+ export declare function MarketingAnnouncementBar({ href, icon, children, className, }: MarketingAnnouncementBarProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from './utils.js';
3
+ export function MarketingAnnouncementBar({ href, icon, children, className, }) {
4
+ const Tag = href ? 'a' : 'span';
5
+ return (_jsxs(Tag, { ...(href ? { href } : {}), className: cx('inline-flex items-center gap-2 rounded-full border border-border/60 bg-card/50 px-4 py-1.5 text-xs font-medium text-foreground/80 backdrop-blur', className), children: [icon, children] }));
6
+ }
@@ -0,0 +1,2 @@
1
+ import type { MarketingCTAProps } from './types.js';
2
+ export declare function MarketingCTA({ eyebrow, title, subtitle, ctas, className }: MarketingCTAProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from './utils.js';
3
+ export function MarketingCTA({ eyebrow, title, subtitle, ctas, className }) {
4
+ return (_jsxs("section", { className: cx('mx-auto w-full max-w-4xl px-6 py-24 text-center', className), children: [eyebrow && _jsx("p", { className: "mb-3 text-sm font-semibold text-primary", children: eyebrow }), _jsx("h2", { className: "mx-auto max-w-3xl text-4xl font-semibold sm:text-5xl", children: title }), subtitle && (_jsx("p", { className: "mx-auto mt-4 max-w-xl text-base text-muted-foreground sm:text-lg", children: subtitle })), ctas && _jsx("div", { className: "mt-8 flex flex-wrap items-center justify-center gap-3", children: ctas })] }));
5
+ }
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
3
4
  import { MarketingNavItem } from './marketing-nav-item.js';
4
5
  import { cx } from './utils.js';
5
6
  const DEFAULT_SIGN_IN = '/sign-in';
@@ -12,6 +13,7 @@ function DefaultLoggedIn({ appHref }) {
12
13
  return (_jsx("a", { href: appHref, className: "inline-flex items-center rounded-full bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90", children: "Open dashboard" }));
13
14
  }
14
15
  export function MarketingHeader({ logo, navItems = [], user, signInHref = DEFAULT_SIGN_IN, signUpHref = DEFAULT_SIGN_UP, appHref = DEFAULT_APP, rightSlot, toggles, renderLoggedOut, renderLoggedIn, className, }) {
16
+ const [openId, setOpenId] = useState(null);
15
17
  let right;
16
18
  if (rightSlot !== undefined) {
17
19
  right = rightSlot;
@@ -22,5 +24,7 @@ export function MarketingHeader({ logo, navItems = [], user, signInHref = DEFAUL
22
24
  else {
23
25
  right = renderLoggedOut ? (renderLoggedOut({ signInHref, signUpHref })) : (_jsx(DefaultLoggedOut, { signInHref: signInHref, signUpHref: signUpHref }));
24
26
  }
25
- return (_jsxs("nav", { "aria-label": "Primary", className: cx('mx-auto flex w-full max-w-6xl items-center justify-between rounded-full border border-border/60 bg-background/70 px-4 py-2 backdrop-blur supports-[backdrop-filter]:bg-background/50', className), children: [_jsxs("div", { className: "flex items-center gap-6", children: [logo, _jsx("ul", { className: "hidden items-center gap-1 md:flex", children: navItems.map((item) => (_jsx("li", { children: _jsx(MarketingNavItem, { ...item }) }, item.id))) })] }), _jsxs("div", { className: "flex items-center gap-3", children: [toggles, right] })] }));
27
+ const activeItem = navItems.find((i) => i.id === openId);
28
+ const activePanel = activeItem && 'panel' in activeItem ? activeItem.panel : undefined;
29
+ return (_jsxs("nav", { "aria-label": "Primary", className: "relative", onMouseLeave: () => setOpenId(null), children: [_jsxs("div", { className: cx('mx-auto flex w-full max-w-6xl items-center justify-between rounded-full border border-border/60 bg-background/70 px-4 py-2 backdrop-blur supports-[backdrop-filter]:bg-background/50', className), children: [_jsxs("div", { className: "flex items-center gap-6", children: [logo, _jsx("ul", { className: "hidden items-center gap-1 md:flex", children: navItems.map((item) => (_jsx("li", { children: _jsx(MarketingNavItem, { ...item, open: openId === item.id, onToggle: item.hasMenu ? () => setOpenId(openId === item.id ? null : item.id) : undefined }) }, item.id))) })] }), _jsxs("div", { className: "flex items-center gap-3", children: [toggles, right] })] }), activePanel] }));
26
30
  }
@@ -0,0 +1,2 @@
1
+ import type { MarketingHeroProps } from './types.js';
2
+ export declare function MarketingHero({ eyebrow, headline, subtitle, ctas, meta, productMockup, className, }: MarketingHeroProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from './utils.js';
3
+ export function MarketingHero({ eyebrow, headline, subtitle, ctas, meta, productMockup, className, }) {
4
+ return (_jsxs("section", { className: cx('mx-auto w-full max-w-6xl px-6 pt-16 pb-24', className), children: [_jsxs("div", { className: "flex flex-col items-center gap-6 text-center", children: [eyebrow, _jsx("h1", { className: "max-w-4xl text-5xl font-semibold leading-[1.05] tracking-tight sm:text-6xl md:text-7xl lg:text-[80px]", children: headline }), subtitle && (_jsx("p", { className: "max-w-2xl text-base text-muted-foreground sm:text-lg", children: subtitle })), ctas && (_jsx("div", { className: "mt-2 flex flex-wrap items-center justify-center gap-3", children: ctas })), meta && _jsx("p", { className: "text-xs text-muted-foreground", children: meta })] }), productMockup && (_jsx("div", { className: "mt-16 overflow-hidden rounded-3xl border border-border bg-card shadow-2xl", children: productMockup }))] }));
5
+ }
@@ -0,0 +1,2 @@
1
+ import type { MarketingMegaMenuProps } from './types.js';
2
+ export declare function MarketingMegaMenu({ triggerId, open, onOpenChange, features, children, className, }: MarketingMegaMenuProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,8 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cx } from './utils.js';
4
+ export function MarketingMegaMenu({ triggerId, open, onOpenChange, features, children, className, }) {
5
+ if (!open)
6
+ return null;
7
+ return (_jsx("div", { role: "dialog", "aria-labelledby": triggerId, onMouseLeave: () => onOpenChange(false), className: cx('absolute left-1/2 top-full mt-3 w-[min(960px,calc(100vw-32px))] -translate-x-1/2 rounded-3xl border border-border bg-card p-6 shadow-2xl backdrop-blur', className), children: children ?? (_jsx("ul", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: features?.map((f) => (_jsx("li", { children: _jsxs("a", { href: f.href, className: "flex items-start gap-3 rounded-xl p-3 hover:bg-muted/40", children: [f.icon && (_jsx("span", { className: "mt-0.5 inline-flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: f.icon })), _jsxs("span", { children: [_jsx("span", { className: "block text-sm font-semibold", children: f.title }), f.description && (_jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: f.description }))] })] }) }, f.id))) })) }));
8
+ }
@@ -1,4 +1,6 @@
1
1
  import type { MarketingNavItemDescriptor } from './types.js';
2
- export declare function MarketingNavItem({ id, label, className, ...rest }: MarketingNavItemDescriptor & {
2
+ export declare function MarketingNavItem({ id, label, className, open, onToggle, ...rest }: MarketingNavItemDescriptor & {
3
3
  className?: string;
4
+ open?: boolean;
5
+ onToggle?: () => void;
4
6
  }): import("react/jsx-runtime").JSX.Element;
@@ -2,9 +2,9 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ChevronDown } from 'lucide-react';
4
4
  import { cx } from './utils.js';
5
- export function MarketingNavItem({ id, label, className, ...rest }) {
5
+ export function MarketingNavItem({ id, label, className, open = false, onToggle, ...rest }) {
6
6
  if (rest.hasMenu) {
7
- return (_jsxs("button", { type: "button", "data-nav-id": id, "aria-expanded": false, "aria-haspopup": "menu", className: cx('inline-flex items-center gap-1 rounded-full px-3 py-1.5 text-sm text-foreground/80 hover:text-foreground', className), children: [label, _jsx(ChevronDown, { "data-chevron": true, className: "h-3.5 w-3.5", "aria-hidden": true })] }));
7
+ return (_jsxs("button", { type: "button", "data-nav-id": id, id: id, "aria-expanded": open, "aria-haspopup": "menu", onClick: onToggle, className: cx('inline-flex items-center gap-1 rounded-full px-3 py-1.5 text-sm text-foreground/80 hover:text-foreground', className), children: [label, _jsx(ChevronDown, { "data-chevron": true, className: cx('h-3.5 w-3.5 transition', open && 'rotate-180'), "aria-hidden": true })] }));
8
8
  }
9
9
  return (_jsx("a", { href: rest.href, "data-nav-id": id, className: cx('inline-flex items-center rounded-full px-3 py-1.5 text-sm text-foreground/80 hover:text-foreground', className), children: label }));
10
10
  }
@@ -0,0 +1,2 @@
1
+ import type { MarketingNumberedFeaturesProps } from './types.js';
2
+ export declare function MarketingNumberedFeatures({ eyebrow, title, features, className, }: MarketingNumberedFeaturesProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from './utils.js';
3
+ export function MarketingNumberedFeatures({ eyebrow, title, features, className, }) {
4
+ return (_jsxs("section", { className: cx('mx-auto w-full max-w-6xl px-6 py-16', className), children: [(eyebrow || title) && (_jsxs("div", { className: "mb-12 flex flex-col items-center gap-2 text-center", children: [eyebrow && _jsx("p", { className: "text-sm font-semibold text-primary", children: eyebrow }), title && _jsx("h2", { className: "max-w-3xl text-3xl font-semibold sm:text-4xl", children: title })] })), _jsx("ol", { className: "grid gap-6 md:grid-cols-3", children: features.map((f) => (_jsxs("li", { className: "rounded-2xl border border-border bg-card p-6", children: [_jsx("div", { className: "text-sm font-semibold text-primary", children: f.index }), _jsx("h3", { className: "mt-2 text-xl font-semibold", children: f.title }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: f.description })] }, f.id))) })] }));
5
+ }
@@ -1 +1 @@
1
- export type { MarketingBentoCardProps, MarketingBentoGridProps, MarketingCalloutProps, MarketingContactPerson, MarketingContactProps, MarketingCta, MarketingFAQItem, MarketingFAQProps, MarketingFooterColumn, MarketingFooterProps, MarketingHeaderProps, MarketingHeaderUser, MarketingLeadMagnetProps, MarketingNavItemDescriptor, MarketingOffice, MarketingShellProps, MarketingTrustBarProps, MarketingTrustStat, } from './types.js';
1
+ export type { MarketingAnnouncementBarProps, MarketingBentoCardProps, MarketingBentoGridProps, MarketingCalloutProps, MarketingContactPerson, MarketingContactProps, MarketingCTAProps, MarketingCta, MarketingFAQItem, MarketingFAQProps, MarketingFooterColumn, MarketingFooterProps, MarketingHeaderProps, MarketingHeaderUser, MarketingHeroProps, MarketingLeadMagnetProps, MarketingMegaMenuFeature, MarketingMegaMenuProps, MarketingNavItemDescriptor, MarketingNumberedFeature, MarketingNumberedFeaturesProps, MarketingOffice, MarketingShellProps, MarketingTrustBarProps, MarketingTrustStat, } from './types.js';
@@ -163,4 +163,53 @@ export interface MarketingFAQProps {
163
163
  defaultOpenId?: string;
164
164
  className?: string;
165
165
  }
166
+ export interface MarketingMegaMenuFeature {
167
+ id: string;
168
+ icon?: ReactNode;
169
+ title: ReactNode;
170
+ description?: ReactNode;
171
+ href: string;
172
+ }
173
+ export interface MarketingMegaMenuProps {
174
+ triggerId: string;
175
+ open: boolean;
176
+ onOpenChange: (open: boolean) => void;
177
+ features?: MarketingMegaMenuFeature[];
178
+ children?: ReactNode;
179
+ className?: string;
180
+ }
181
+ export interface MarketingAnnouncementBarProps {
182
+ href?: string;
183
+ icon?: ReactNode;
184
+ children: ReactNode;
185
+ className?: string;
186
+ }
187
+ export interface MarketingHeroProps {
188
+ eyebrow?: ReactNode;
189
+ headline: ReactNode;
190
+ subtitle?: ReactNode;
191
+ ctas?: ReactNode;
192
+ meta?: ReactNode;
193
+ productMockup?: ReactNode;
194
+ className?: string;
195
+ }
196
+ export interface MarketingNumberedFeature {
197
+ id: string;
198
+ index: ReactNode;
199
+ title: ReactNode;
200
+ description: ReactNode;
201
+ }
202
+ export interface MarketingNumberedFeaturesProps {
203
+ eyebrow?: ReactNode;
204
+ title?: ReactNode;
205
+ features: MarketingNumberedFeature[];
206
+ className?: string;
207
+ }
208
+ export interface MarketingCTAProps {
209
+ eyebrow?: ReactNode;
210
+ title: ReactNode;
211
+ subtitle?: ReactNode;
212
+ ctas?: ReactNode;
213
+ className?: string;
214
+ }
166
215
  export {};
@@ -0,0 +1,15 @@
1
+ import type { ComponentPropsWithoutRef } from 'react';
2
+ export type CardProps = ComponentPropsWithoutRef<'div'>;
3
+ export type CardHeaderProps = ComponentPropsWithoutRef<'div'>;
4
+ export type CardBodyProps = ComponentPropsWithoutRef<'div'>;
5
+ export type CardFooterProps = ComponentPropsWithoutRef<'div'>;
6
+ declare function CardRoot({ children, className, ...props }: CardProps): import("react/jsx-runtime").JSX.Element;
7
+ declare function CardHeader({ children, className, ...props }: CardHeaderProps): import("react/jsx-runtime").JSX.Element;
8
+ declare function CardBody({ children, className, ...props }: CardBodyProps): import("react/jsx-runtime").JSX.Element;
9
+ declare function CardFooter({ children, className, ...props }: CardFooterProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare const Card: typeof CardRoot & {
11
+ Header: typeof CardHeader;
12
+ Body: typeof CardBody;
13
+ Footer: typeof CardFooter;
14
+ };
15
+ export {};
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cn } from '../lib/utils.js';
4
+ function CardRoot({ children, className, ...props }) {
5
+ return (_jsx("div", { "data-slot": "card", className: cn('flex flex-col rounded-xl border border-border bg-card text-card-foreground', className), ...props, children: children }));
6
+ }
7
+ function CardHeader({ children, className, ...props }) {
8
+ return (_jsx("div", { "data-slot": "card-header", className: cn('flex flex-col gap-1 border-b border-border px-4 py-3 last:border-b-0', className), ...props, children: children }));
9
+ }
10
+ function CardBody({ children, className, ...props }) {
11
+ return (_jsx("div", { "data-slot": "card-body", className: cn('px-4 py-3', className), ...props, children: children }));
12
+ }
13
+ function CardFooter({ children, className, ...props }) {
14
+ return (_jsx("div", { "data-slot": "card-footer", className: cn('flex items-center justify-end gap-2 border-t border-border px-4 py-3 first:border-t-0', className), ...props, children: children }));
15
+ }
16
+ export const Card = Object.assign(CardRoot, {
17
+ Header: CardHeader,
18
+ Body: CardBody,
19
+ Footer: CardFooter,
20
+ });
@@ -1,6 +1,10 @@
1
+ export type { CardBodyProps, CardFooterProps, CardHeaderProps, CardProps, } from './card.js';
2
+ export { Card } from './card.js';
1
3
  export type { EmptyStateProps, EmptyStateVariant } from './empty-state.js';
2
4
  export { EmptyState } from './empty-state.js';
3
5
  export type { FilterRailGroupProps, FilterRailProps } from './filter-rail.js';
4
6
  export { FilterRail } from './filter-rail.js';
5
7
  export type { SkeletonProps } from './skeleton.js';
6
8
  export { Skeleton } from './skeleton.js';
9
+ export type { StatusPillProps, StatusPillSize, StatusPillTone } from './status-pill.js';
10
+ export { StatusPill } from './status-pill.js';
@@ -1,3 +1,5 @@
1
+ export { Card } from './card.js';
1
2
  export { EmptyState } from './empty-state.js';
2
3
  export { FilterRail } from './filter-rail.js';
3
4
  export { Skeleton } from './skeleton.js';
5
+ export { StatusPill } from './status-pill.js';
@@ -0,0 +1,2 @@
1
+ export type { MarkdownViewProps } from './markdown-view.js';
2
+ export { MarkdownView } from './markdown-view.js';
@@ -0,0 +1,7 @@
1
+ // Dedicated subpath entry for MarkdownView.
2
+ //
3
+ // Importing `@medalsocial/meda/markdown-view` opts the consumer into the
4
+ // react-markdown / remark-gfm / rehype-highlight peer chain. Consumers who
5
+ // don't need markdown rendering should import from `@medalsocial/meda` /
6
+ // `@medalsocial/meda/primitives` instead — neither pulls these peers in.
7
+ export { MarkdownView } from './markdown-view.js';
@@ -0,0 +1,22 @@
1
+ import type { ComponentPropsWithoutRef } from 'react';
2
+ import { type Components } from 'react-markdown';
3
+ export interface MarkdownViewProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
4
+ children: string;
5
+ /**
6
+ * Override or extend the default react-markdown component map.
7
+ * Spread defaults are applied first, so caller overrides win.
8
+ */
9
+ components?: Components;
10
+ }
11
+ /**
12
+ * MarkdownView wraps `react-markdown` with `remark-gfm` and `rehype-highlight`,
13
+ * and applies a consistent prose stylesheet using meda design tokens.
14
+ *
15
+ * The markdown libraries are listed as **optional peer dependencies**. Install
16
+ * them in your app if you import this component:
17
+ *
18
+ * ```bash
19
+ * pnpm add react-markdown remark-gfm rehype-highlight
20
+ * ```
21
+ */
22
+ export declare function MarkdownView({ children, className, components, ...props }: MarkdownViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import ReactMarkdown from 'react-markdown';
4
+ import rehypeHighlight from 'rehype-highlight';
5
+ import remarkGfm from 'remark-gfm';
6
+ import { cn } from '../lib/utils.js';
7
+ // remark-gfm renders task-list `[ ]` / `[x]` as unlabelled disabled checkboxes,
8
+ // which fails the axe `label` rule. Override the `input` renderer to add an
9
+ // aria-label derived from the checked state — keeps the checkbox semantics
10
+ // for AT users while satisfying the contrast/label gate.
11
+ const DEFAULT_COMPONENTS = {
12
+ input: ({ type, checked, ...rest }) => {
13
+ if (type === 'checkbox') {
14
+ return (_jsx("input", { type: "checkbox", checked: checked, "aria-label": checked ? 'task complete' : 'task incomplete', ...rest }));
15
+ }
16
+ return _jsx("input", { type: type, ...rest });
17
+ },
18
+ };
19
+ /**
20
+ * MarkdownView wraps `react-markdown` with `remark-gfm` and `rehype-highlight`,
21
+ * and applies a consistent prose stylesheet using meda design tokens.
22
+ *
23
+ * The markdown libraries are listed as **optional peer dependencies**. Install
24
+ * them in your app if you import this component:
25
+ *
26
+ * ```bash
27
+ * pnpm add react-markdown remark-gfm rehype-highlight
28
+ * ```
29
+ */
30
+ export function MarkdownView({ children, className, components, ...props }) {
31
+ return (_jsx("div", { "data-slot": "markdown-view", className: cn(
32
+ // Base prose
33
+ 'text-sm leading-relaxed text-foreground',
34
+ // Trim outer block margins: only the first/last *child* of the
35
+ // wrapper should lose its top/bottom margin. The earlier shape
36
+ // `first:[&_h1]:mt-0` selects all descendant h1s when the wrapper
37
+ // itself is :first-child of its parent — which then collapses the
38
+ // margin on every nested h1, not just the leading one.
39
+ '[&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
40
+ // Headings
41
+ '[&_h1]:mt-6 [&_h1]:mb-3 [&_h1]:text-xl [&_h1]:font-semibold [&_h1]:text-foreground', '[&_h2]:mt-5 [&_h2]:mb-2 [&_h2]:text-lg [&_h2]:font-semibold [&_h2]:text-foreground', '[&_h3]:mt-4 [&_h3]:mb-2 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-foreground', '[&_h4]:mt-3 [&_h4]:mb-1.5 [&_h4]:text-sm [&_h4]:font-semibold [&_h4]:text-foreground',
42
+ // Paragraphs and inline
43
+ '[&_p]:my-3', '[&_strong]:font-semibold [&_strong]:text-foreground', '[&_em]:italic', '[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2 hover:[&_a]:no-underline',
44
+ // Lists
45
+ '[&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:space-y-1', '[&_ol]:my-3 [&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:space-y-1', '[&_li]:text-foreground',
46
+ // Blockquote
47
+ '[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground',
48
+ // Code
49
+ '[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.875em] [&_code]:text-foreground', '[&_pre]:my-3 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-border [&_pre]:bg-muted [&_pre]:p-3', '[&_pre_code]:bg-transparent [&_pre_code]:p-0',
50
+ // Tables (GFM)
51
+ '[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_table]:text-sm', '[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold', '[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1',
52
+ // Horizontal rule
53
+ '[&_hr]:my-6 [&_hr]:border-border',
54
+ // Images
55
+ '[&_img]:my-3 [&_img]:rounded-md [&_img]:border [&_img]:border-border', className), ...props, children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], rehypePlugins: [rehypeHighlight], components: { ...DEFAULT_COMPONENTS, ...components }, children: children }) }));
56
+ }
@@ -0,0 +1,12 @@
1
+ import type { ComponentPropsWithoutRef } from 'react';
2
+ export type StatusPillTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
3
+ export type StatusPillSize = 'sm' | 'md';
4
+ export interface StatusPillProps extends ComponentPropsWithoutRef<'span'> {
5
+ tone?: StatusPillTone;
6
+ size?: StatusPillSize;
7
+ /**
8
+ * Show a leading dot. Defaults to true; pass `false` to hide.
9
+ */
10
+ dot?: boolean;
11
+ }
12
+ export declare function StatusPill({ tone, size, dot, className, children, ...props }: StatusPillProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../lib/utils.js';
4
+ // Tone classes use solid backgrounds + their semantic foreground tokens.
5
+ // Tinted backgrounds (e.g. `bg-info/15 text-info`) failed WCAG AA contrast on
6
+ // the 11px size (~3:1 vs. the 4.5:1 minimum) — see vitest-axe a11y gate. The
7
+ // solid pairing is what existing meda status pills (e.g. voice-status-pill)
8
+ // use, and theme tokens already guarantee AA in both light and dark modes.
9
+ const TONE_CLASSES = {
10
+ neutral: 'bg-muted text-muted-foreground',
11
+ info: 'bg-info text-info-foreground',
12
+ success: 'bg-success text-success-foreground',
13
+ warning: 'bg-warning text-warning-foreground',
14
+ danger: 'bg-destructive text-destructive-foreground',
15
+ };
16
+ const SIZE_CLASSES = {
17
+ sm: 'gap-1 px-2 py-0.5 text-[11px]',
18
+ md: 'gap-1.5 px-2.5 py-1 text-xs',
19
+ };
20
+ const DOT_SIZE = {
21
+ sm: 'size-1.5',
22
+ md: 'size-2',
23
+ };
24
+ export function StatusPill({ tone = 'neutral', size = 'sm', dot = true, className, children, ...props }) {
25
+ return (_jsxs("span", { "data-slot": "status-pill", "data-tone": tone, "data-size": size, className: cn('inline-flex items-center rounded-full font-medium', TONE_CLASSES[tone], SIZE_CLASSES[size], className), ...props, children: [dot ? (_jsx("span", { "data-slot": "status-pill-dot", "aria-hidden": "true", className: cn('rounded-full bg-current', DOT_SIZE[size]) })) : null, children] }));
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medalsocial/meda",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Shared Meda UI shell and runtime package.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -107,6 +107,10 @@
107
107
  "./theme": {
108
108
  "types": "./dist/theme/index.d.ts",
109
109
  "default": "./dist/theme/index.js"
110
+ },
111
+ "./markdown-view": {
112
+ "types": "./dist/primitives/markdown-view-entry.d.ts",
113
+ "default": "./dist/primitives/markdown-view-entry.js"
110
114
  }
111
115
  },
112
116
  "sideEffects": [
@@ -125,7 +129,10 @@
125
129
  "lucide-react": "^1.8.0",
126
130
  "next-themes": "^0.4.0",
127
131
  "react": ">=19",
128
- "react-dom": ">=19"
132
+ "react-dom": ">=19",
133
+ "react-markdown": "^10.0.0",
134
+ "rehype-highlight": "^7.0.0",
135
+ "remark-gfm": "^4.0.0"
129
136
  },
130
137
  "peerDependenciesMeta": {
131
138
  "@dnd-kit/core": {
@@ -145,6 +152,15 @@
145
152
  },
146
153
  "next-themes": {
147
154
  "optional": true
155
+ },
156
+ "react-markdown": {
157
+ "optional": true
158
+ },
159
+ "rehype-highlight": {
160
+ "optional": true
161
+ },
162
+ "remark-gfm": {
163
+ "optional": true
148
164
  }
149
165
  },
150
166
  "dependencies": {
@@ -192,6 +208,9 @@
192
208
  "playwright": "1.59.1",
193
209
  "react": "19.2.5",
194
210
  "react-dom": "19.2.5",
211
+ "react-markdown": "10.1.0",
212
+ "rehype-highlight": "7.0.2",
213
+ "remark-gfm": "4.0.1",
195
214
  "size-limit": "12.1.0",
196
215
  "storybook": "10.3.6",
197
216
  "tailwindcss": "4.2.4",