kaze-design-system 0.2.6 → 0.3.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/components.css CHANGED
@@ -3246,17 +3246,46 @@
3246
3246
  gap: var(--space-3);
3247
3247
  }
3248
3248
 
3249
+ .empty-state--sm {
3250
+ padding: var(--space-3) var(--space-2);
3251
+ gap: var(--space-1);
3252
+ }
3253
+
3254
+ .empty-state--md {
3255
+ padding: var(--space-8) var(--space-4);
3256
+ gap: var(--space-2);
3257
+ }
3258
+
3259
+ .empty-state--lg {
3260
+ padding: var(--space-12) var(--space-6);
3261
+ gap: var(--space-3);
3262
+ }
3263
+
3249
3264
  .empty-state__icon {
3250
3265
  color: var(--color-fg-tertiary);
3251
3266
  margin-bottom: var(--space-2);
3252
3267
  }
3253
3268
 
3269
+ .empty-state--sm .empty-state__icon {
3270
+ margin-bottom: 0;
3271
+ }
3272
+
3254
3273
  .empty-state__title {
3255
3274
  font-size: var(--font-size-lg);
3256
3275
  font-weight: var(--font-weight-semibold);
3257
3276
  color: var(--color-fg);
3258
3277
  }
3259
3278
 
3279
+ .empty-state--sm .empty-state__title {
3280
+ font-size: var(--font-size-sm);
3281
+ font-weight: var(--font-weight-medium);
3282
+ color: var(--color-fg-secondary);
3283
+ }
3284
+
3285
+ .empty-state--md .empty-state__title {
3286
+ font-size: var(--font-size-base);
3287
+ }
3288
+
3260
3289
  .empty-state__description {
3261
3290
  font-size: var(--font-size-sm);
3262
3291
  color: var(--color-fg-secondary);
@@ -3264,6 +3293,10 @@
3264
3293
  line-height: var(--line-height-relaxed);
3265
3294
  }
3266
3295
 
3296
+ .empty-state--sm .empty-state__description {
3297
+ font-size: var(--font-size-xs);
3298
+ }
3299
+
3267
3300
  .empty-state__actions {
3268
3301
  display: flex;
3269
3302
  gap: var(--space-3);
@@ -3955,3 +3988,83 @@
3955
3988
  font-size: var(--font-size-sm);
3956
3989
  color: var(--color-fg-tertiary);
3957
3990
  }
3991
+
3992
+ /* ================================================================
3993
+ HelpButton
3994
+ ================================================================ */
3995
+
3996
+ .help-button {
3997
+ display: inline-flex;
3998
+ align-items: center;
3999
+ justify-content: center;
4000
+ width: 18px;
4001
+ height: 18px;
4002
+ padding: 0;
4003
+ border: 1px solid var(--color-border);
4004
+ border-radius: var(--radius-full);
4005
+ background: var(--color-bg);
4006
+ color: var(--color-fg-tertiary);
4007
+ font-size: 11px;
4008
+ font-weight: var(--font-weight-medium);
4009
+ line-height: 1;
4010
+ cursor: pointer;
4011
+ transition: color var(--duration-fast) var(--ease-out),
4012
+ border-color var(--duration-fast) var(--ease-out),
4013
+ background-color var(--duration-fast) var(--ease-out);
4014
+ }
4015
+
4016
+ .help-button:hover,
4017
+ .help-button[aria-expanded="true"] {
4018
+ color: var(--color-fg);
4019
+ border-color: var(--color-border-strong, var(--color-fg-tertiary));
4020
+ background: var(--color-bg-subtle, var(--color-bg));
4021
+ }
4022
+
4023
+ .help-button:focus-visible {
4024
+ outline: 2px solid var(--color-focus, var(--color-primary));
4025
+ outline-offset: 2px;
4026
+ }
4027
+
4028
+ .help-popover {
4029
+ position: absolute;
4030
+ z-index: 1000;
4031
+ background: var(--color-surface, var(--color-bg));
4032
+ border: 1px solid var(--color-border);
4033
+ border-radius: var(--radius-md);
4034
+ box-shadow: var(--shadow-lg);
4035
+ padding: var(--space-3) var(--space-4);
4036
+ font-size: var(--font-size-sm);
4037
+ color: var(--color-fg);
4038
+ line-height: var(--line-height-relaxed);
4039
+ }
4040
+
4041
+ .help-popover--sm {
4042
+ max-width: 220px;
4043
+ }
4044
+
4045
+ .help-popover--md {
4046
+ max-width: 300px;
4047
+ }
4048
+
4049
+ .help-popover--lg {
4050
+ max-width: 420px;
4051
+ }
4052
+
4053
+ .help-popover__title {
4054
+ font-size: var(--font-size-sm);
4055
+ font-weight: var(--font-weight-semibold);
4056
+ color: var(--color-fg);
4057
+ margin: 0 0 var(--space-2) 0;
4058
+ }
4059
+
4060
+ .help-popover__body {
4061
+ color: var(--color-fg-secondary);
4062
+ }
4063
+
4064
+ .help-popover__body p {
4065
+ margin: 0 0 var(--space-2) 0;
4066
+ }
4067
+
4068
+ .help-popover__body p:last-child {
4069
+ margin-bottom: 0;
4070
+ }
@@ -2,12 +2,21 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { cn } from "../../lib/utils.js";
4
4
  const EmptyState = forwardRef(
5
- ({ icon, title, description, actions, className, ...rest }, ref) => /* @__PURE__ */ jsxs("div", { ref, className: cn("empty-state", className), ...rest, children: [
6
- icon && /* @__PURE__ */ jsx("span", { className: "empty-state__icon", children: icon }),
7
- /* @__PURE__ */ jsx("h3", { className: "empty-state__title", children: title }),
8
- description && /* @__PURE__ */ jsx("p", { className: "empty-state__description", children: description }),
9
- actions && /* @__PURE__ */ jsx("div", { className: "empty-state__actions", children: actions })
10
- ] })
5
+ ({ icon, title, description, actions, size = "md", className, ...rest }, ref) => /* @__PURE__ */ jsxs(
6
+ "div",
7
+ {
8
+ ref,
9
+ role: "status",
10
+ className: cn("empty-state", `empty-state--${size}`, className),
11
+ ...rest,
12
+ children: [
13
+ icon && /* @__PURE__ */ jsx("span", { className: "empty-state__icon", children: icon }),
14
+ /* @__PURE__ */ jsx("h3", { className: "empty-state__title", children: title }),
15
+ description && /* @__PURE__ */ jsx("p", { className: "empty-state__description", children: description }),
16
+ actions && /* @__PURE__ */ jsx("div", { className: "empty-state__actions", children: actions })
17
+ ]
18
+ }
19
+ )
11
20
  );
12
21
  EmptyState.displayName = "EmptyState";
13
22
  export {
@@ -0,0 +1,127 @@
1
+ "use client";
2
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
3
+ import { forwardRef, useState, useRef, useId, useCallback, useEffect } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { cn } from "../../lib/utils.js";
6
+ const GAP = 8;
7
+ function computePosition(trigger, pop, side) {
8
+ const sy = window.scrollY;
9
+ const sx = window.scrollX;
10
+ switch (side) {
11
+ case "top":
12
+ return {
13
+ top: trigger.top - pop.height - GAP + sy,
14
+ left: trigger.left + trigger.width / 2 - pop.width / 2 + sx
15
+ };
16
+ case "bottom":
17
+ return {
18
+ top: trigger.bottom + GAP + sy,
19
+ left: trigger.left + trigger.width / 2 - pop.width / 2 + sx
20
+ };
21
+ case "left":
22
+ return {
23
+ top: trigger.top + trigger.height / 2 - pop.height / 2 + sy,
24
+ left: trigger.left - pop.width - GAP + sx
25
+ };
26
+ case "right":
27
+ return {
28
+ top: trigger.top + trigger.height / 2 - pop.height / 2 + sy,
29
+ left: trigger.right + GAP + sx
30
+ };
31
+ }
32
+ }
33
+ const HelpButton = forwardRef(
34
+ ({
35
+ title,
36
+ children,
37
+ position = "bottom",
38
+ size = "md",
39
+ icon,
40
+ ariaLabel = "ヘルプ",
41
+ className
42
+ }, ref) => {
43
+ const [open, setOpen] = useState(false);
44
+ const [coords, setCoords] = useState({
45
+ top: 0,
46
+ left: 0
47
+ });
48
+ const triggerRef = useRef(null);
49
+ const popRef = useRef(null);
50
+ const popoverId = useId();
51
+ const updatePosition = useCallback(() => {
52
+ if (!triggerRef.current || !popRef.current) return;
53
+ const t = triggerRef.current.getBoundingClientRect();
54
+ const p = popRef.current.getBoundingClientRect();
55
+ setCoords(computePosition(t, p, position));
56
+ }, [position]);
57
+ useEffect(() => {
58
+ if (!open) return;
59
+ requestAnimationFrame(updatePosition);
60
+ const onClickOutside = (e) => {
61
+ const target = e.target;
62
+ if (popRef.current?.contains(target) || triggerRef.current?.contains(target)) {
63
+ return;
64
+ }
65
+ setOpen(false);
66
+ };
67
+ const onKeyDown = (e) => {
68
+ if (e.key === "Escape") {
69
+ setOpen(false);
70
+ triggerRef.current?.focus();
71
+ }
72
+ };
73
+ const onScroll = () => updatePosition();
74
+ document.addEventListener("mousedown", onClickOutside);
75
+ document.addEventListener("keydown", onKeyDown);
76
+ window.addEventListener("scroll", onScroll, true);
77
+ window.addEventListener("resize", onScroll);
78
+ return () => {
79
+ document.removeEventListener("mousedown", onClickOutside);
80
+ document.removeEventListener("keydown", onKeyDown);
81
+ window.removeEventListener("scroll", onScroll, true);
82
+ window.removeEventListener("resize", onScroll);
83
+ };
84
+ }, [open, updatePosition]);
85
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ /* @__PURE__ */ jsx(
87
+ "button",
88
+ {
89
+ type: "button",
90
+ ref: (node) => {
91
+ triggerRef.current = node;
92
+ if (typeof ref === "function") ref(node);
93
+ else if (ref) ref.current = node;
94
+ },
95
+ className: cn("help-button", className),
96
+ "aria-label": ariaLabel,
97
+ "aria-expanded": open,
98
+ "aria-controls": open ? popoverId : void 0,
99
+ onClick: () => setOpen((v) => !v),
100
+ children: icon ?? "?"
101
+ }
102
+ ),
103
+ open && typeof document !== "undefined" && createPortal(
104
+ /* @__PURE__ */ jsxs(
105
+ "div",
106
+ {
107
+ ref: popRef,
108
+ id: popoverId,
109
+ role: "dialog",
110
+ "aria-modal": "false",
111
+ className: cn("help-popover", `help-popover--${size}`),
112
+ style: { top: coords.top, left: coords.left },
113
+ children: [
114
+ title && /* @__PURE__ */ jsx("p", { className: "help-popover__title", children: title }),
115
+ /* @__PURE__ */ jsx("div", { className: "help-popover__body", children })
116
+ ]
117
+ }
118
+ ),
119
+ document.body
120
+ )
121
+ ] });
122
+ }
123
+ );
124
+ HelpButton.displayName = "HelpButton";
125
+ export {
126
+ HelpButton
127
+ };
@@ -0,0 +1,31 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { Badge } from "../Badge/Badge.js";
4
+ const STATUS_CONFIG = {
5
+ live: { variant: "positive", label: "自動更新" },
6
+ stale: { variant: "warning", label: "要更新" },
7
+ missing: { variant: "negative", label: "欠損" },
8
+ manual: { variant: "default", label: "手動" },
9
+ loading: { variant: "info", label: "取得中" }
10
+ };
11
+ const StatusBadge = forwardRef(
12
+ ({ status, label, dot = true, live, ...rest }, ref) => {
13
+ const config = STATUS_CONFIG[status];
14
+ const isLive = live ?? (status === "live" || status === "loading");
15
+ return /* @__PURE__ */ jsx(
16
+ Badge,
17
+ {
18
+ ref,
19
+ variant: config.variant,
20
+ dot,
21
+ live: isLive,
22
+ ...rest,
23
+ children: label ?? config.label
24
+ }
25
+ );
26
+ }
27
+ );
28
+ StatusBadge.displayName = "StatusBadge";
29
+ export {
30
+ StatusBadge
31
+ };
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useState, useCallback, useMemo } from "react";
4
+ function useLegendToggle() {
5
+ const [hiddenSeries, setHiddenSeries] = useState(/* @__PURE__ */ new Set());
6
+ const toggle = useCallback((dataKey) => {
7
+ setHiddenSeries((prev) => {
8
+ const next = new Set(prev);
9
+ if (next.has(dataKey)) next.delete(dataKey);
10
+ else next.add(dataKey);
11
+ return next;
12
+ });
13
+ }, []);
14
+ const isHidden = useCallback(
15
+ (dataKey) => hiddenSeries.has(dataKey),
16
+ [hiddenSeries]
17
+ );
18
+ const reset = useCallback(() => setHiddenSeries(/* @__PURE__ */ new Set()), []);
19
+ const legendProps = useMemo(
20
+ () => ({
21
+ wrapperStyle: {
22
+ fontSize: 12,
23
+ cursor: "pointer"
24
+ },
25
+ onClick: (e) => {
26
+ if (e?.dataKey != null) toggle(String(e.dataKey));
27
+ },
28
+ formatter: (value, entry) => {
29
+ const key = entry?.dataKey != null ? String(entry.dataKey) : "";
30
+ const hidden = hiddenSeries.has(key);
31
+ return /* @__PURE__ */ jsx(
32
+ "span",
33
+ {
34
+ style: {
35
+ color: hidden ? "var(--color-fg-tertiary, #a1a1aa)" : entry?.color,
36
+ transition: "color 0.15s ease"
37
+ },
38
+ children: value
39
+ }
40
+ );
41
+ }
42
+ }),
43
+ [hiddenSeries, toggle]
44
+ );
45
+ return { isHidden, toggle, reset, legendProps };
46
+ }
47
+ export {
48
+ useLegendToggle
49
+ };
package/dist/hooks.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { ThemeProvider, useTheme } from "./hooks/useTheme.js";
3
3
  import { useFocusTrap } from "./hooks/useFocusTrap.js";
4
+ import { useLegendToggle } from "./hooks/useLegendToggle.js";
4
5
  export {
5
6
  ThemeProvider,
6
7
  useFocusTrap,
8
+ useLegendToggle,
7
9
  useTheme
8
10
  };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ import { FilterPill } from "./components/FilterPill/FilterPill.js";
25
25
  import { FormField } from "./components/FormField/FormField.js";
26
26
  import { Grid } from "./components/Grid/Grid.js";
27
27
  import { Heading } from "./components/Heading/Heading.js";
28
+ import { HelpButton } from "./components/HelpButton/HelpButton.js";
28
29
  import { Hero } from "./components/Hero/Hero.js";
29
30
  import { Icon } from "./components/Icon/Icon.js";
30
31
  import { Input } from "./components/Input/Input.js";
@@ -47,6 +48,7 @@ import { Skeleton } from "./components/Skeleton/Skeleton.js";
47
48
  import { Sparkline } from "./components/Sparkline/Sparkline.js";
48
49
  import { SplitSection } from "./components/SplitSection/SplitSection.js";
49
50
  import { StatItem, Stats } from "./components/Stats/Stats.js";
51
+ import { StatusBadge } from "./components/StatusBadge/StatusBadge.js";
50
52
  import { Stepper } from "./components/Stepper/Stepper.js";
51
53
  import { Switch } from "./components/Switch/Switch.js";
52
54
  import { Tab, TabGroup, TabPanel, Tabs } from "./components/Tabs/Tabs.js";
@@ -102,6 +104,7 @@ export {
102
104
  FormField,
103
105
  Grid,
104
106
  Heading,
107
+ HelpButton,
105
108
  Hero,
106
109
  Icon,
107
110
  Input,
@@ -134,6 +137,7 @@ export {
134
137
  SplitSection,
135
138
  StatItem,
136
139
  Stats,
140
+ StatusBadge,
137
141
  Stepper,
138
142
  Switch,
139
143
  Tab,
@@ -1,8 +1,10 @@
1
1
  import { type ReactNode, type HTMLAttributes } from "react";
2
+ export type EmptyStateSize = "sm" | "md" | "lg";
2
3
  export interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> {
3
4
  icon?: ReactNode;
4
5
  title: string;
5
- description?: string;
6
+ description?: ReactNode;
6
7
  actions?: ReactNode;
8
+ size?: EmptyStateSize;
7
9
  }
8
10
  export declare const EmptyState: import("react").ForwardRefExoticComponent<EmptyStateProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,2 +1,2 @@
1
1
  export { EmptyState } from "./EmptyState";
2
- export type { EmptyStateProps } from "./EmptyState";
2
+ export type { EmptyStateProps, EmptyStateSize } from "./EmptyState";
@@ -0,0 +1,19 @@
1
+ import { type ReactNode } from "react";
2
+ export type HelpButtonPosition = "top" | "bottom" | "left" | "right";
3
+ export type HelpButtonSize = "sm" | "md" | "lg";
4
+ export interface HelpButtonProps {
5
+ /** Popover title */
6
+ title?: string;
7
+ /** Popover body content */
8
+ children: ReactNode;
9
+ /** Preferred side the popover appears on */
10
+ position?: HelpButtonPosition;
11
+ /** Popover width */
12
+ size?: HelpButtonSize;
13
+ /** Icon shown inside the trigger button (defaults to "?") */
14
+ icon?: ReactNode;
15
+ /** Accessible label for the trigger */
16
+ ariaLabel?: string;
17
+ className?: string;
18
+ }
19
+ export declare const HelpButton: import("react").ForwardRefExoticComponent<HelpButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,2 @@
1
+ export { HelpButton } from "./HelpButton";
2
+ export type { HelpButtonProps, HelpButtonPosition, HelpButtonSize, } from "./HelpButton";
@@ -0,0 +1,8 @@
1
+ import { type BadgeProps } from "../Badge";
2
+ export type StatusBadgeStatus = "live" | "stale" | "missing" | "manual" | "loading";
3
+ export interface StatusBadgeProps extends Omit<BadgeProps, "variant" | "children"> {
4
+ status: StatusBadgeStatus;
5
+ /** Overrides the default label for the given status */
6
+ label?: string;
7
+ }
8
+ export declare const StatusBadge: import("react").ForwardRefExoticComponent<StatusBadgeProps & import("react").RefAttributes<HTMLSpanElement>>;
@@ -0,0 +1,2 @@
1
+ export { StatusBadge } from "./StatusBadge";
2
+ export type { StatusBadgeProps, StatusBadgeStatus } from "./StatusBadge";
@@ -6,6 +6,10 @@ export { Card, CardHeader, CardTitle, CardDescription, CardBody, CardFooter, } f
6
6
  export type { CardProps, CardVariant, CardHeaderProps, CardTitleProps, CardDescriptionProps, CardBodyProps, CardFooterProps, } from "./Card";
7
7
  export { Badge } from "./Badge";
8
8
  export type { BadgeProps, BadgeVariant } from "./Badge";
9
+ export { StatusBadge } from "./StatusBadge";
10
+ export type { StatusBadgeProps, StatusBadgeStatus } from "./StatusBadge";
11
+ export { HelpButton } from "./HelpButton";
12
+ export type { HelpButtonProps, HelpButtonPosition, HelpButtonSize, } from "./HelpButton";
9
13
  export { FilterPill } from "./FilterPill";
10
14
  export type { FilterPillProps } from "./FilterPill";
11
15
  export { Input } from "./Input";
@@ -79,7 +83,7 @@ export type { SwitchProps } from "./Switch";
79
83
  export { Skeleton } from "./Skeleton";
80
84
  export type { SkeletonProps } from "./Skeleton";
81
85
  export { EmptyState } from "./EmptyState";
82
- export type { EmptyStateProps } from "./EmptyState";
86
+ export type { EmptyStateProps, EmptyStateSize } from "./EmptyState";
83
87
  export { Breadcrumb } from "./Breadcrumb";
84
88
  export type { BreadcrumbProps, BreadcrumbItem } from "./Breadcrumb";
85
89
  export { Pagination } from "./Pagination";
@@ -1,3 +1,5 @@
1
1
  export { useTheme, ThemeProvider } from "./useTheme";
2
2
  export type { Theme, ThemeContextValue, ThemeProviderProps } from "./useTheme";
3
3
  export { useFocusTrap } from "./useFocusTrap";
4
+ export { useLegendToggle } from "./useLegendToggle";
5
+ export type { UseLegendToggleResult } from "./useLegendToggle";
@@ -0,0 +1,38 @@
1
+ import { type CSSProperties } from "react";
2
+ interface RechartsLegendClickEvent {
3
+ dataKey?: string | number;
4
+ value?: string;
5
+ color?: string;
6
+ [key: string]: unknown;
7
+ }
8
+ export interface UseLegendToggleResult {
9
+ /** Whether a given dataKey is currently hidden */
10
+ isHidden: (dataKey: string) => boolean;
11
+ /** Toggle visibility for a given dataKey */
12
+ toggle: (dataKey: string) => void;
13
+ /** Reset all series to visible */
14
+ reset: () => void;
15
+ /** Props to spread onto a Recharts `<Legend />` */
16
+ legendProps: {
17
+ wrapperStyle: CSSProperties;
18
+ onClick: (e: RechartsLegendClickEvent) => void;
19
+ formatter: (value: string, entry: {
20
+ dataKey?: string | number;
21
+ color?: string;
22
+ }) => React.ReactNode;
23
+ };
24
+ }
25
+ /**
26
+ * Hook for toggling Recharts legend entries to hide/show individual series.
27
+ *
28
+ * @example
29
+ * const { isHidden, legendProps } = useLegendToggle();
30
+ * return (
31
+ * <BarChart data={data}>
32
+ * <Legend {...legendProps} />
33
+ * <Bar dataKey="revenue" hide={isHidden("revenue")} />
34
+ * </BarChart>
35
+ * );
36
+ */
37
+ export declare function useLegendToggle(): UseLegendToggleResult;
38
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaze-design-system",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",