@enmera/panel-kit 0.1.0 → 0.2.1

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - `SectionCard`: on phones (below `sm`) the header wraps, so actions that do not fit move under the title instead of
6
+ squeezing it into a narrow column. Nothing changes from `sm` up.
7
+ - `Button` / `LinkButton` accept `aria-keyshortcuts`. react-aria drops this attribute from DOM props; it is now written to
8
+ the element through the `render` prop (a `render` you pass yourself still works).
9
+ - `Button` `variant="floating"`: in dark mode it uses the card colour with a light ring instead of the primary colour, so the
10
+ floating button does not look like the page's main action.
11
+
12
+ ## 0.2.0
13
+
14
+ - `Button` `variant="destructive-ghost"`: row delete/remove icons; red text, background only on hover.
15
+ - `TextButton` / `TextLink` `tone="danger"`.
16
+ - `Heading` `size="xs"` for dense tool panels.
17
+ - `Input` `size="xs"` and `mono`; `Textarea` `size="sm"` and `mono` (codes, keys, JSON).
18
+
3
19
  ## 0.1.0
4
20
 
5
21
  - First release: the shared components of the Searvia panel, with `configurePanelKit` for translation, locale and
package/README.md CHANGED
@@ -63,14 +63,14 @@ class at the call site.
63
63
 
64
64
  | What | Component | Options |
65
65
  |---|---|---|
66
- | Button, link button | `ui/button`: `Button`, `LinkButton` | default, outline, secondary, ghost, inverse, light, on-brand, on-brand-soft, floating, warning, caution, destructive, link · xs, sm, default, lg, icon sizes |
67
- | Text button and link | `ui/text-button`: `TextButton`, `TextLink` | primary, muted, brand, warning · xs, sm |
66
+ | Button, link button | `ui/button`: `Button`, `LinkButton` | default, outline, secondary, ghost, inverse, light, on-brand, on-brand-soft, floating, warning, caution, destructive, destructive-ghost, link · xs, sm, default, lg, icon sizes |
67
+ | Text button and link | `ui/text-button`: `TextButton`, `TextLink` | primary, muted, brand, warning, danger · xs, sm |
68
68
  | Clickable row | `ui/row-button`: `RowButton` (`isSelected`), `RowLink` | padding from the caller |
69
69
  | Selectable card | `ui/option-card`: `OptionCard` (`isSelected`) | |
70
70
  | Main choice, filter pill | `ui/segmented`: `Segmented`; `ui/chip`: `Chip` | |
71
71
  | Badge | `status-badge`: `StatusBadge` | success, danger, warning, info, purple, neutral, primary, accent, overlay · sm, default |
72
72
  | Box, section | `ui/tile`: `Tile`, `TooltipBox`; `section-card`: `SectionCard` | default, primary, muted, success, warning, danger, info · sm, default, lg, flush · `interactive`; SectionCard `highlighted`, `flush` |
73
- | Heading | `ui/heading`: `Heading` | mega, hero, display, section, title, subtitle, card, small, base, sm |
73
+ | Heading | `ui/heading`: `Heading` | mega, hero, display, section, title, subtitle, card, small, base, sm, xs |
74
74
  | Icon background | `ui/icon-tile`: `IconTile` | beside text, not above a heading |
75
75
  | Small parts | `ui/code`: `Code`; `ui/meta`: `MetaRow`, `List`, `ListRow` | |
76
76
  | Forms and overlays | `ui/input`, `ui/input-group`, `ui/select`, `ui/combobox`, `ui/textarea`, `ui/checkbox`, `ui/radio-group`, `ui/switch`, `ui/slider`, `ui/field`, `ui/dialog`, `ui/alert-dialog`, `ui/sheet`, `ui/popover`, `ui/dropdown-menu`, `ui/tooltip`, `ui/command` | |
@@ -2,6 +2,7 @@ import { type ReactNode } from 'react';
2
2
  /**
3
3
  * Bölüm kartı: başlık, açıklama, sağda seçenekler.
4
4
  * `flush` gövdeyi kenar boşluksuz bırakır (liste, ızgara, tablo için).
5
+ * Telefonda başlık satırı sarılır: sığmayan seçenekler başlığın altına iner, başlık dar bir sütuna sıkışmaz.
5
6
  */
6
7
  export declare function SectionCard({ title, description, actions, flush, highlighted, className, contentClassName, children, }: {
7
8
  title?: ReactNode;
@@ -4,9 +4,10 @@ import { cn } from './utils.js';
4
4
  /**
5
5
  * Bölüm kartı: başlık, açıklama, sağda seçenekler.
6
6
  * `flush` gövdeyi kenar boşluksuz bırakır (liste, ızgara, tablo için).
7
+ * Telefonda başlık satırı sarılır: sığmayan seçenekler başlığın altına iner, başlık dar bir sütuna sıkışmaz.
7
8
  */
8
9
  export function SectionCard({ title, description, actions, flush = false, highlighted = false, className, contentClassName, children, }) {
9
- return (_jsxs("section", { className: cn('flex flex-col overflow-hidden rounded-3xl bg-card text-card-foreground shadow-sm ring-1 ring-foreground/5 dark:ring-foreground/10', highlighted && 'ring-2 ring-primary/40 dark:ring-primary/40', className), children: [(title || actions) && (_jsxs("header", { className: cn('flex items-start gap-3 px-4 py-3 sm:px-6 sm:py-4', hasContent(children) && 'border-b'), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [title && _jsx("h2", { className: "text-sm font-semibold sm:text-base", children: title }), description && _jsx("p", { className: "mt-0.5 text-xs text-muted-foreground sm:text-sm", children: description })] }), actions && _jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions })] })), hasContent(children) && _jsx("div", { className: cn('flex-1', !flush && 'p-4 sm:p-6', contentClassName), children: children })] }));
10
+ return (_jsxs("section", { className: cn('flex flex-col overflow-hidden rounded-3xl bg-card text-card-foreground shadow-sm ring-1 ring-foreground/5 dark:ring-foreground/10', highlighted && 'ring-2 ring-primary/40 dark:ring-primary/40', className), children: [(title || actions) && (_jsxs("header", { className: cn('flex items-start gap-3 max-sm:flex-wrap px-4 py-3 sm:px-6 sm:py-4', hasContent(children) && 'border-b'), children: [_jsxs("div", { className: "min-w-0 flex-1 max-sm:min-w-[min(100%,12rem)]", children: [title && _jsx("h2", { className: "text-sm font-semibold sm:text-base", children: title }), description && _jsx("p", { className: "mt-0.5 text-xs text-muted-foreground sm:text-sm", children: description })] }), actions && _jsx("div", { className: "flex shrink-0 items-center gap-2 max-sm:max-w-full max-sm:flex-wrap", children: actions })] })), hasContent(children) && _jsx("div", { className: cn('flex-1', !flush && 'p-4 sm:p-6', contentClassName), children: children })] }));
10
11
  }
11
12
  function hasContent(children) {
12
13
  return Children.toArray(children).length > 0;
@@ -2,13 +2,17 @@ import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  import { type ButtonProps as ButtonPrimitiveProps, type LinkProps as LinkPrimitiveProps } from "react-aria-components";
4
4
  declare const buttonVariants: (props?: ({
5
- variant?: "caution" | "default" | "destructive" | "floating" | "ghost" | "inverse" | "light" | "link" | "on-brand" | "on-brand-soft" | "outline" | "secondary" | "warning" | null | undefined;
5
+ variant?: "caution" | "default" | "destructive" | "destructive-ghost" | "floating" | "ghost" | "inverse" | "light" | "link" | "on-brand" | "on-brand-soft" | "outline" | "secondary" | "warning" | null | undefined;
6
6
  size?: "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
- declare function Button({ className, variant, size, children, ...props }: Omit<ButtonPrimitiveProps, "className"> & React.RefAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> & {
8
+ declare function Button({ className, variant, size, children, "aria-keyshortcuts": keyShortcuts, render, ...props }: Omit<ButtonPrimitiveProps, "className"> & React.RefAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> & {
9
9
  className?: string;
10
+ /** Klavye kısayolu, ör. "Meta+K Control+K"; öğeye `aria-keyshortcuts` olarak yazılır. */
11
+ "aria-keyshortcuts"?: string;
10
12
  }): React.JSX.Element;
11
- declare function LinkButton({ className, variant, size, ...props }: Omit<LinkPrimitiveProps, "className"> & VariantProps<typeof buttonVariants> & {
13
+ declare function LinkButton({ className, variant, size, "aria-keyshortcuts": keyShortcuts, render, ...props }: Omit<LinkPrimitiveProps, "className"> & VariantProps<typeof buttonVariants> & {
12
14
  className?: string;
15
+ /** Klavye kısayolu, ör. "Meta+K Control+K"; öğeye `aria-keyshortcuts` olarak yazılır. */
16
+ "aria-keyshortcuts"?: string;
13
17
  }): React.JSX.Element;
14
18
  export { Button, LinkButton, buttonVariants };
package/dist/ui/button.js CHANGED
@@ -16,11 +16,13 @@ const buttonVariants = cva("group/button inline-flex shrink-0 items-center justi
16
16
  // Zamanlama gibi "sonra olacak" eylemleri.
17
17
  caution: "bg-yellow-400 text-yellow-950 hover:bg-yellow-400/85 focus-visible:ring-yellow-400/40",
18
18
  destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
19
+ // Satırdaki sil/kaldır ikonu: zemin yalnız üzerine gelince, yazı her zaman kırmızı.
20
+ "destructive-ghost": "text-destructive hover:bg-destructive/10 hover:text-destructive focus-visible:ring-destructive/20 aria-expanded:bg-destructive/10 dark:hover:bg-destructive/20",
19
21
  link: "text-primary underline-offset-4 hover:underline",
20
22
  // Açık zeminde koyu düğme (sitede ikinci çağrı, yüzen "Searvia'ya sor"); koyu temada marka rengi.
21
23
  inverse: "bg-ink text-white hover:bg-ink/85 dark:bg-primary dark:text-primary-foreground dark:hover:bg-primary/85",
22
- // Sayfanın üstünde yüzen koyu düğme ("Searvia'ya sor").
23
- floating: "bg-ink text-white shadow-lg hover:bg-ink/85 dark:bg-primary dark:text-primary-foreground dark:hover:bg-primary/85",
24
+ // Sayfanın üstünde yüzen koyu düğme ("Searvia'ya sor"); koyu temada kart rengi, sayfanın birincil düğmesiyle karışmaz.
25
+ floating: "bg-ink text-white shadow-lg hover:bg-ink/85 dark:bg-card dark:text-foreground dark:ring-1 dark:ring-foreground/15 dark:hover:bg-muted",
24
26
  // Marka renkli zeminin üstündeki düğmeler (ana sayfa, blog çağrısı): dolu ve saydam.
25
27
  "on-brand": "bg-on-brand text-on-brand-ink hover:bg-on-brand/90",
26
28
  "on-brand-soft": "bg-on-brand/10 text-on-brand ring-1 ring-on-brand/30 hover:bg-on-brand/20",
@@ -56,10 +58,19 @@ function withPendingIcon(children) {
56
58
  }
57
59
  return [_jsx(LoaderCircleIcon, { className: "animate-spin" }, "pending"), ...items];
58
60
  }
59
- function Button({ className, variant = "default", size = "default", children, ...props }) {
60
- return (_jsx(ButtonPrimitive, { "data-slot": "button", "data-variant": variant, "data-size": size, className: cn(buttonVariants({ variant, size, className })), ...props, children: props.isPending && typeof children !== "function" ? withPendingIcon(children) : children }));
61
+ function withKeyShortcuts(tag, shortcuts, render) {
62
+ if (!shortcuts)
63
+ return render;
64
+ return (props, renderProps) => render
65
+ ? React.cloneElement(render(props, renderProps), { "aria-keyshortcuts": shortcuts })
66
+ : React.createElement(tag, { ...props, "aria-keyshortcuts": shortcuts });
61
67
  }
62
- function LinkButton({ className, variant = "default", size = "default", ...props }) {
63
- return (_jsx(LinkPrimitive, { "data-slot": "button", "data-variant": variant, "data-size": size, className: cn(buttonVariants({ variant, size, className })), ...props }));
68
+ function Button({ className, variant = "default", size = "default", children, "aria-keyshortcuts": keyShortcuts, render, ...props }) {
69
+ return (_jsx(ButtonPrimitive, { "data-slot": "button", "data-variant": variant, "data-size": size, className: cn(buttonVariants({ variant, size, className })), render: withKeyShortcuts("button", keyShortcuts, render), ...props, children: props.isPending && typeof children !== "function" ? withPendingIcon(children) : children }));
70
+ }
71
+ function LinkButton({ className, variant = "default", size = "default", "aria-keyshortcuts": keyShortcuts, render, ...props }) {
72
+ // react-aria bağlantısı adres yoksa ya da kapalıysa <span> çizer.
73
+ const tag = props.href && !props.isDisabled ? "a" : "span";
74
+ return (_jsx(LinkPrimitive, { "data-slot": "button", "data-variant": variant, "data-size": size, className: cn(buttonVariants({ variant, size, className })), render: withKeyShortcuts(tag, keyShortcuts, render), ...props }));
64
75
  }
65
76
  export { Button, LinkButton, buttonVariants };
@@ -14,6 +14,7 @@ declare const sizes: {
14
14
  readonly small: 'font-display text-lg tracking-tight';
15
15
  readonly base: 'text-base';
16
16
  readonly sm: 'text-sm';
17
+ readonly xs: 'text-xs';
17
18
  };
18
19
  export type HeadingSize = keyof typeof sizes;
19
20
  export declare function Heading({ as: Tag, size, className, ...props }: React.HTMLAttributes<HTMLHeadingElement> & {
@@ -16,6 +16,8 @@ const sizes = {
16
16
  small: 'font-display text-lg tracking-tight',
17
17
  base: 'text-base',
18
18
  sm: 'text-sm',
19
+ // Yoğun araç panellerindeki küçük bölüm başlığı.
20
+ xs: 'text-xs',
19
21
  };
20
22
  export function Heading({ as: Tag = 'h2', size, className, ...props }) {
21
23
  return _jsx(Tag, { className: cn('font-semibold text-balance', sizes[size], className), ...props });
@@ -1,6 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { Input as InputPrimitive } from "react-aria-components";
3
- declare function Input({ className, type, size, ...props }: Omit<React.ComponentProps<typeof InputPrimitive>, "size"> & {
4
- size?: "default" | "sm";
3
+ declare function Input({ className, type, size, mono, ...props }: Omit<React.ComponentProps<typeof InputPrimitive>, "size"> & {
4
+ size?: "default" | "sm" | "xs";
5
+ /** Kod, anahtar, adres gibi eş aralıklı yazılması gereken değerler. */
6
+ mono?: boolean;
5
7
  }): React.JSX.Element;
6
8
  export { Input };
package/dist/ui/input.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../utils.js";
3
3
  import { composeRenderProps, Input as InputPrimitive, } from "react-aria-components";
4
- function Input({ className, type, size = "default", ...props }) {
5
- return (_jsx(InputPrimitive, { type: type, "data-slot": "input", "data-size": size, className: composeRenderProps(className, (className) => cn("h-9 data-[size=sm]:h-8 data-[size=sm]:text-sm w-full min-w-0 rounded-3xl border border-transparent bg-input/50 px-3 py-1 text-base transition-[color,box-shadow,background-color] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className)), ...props }));
4
+ function Input({ className, type, size = "default", mono = false, ...props }) {
5
+ return (_jsx(InputPrimitive, { type: type, "data-slot": "input", "data-size": size, "data-mono": mono || undefined, className: composeRenderProps(className, (className) => cn("h-9 data-[size=sm]:h-8 data-[size=sm]:text-sm data-[size=xs]:h-7 data-[size=xs]:px-2.5 data-[size=xs]:text-xs data-mono:font-mono w-full min-w-0 rounded-3xl border border-transparent bg-input/50 px-3 py-1 text-base transition-[color,box-shadow,background-color] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className)), ...props }));
6
6
  }
7
7
  export { Input };
@@ -4,6 +4,7 @@ declare const tones: {
4
4
  readonly muted: 'text-muted-foreground hover:text-foreground hover:underline';
5
5
  readonly brand: 'font-medium text-brand-link hover:underline dark:text-primary';
6
6
  readonly warning: 'font-medium text-amber-700 hover:underline dark:text-amber-400';
7
+ readonly danger: 'font-medium text-destructive hover:underline';
7
8
  };
8
9
  /** Bağlantı görünümlü metin düğmesi ("Tüm kelimeler", geliştirici adına göre süz): satır içinde, kutusuz. */
9
10
  export declare function TextButton({ tone, size, className, ...props }: Omit<ButtonProps, 'className'> & {
@@ -7,6 +7,8 @@ const tones = {
7
7
  // Tanıtım sitesinin bağlantı rengi (beyaz zeminde okunaklı marka tonu).
8
8
  brand: 'font-medium text-brand-link hover:underline dark:text-primary',
9
9
  warning: 'font-medium text-amber-700 hover:underline dark:text-amber-400',
10
+ // Başarısız işlemi yeniden deneme gibi hata bağlamındaki metin düğmesi.
11
+ danger: 'font-medium text-destructive hover:underline',
10
12
  };
11
13
  const base = 'inline-flex max-w-full items-center gap-1 rounded-sm text-start underline-offset-4 outline-hidden focus-visible:ring-2 focus-visible:ring-ring/40 [&_svg]:size-3.5 [&_svg]:shrink-0';
12
14
  /** Bağlantı görünümlü metin düğmesi ("Tüm kelimeler", geliştirici adına göre süz): satır içinde, kutusuz. */
@@ -1,4 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { TextArea as TextareaPrimitive } from "react-aria-components";
3
- declare function Textarea({ className, ...props }: React.ComponentProps<typeof TextareaPrimitive>): React.JSX.Element;
3
+ declare function Textarea({ className, size, mono, ...props }: React.ComponentProps<typeof TextareaPrimitive> & {
4
+ /** `sm`: ızgara ve tablo içindeki sıkı alan. */
5
+ size?: "default" | "sm";
6
+ /** Kod, JSON gibi eş aralıklı yazılması gereken değerler. */
7
+ mono?: boolean;
8
+ }): React.JSX.Element;
4
9
  export { Textarea };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../utils.js";
3
3
  import { composeRenderProps, TextArea as TextareaPrimitive, } from "react-aria-components";
4
- function Textarea({ className, ...props }) {
5
- return (_jsx(TextareaPrimitive, { "data-slot": "textarea", className: composeRenderProps(className, (className) => cn("flex field-sizing-content min-h-16 w-full resize-none rounded-2xl border border-transparent bg-input/50 px-3 py-3 text-base transition-[color,box-shadow,background-color] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className)), ...props }));
4
+ function Textarea({ className, size = "default", mono = false, ...props }) {
5
+ return (_jsx(TextareaPrimitive, { "data-slot": "textarea", "data-size": size, "data-mono": mono || undefined, className: composeRenderProps(className, (className) => cn("flex field-sizing-content min-h-16 data-[size=sm]:min-h-9 data-[size=sm]:rounded-xl data-[size=sm]:px-2.5 data-[size=sm]:py-2 data-[size=sm]:text-xs data-mono:font-mono w-full resize-none rounded-2xl border border-transparent bg-input/50 px-3 py-3 text-base transition-[color,box-shadow,background-color] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className)), ...props }));
6
6
  }
7
7
  export { Textarea };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enmera/panel-kit",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Shared components for admin panels built with React, react-aria and Tailwind CSS: one look for every button, badge, box and heading.",
5
5
  "type": "module",
6
6
  "license": "MIT",