cortena-ui 1.0.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortena-ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Cortena UI — the shared React component library for cortenaweb and every Cortena extension. shadcn over Base UI, styled entirely from cortena-design tokens.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import type * as React from "react";
4
+ import { cn } from "@/lib/cn";
5
+ import { buttonVariants, type ButtonProps } from "@/components/button";
6
+
7
+ /**
8
+ * An anchor that looks like a Button and keeps link semantics.
9
+ *
10
+ * `Button render={<a />}` makes Base UI treat the anchor as a button (role,
11
+ * tabindex, its `nativeButton` warning), which demotes real navigation for
12
+ * assistive technology and breaks `getByRole("link")`. Navigation should stay
13
+ * an anchor. This is the migration target for every Radix-era
14
+ * `<Button asChild><Link /></Button>`.
15
+ *
16
+ * Framework routers: pass `render` with the router's link so the styling and
17
+ * `href` land on it, e.g. `render={<NextLink href="/x" />}`; otherwise it
18
+ * renders a plain `<a>`.
19
+ */
20
+ export interface ButtonLinkProps
21
+ extends Omit<React.ComponentProps<"a">, "children">,
22
+ Pick<ButtonProps, "variant" | "size"> {
23
+ children?: React.ReactNode;
24
+ /** Replace the rendered `<a>` with a router link; props are merged onto it. */
25
+ render?: React.ReactElement<Record<string, unknown>>;
26
+ disabled?: boolean;
27
+ }
28
+
29
+ function ButtonLink({ className, variant, size, render, disabled, children, ...props }: ButtonLinkProps) {
30
+ const classes = cn(buttonVariants({ variant, size }), disabled && "pointer-events-none opacity-50", className);
31
+ const merged = {
32
+ ...props,
33
+ "data-slot": "button-link",
34
+ className: classes,
35
+ "aria-disabled": disabled || undefined,
36
+ tabIndex: disabled ? -1 : props.tabIndex,
37
+ };
38
+ if (render) {
39
+ const Element = render.type as React.ElementType;
40
+ return (
41
+ <Element {...(render.props as Record<string, unknown>)} {...merged}>
42
+ {children ?? (render.props as { children?: React.ReactNode }).children}
43
+ </Element>
44
+ );
45
+ }
46
+ return <a {...merged}>{children}</a>;
47
+ }
48
+
49
+ export { ButtonLink };
@@ -327,6 +327,15 @@ export interface MarkdownProps extends Omit<React.ComponentProps<"div">, "childr
327
327
  remarkPlugins?: NonNullable<Options["remarkPlugins"]>;
328
328
  /** Extra rehype plugins, appended after sanitize. */
329
329
  rehypePlugins?: NonNullable<Options["rehypePlugins"]>;
330
+ /**
331
+ * Rehype plugins that must run BEFORE sanitize. The one legitimate use is
332
+ * `rehype-raw` in a host that embeds trusted custom tags in markdown (the
333
+ * webchat's `<cortena-*>` blocks): raw HTML has to become nodes before the
334
+ * sanitizer sees it, and the sanitize schema then decides what survives.
335
+ * Anything placed here widens what reaches the sanitizer, so keep the
336
+ * schema strict when you use it.
337
+ */
338
+ rehypePluginsBeforeSanitize?: NonNullable<Options["rehypePlugins"]>;
330
339
  /** Replace the sanitize schema; defaults to `markdownSanitizeSchema`. */
331
340
  sanitizeSchema?: SanitizeSchema;
332
341
  /** Rewrite URLs; defaults to react-markdown's, which drops non-http(s)/mailto/tel schemes. */
@@ -340,6 +349,7 @@ function Markdown({
340
349
  components,
341
350
  remarkPlugins,
342
351
  rehypePlugins,
352
+ rehypePluginsBeforeSanitize,
343
353
  sanitizeSchema = markdownSanitizeSchema,
344
354
  urlTransform,
345
355
  className,
@@ -351,8 +361,8 @@ function Markdown({
351
361
  );
352
362
  const remark = React.useMemo(() => [remarkGfm, ...(remarkPlugins ?? [])], [remarkPlugins]);
353
363
  const rehype = React.useMemo(
354
- () => [[rehypeSanitize, sanitizeSchema] as const, ...(rehypePlugins ?? [])],
355
- [rehypePlugins, sanitizeSchema],
364
+ () => [...(rehypePluginsBeforeSanitize ?? []), [rehypeSanitize, sanitizeSchema] as const, ...(rehypePlugins ?? [])],
365
+ [rehypePluginsBeforeSanitize, rehypePlugins, sanitizeSchema],
356
366
  );
357
367
  return (
358
368
  <div
package/src/index.ts CHANGED
@@ -9,6 +9,8 @@ export type { AlertProps } from "@/components/alert";
9
9
  export { Avatar, AvatarFallback, AvatarImage } from "@/components/avatar";
10
10
  export { Badge, badgeVariants } from "@/components/badge";
11
11
  export type { BadgeProps } from "@/components/badge";
12
+ export { ButtonLink } from "@/components/button-link";
13
+ export type { ButtonLinkProps } from "@/components/button-link";
12
14
  export { Button, buttonVariants } from "@/components/button";
13
15
  export type { ButtonProps } from "@/components/button";
14
16
  export { Calendar, CalendarDayButton } from "@/components/calendar";