@stratakit/structures 0.2.4 → 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/dist/Banner.js CHANGED
@@ -4,69 +4,170 @@ import { Role } from "@ariakit/react/role";
4
4
  import { IconButton, Text } from "@stratakit/bricks";
5
5
  import { GhostAligner } from "@stratakit/bricks/secret-internals";
6
6
  import { Icon } from "@stratakit/foundations";
7
- import { forwardRef } from "@stratakit/foundations/secret-internals";
7
+ import {
8
+ forwardRef,
9
+ useSafeContext
10
+ } from "@stratakit/foundations/secret-internals";
8
11
  import cx from "classnames";
12
+ import { createStore, useStore } from "zustand";
13
+ import { combine } from "zustand/middleware";
9
14
  import { Dismiss, StatusIcon } from "./~utils.icons.js";
15
+ function createBannerStore(initialState) {
16
+ return createStore(
17
+ combine(initialState, (set, _, store) => ({
18
+ setLabelId: (labelId) => {
19
+ set({ labelId: labelId || store.getInitialState().labelId });
20
+ }
21
+ }))
22
+ );
23
+ }
24
+ const BannerContext = React.createContext(void 0);
25
+ function BannerProvider(props) {
26
+ const [store] = React.useState(
27
+ () => createBannerStore({
28
+ tone: props.tone
29
+ })
30
+ );
31
+ return /* @__PURE__ */ jsx(BannerContext.Provider, { value: store, children: props.children });
32
+ }
33
+ function useBannerState(selectorFn) {
34
+ const store = useSafeContext(BannerContext);
35
+ return useStore(store, selectorFn);
36
+ }
37
+ const BannerRoot = forwardRef((props, forwardedRef) => {
38
+ const { tone = "neutral", variant = "outline", ...rest } = props;
39
+ return /* @__PURE__ */ jsx(BannerProvider, { tone, children: /* @__PURE__ */ jsx(
40
+ Role,
41
+ {
42
+ ...rest,
43
+ "data-kiwi-tone": tone,
44
+ "data-kiwi-variant": variant,
45
+ className: cx("\u{1F95D}-banner", props.className),
46
+ ref: forwardedRef
47
+ }
48
+ ) });
49
+ });
50
+ const BannerIcon = forwardRef((props, forwardedRef) => {
51
+ const tone = useBannerState((state) => state.tone);
52
+ const hasDefaultIcon = props.href === void 0 && tone !== "neutral";
53
+ const {
54
+ render = hasDefaultIcon ? /* @__PURE__ */ jsx(StatusIcon, { tone }) : void 0,
55
+ ...rest
56
+ } = props;
57
+ return /* @__PURE__ */ jsx(
58
+ Icon,
59
+ {
60
+ ...rest,
61
+ render,
62
+ className: cx("\u{1F95D}-banner-icon", props.className),
63
+ ref: forwardedRef
64
+ }
65
+ );
66
+ });
67
+ const BannerLabel = forwardRef(
68
+ (props, forwardedRef) => {
69
+ const defaultLabelId = React.useId();
70
+ const labelId = useBannerState((state) => state.labelId);
71
+ const setLabelId = useBannerState((state) => state.setLabelId);
72
+ const id = props.id ?? defaultLabelId;
73
+ React.useEffect(() => {
74
+ setLabelId(id);
75
+ return () => setLabelId(void 0);
76
+ }, [setLabelId, id]);
77
+ return /* @__PURE__ */ jsx(
78
+ Text,
79
+ {
80
+ id: labelId,
81
+ render: /* @__PURE__ */ jsx("span", {}),
82
+ ...props,
83
+ className: cx("\u{1F95D}-banner-label", props.className),
84
+ variant: "body-sm",
85
+ ref: forwardedRef
86
+ }
87
+ );
88
+ }
89
+ );
90
+ const BannerMessage = forwardRef(
91
+ (props, forwardedRef) => {
92
+ return /* @__PURE__ */ jsx(
93
+ Text,
94
+ {
95
+ ...props,
96
+ variant: "body-sm",
97
+ className: cx("\u{1F95D}-banner-message", props.className),
98
+ ref: forwardedRef
99
+ }
100
+ );
101
+ }
102
+ );
103
+ const BannerActions = forwardRef(
104
+ (props, forwardedRef) => {
105
+ return /* @__PURE__ */ jsx(
106
+ Role.div,
107
+ {
108
+ ...props,
109
+ className: cx("\u{1F95D}-banner-actions", props.className),
110
+ ref: forwardedRef
111
+ }
112
+ );
113
+ }
114
+ );
115
+ const BannerDismissButton = forwardRef(
116
+ (props, forwardedRef) => {
117
+ const { label = "Dismiss", ...rest } = props;
118
+ const labelId = useBannerState((state) => state.labelId);
119
+ const defaultId = React.useId();
120
+ const id = props.id ?? defaultId;
121
+ return /* @__PURE__ */ jsx(GhostAligner, { align: "block", children: /* @__PURE__ */ jsx(
122
+ IconButton,
123
+ {
124
+ ...rest,
125
+ id,
126
+ className: cx("\u{1F95D}-banner-dismiss-button", props.className),
127
+ variant: "ghost",
128
+ label,
129
+ "aria-labelledby": `${id} ${labelId || ""}`,
130
+ icon: /* @__PURE__ */ jsx(Dismiss, {}),
131
+ ref: forwardedRef
132
+ }
133
+ ) });
134
+ }
135
+ );
10
136
  const Banner = forwardRef((props, forwardedRef) => {
11
137
  const {
12
138
  message,
13
139
  label,
14
140
  actions,
15
141
  onDismiss,
142
+ icon,
16
143
  tone = "neutral",
17
- icon = tone !== "neutral" ? /* @__PURE__ */ jsx(StatusIcon, { tone }) : void 0,
18
- variant = "outline",
19
144
  ...rest
20
145
  } = props;
21
- const baseId = React.useId();
22
- const labelId = `${baseId}-label`;
23
- const dismissId = `${baseId}-dismiss`;
24
- return /* @__PURE__ */ jsxs(
25
- Role,
26
- {
27
- ...rest,
28
- "data-kiwi-tone": tone,
29
- "data-kiwi-variant": variant,
30
- className: cx("\u{1F95D}-banner", props.className),
31
- ref: forwardedRef,
32
- children: [
33
- icon ? /* @__PURE__ */ jsx(
34
- Icon,
35
- {
36
- className: "\u{1F95D}-banner-icon",
37
- href: typeof icon === "string" ? icon : void 0,
38
- render: React.isValidElement(icon) ? icon : void 0
39
- }
40
- ) : null,
41
- /* @__PURE__ */ jsx(
42
- Text,
43
- {
44
- className: "\u{1F95D}-banner-label",
45
- id: labelId,
46
- variant: "body-sm",
47
- render: React.isValidElement(label) ? label : /* @__PURE__ */ jsx("span", {}),
48
- children: !React.isValidElement(label) ? label : void 0
49
- }
50
- ),
51
- /* @__PURE__ */ jsx(Text, { render: /* @__PURE__ */ jsx("div", {}), variant: "body-sm", className: "\u{1F95D}-banner-message", children: message }),
52
- actions != null ? /* @__PURE__ */ jsx("div", { className: "\u{1F95D}-banner-actions", children: actions }) : null,
53
- onDismiss ? /* @__PURE__ */ jsx(GhostAligner, { align: "block", children: /* @__PURE__ */ jsx(
54
- IconButton,
55
- {
56
- id: dismissId,
57
- className: "\u{1F95D}-banner-dismiss-button",
58
- variant: "ghost",
59
- label: "Dismiss",
60
- "aria-labelledby": `${dismissId} ${labelId}`,
61
- icon: /* @__PURE__ */ jsx(Dismiss, {}),
62
- onClick: onDismiss
63
- }
64
- ) }) : null
65
- ]
66
- }
146
+ const shouldRenderIcon = React.useMemo(
147
+ () => icon !== void 0 || tone !== "neutral",
148
+ [icon, tone]
67
149
  );
150
+ return /* @__PURE__ */ jsxs(BannerRoot, { tone, ...rest, ref: forwardedRef, children: [
151
+ shouldRenderIcon ? /* @__PURE__ */ jsx(
152
+ BannerIcon,
153
+ {
154
+ href: typeof icon === "string" ? icon : void 0,
155
+ render: React.isValidElement(icon) ? icon : void 0
156
+ }
157
+ ) : null,
158
+ /* @__PURE__ */ jsx(BannerLabel, { render: React.isValidElement(label) ? label : void 0, children: label }),
159
+ /* @__PURE__ */ jsx(BannerMessage, { children: message }),
160
+ actions != null ? /* @__PURE__ */ jsx(BannerActions, { children: actions }) : null,
161
+ onDismiss ? /* @__PURE__ */ jsx(BannerDismissButton, { onClick: onDismiss }) : null
162
+ ] });
68
163
  });
69
164
  var Banner_default = Banner;
70
165
  export {
166
+ BannerActions as Actions,
167
+ BannerDismissButton as DismissButton,
168
+ BannerIcon as Icon,
169
+ BannerLabel as Label,
170
+ BannerMessage as Message,
171
+ BannerRoot as Root,
71
172
  Banner_default as default
72
173
  };
@@ -4,70 +4,177 @@ import { Role } from "@ariakit/react/role";
4
4
  import { IconButton, Text } from "@stratakit/bricks";
5
5
  import { GhostAligner } from "@stratakit/bricks/secret-internals";
6
6
  import { Icon } from "@stratakit/foundations";
7
- import { forwardRef } from "@stratakit/foundations/secret-internals";
7
+ import {
8
+ forwardRef,
9
+ useSafeContext
10
+ } from "@stratakit/foundations/secret-internals";
8
11
  import cx from "classnames";
12
+ import { createStore, useStore } from "zustand";
13
+ import { combine } from "zustand/middleware";
9
14
  import { Dismiss, StatusIcon } from "./~utils.icons.js";
15
+ function createBannerStore(initialState) {
16
+ return createStore(
17
+ combine(initialState, (set, _, store) => ({
18
+ setLabelId: (labelId) => {
19
+ set({ labelId: labelId || store.getInitialState().labelId });
20
+ }
21
+ }))
22
+ );
23
+ }
24
+ const BannerContext = React.createContext(void 0);
25
+ function BannerProvider(props) {
26
+ const [store] = React.useState(
27
+ () => createBannerStore({
28
+ tone: props.tone
29
+ })
30
+ );
31
+ return /* @__PURE__ */ jsx(BannerContext.Provider, { value: store, children: props.children });
32
+ }
33
+ function useBannerState(selectorFn) {
34
+ const store = useSafeContext(BannerContext);
35
+ return useStore(store, selectorFn);
36
+ }
37
+ const BannerRoot = forwardRef((props, forwardedRef) => {
38
+ const { tone = "neutral", variant = "outline", ...rest } = props;
39
+ return /* @__PURE__ */ jsx(BannerProvider, { tone, children: /* @__PURE__ */ jsx(
40
+ Role,
41
+ {
42
+ ...rest,
43
+ "data-kiwi-tone": tone,
44
+ "data-kiwi-variant": variant,
45
+ className: cx("\u{1F95D}-banner", props.className),
46
+ ref: forwardedRef
47
+ }
48
+ ) });
49
+ });
50
+ DEV: BannerRoot.displayName = "Banner.Root";
51
+ const BannerIcon = forwardRef((props, forwardedRef) => {
52
+ const tone = useBannerState((state) => state.tone);
53
+ const hasDefaultIcon = props.href === void 0 && tone !== "neutral";
54
+ const {
55
+ render = hasDefaultIcon ? /* @__PURE__ */ jsx(StatusIcon, { tone }) : void 0,
56
+ ...rest
57
+ } = props;
58
+ return /* @__PURE__ */ jsx(
59
+ Icon,
60
+ {
61
+ ...rest,
62
+ render,
63
+ className: cx("\u{1F95D}-banner-icon", props.className),
64
+ ref: forwardedRef
65
+ }
66
+ );
67
+ });
68
+ DEV: BannerIcon.displayName = "Banner.Icon";
69
+ const BannerLabel = forwardRef(
70
+ (props, forwardedRef) => {
71
+ const defaultLabelId = React.useId();
72
+ const labelId = useBannerState((state) => state.labelId);
73
+ const setLabelId = useBannerState((state) => state.setLabelId);
74
+ const id = props.id ?? defaultLabelId;
75
+ React.useEffect(() => {
76
+ setLabelId(id);
77
+ return () => setLabelId(void 0);
78
+ }, [setLabelId, id]);
79
+ return /* @__PURE__ */ jsx(
80
+ Text,
81
+ {
82
+ id: labelId,
83
+ render: /* @__PURE__ */ jsx("span", {}),
84
+ ...props,
85
+ className: cx("\u{1F95D}-banner-label", props.className),
86
+ variant: "body-sm",
87
+ ref: forwardedRef
88
+ }
89
+ );
90
+ }
91
+ );
92
+ DEV: BannerLabel.displayName = "Banner.Label";
93
+ const BannerMessage = forwardRef(
94
+ (props, forwardedRef) => {
95
+ return /* @__PURE__ */ jsx(
96
+ Text,
97
+ {
98
+ ...props,
99
+ variant: "body-sm",
100
+ className: cx("\u{1F95D}-banner-message", props.className),
101
+ ref: forwardedRef
102
+ }
103
+ );
104
+ }
105
+ );
106
+ DEV: BannerMessage.displayName = "Banner.Message";
107
+ const BannerActions = forwardRef(
108
+ (props, forwardedRef) => {
109
+ return /* @__PURE__ */ jsx(
110
+ Role.div,
111
+ {
112
+ ...props,
113
+ className: cx("\u{1F95D}-banner-actions", props.className),
114
+ ref: forwardedRef
115
+ }
116
+ );
117
+ }
118
+ );
119
+ DEV: BannerActions.displayName = "Banner.Actions";
120
+ const BannerDismissButton = forwardRef(
121
+ (props, forwardedRef) => {
122
+ const { label = "Dismiss", ...rest } = props;
123
+ const labelId = useBannerState((state) => state.labelId);
124
+ const defaultId = React.useId();
125
+ const id = props.id ?? defaultId;
126
+ return /* @__PURE__ */ jsx(GhostAligner, { align: "block", children: /* @__PURE__ */ jsx(
127
+ IconButton,
128
+ {
129
+ ...rest,
130
+ id,
131
+ className: cx("\u{1F95D}-banner-dismiss-button", props.className),
132
+ variant: "ghost",
133
+ label,
134
+ "aria-labelledby": `${id} ${labelId || ""}`,
135
+ icon: /* @__PURE__ */ jsx(Dismiss, {}),
136
+ ref: forwardedRef
137
+ }
138
+ ) });
139
+ }
140
+ );
141
+ DEV: BannerDismissButton.displayName = "Banner.DismissButton";
10
142
  const Banner = forwardRef((props, forwardedRef) => {
11
143
  const {
12
144
  message,
13
145
  label,
14
146
  actions,
15
147
  onDismiss,
148
+ icon,
16
149
  tone = "neutral",
17
- icon = tone !== "neutral" ? /* @__PURE__ */ jsx(StatusIcon, { tone }) : void 0,
18
- variant = "outline",
19
150
  ...rest
20
151
  } = props;
21
- const baseId = React.useId();
22
- const labelId = `${baseId}-label`;
23
- const dismissId = `${baseId}-dismiss`;
24
- return /* @__PURE__ */ jsxs(
25
- Role,
26
- {
27
- ...rest,
28
- "data-kiwi-tone": tone,
29
- "data-kiwi-variant": variant,
30
- className: cx("\u{1F95D}-banner", props.className),
31
- ref: forwardedRef,
32
- children: [
33
- icon ? /* @__PURE__ */ jsx(
34
- Icon,
35
- {
36
- className: "\u{1F95D}-banner-icon",
37
- href: typeof icon === "string" ? icon : void 0,
38
- render: React.isValidElement(icon) ? icon : void 0
39
- }
40
- ) : null,
41
- /* @__PURE__ */ jsx(
42
- Text,
43
- {
44
- className: "\u{1F95D}-banner-label",
45
- id: labelId,
46
- variant: "body-sm",
47
- render: React.isValidElement(label) ? label : /* @__PURE__ */ jsx("span", {}),
48
- children: !React.isValidElement(label) ? label : void 0
49
- }
50
- ),
51
- /* @__PURE__ */ jsx(Text, { render: /* @__PURE__ */ jsx("div", {}), variant: "body-sm", className: "\u{1F95D}-banner-message", children: message }),
52
- actions != null ? /* @__PURE__ */ jsx("div", { className: "\u{1F95D}-banner-actions", children: actions }) : null,
53
- onDismiss ? /* @__PURE__ */ jsx(GhostAligner, { align: "block", children: /* @__PURE__ */ jsx(
54
- IconButton,
55
- {
56
- id: dismissId,
57
- className: "\u{1F95D}-banner-dismiss-button",
58
- variant: "ghost",
59
- label: "Dismiss",
60
- "aria-labelledby": `${dismissId} ${labelId}`,
61
- icon: /* @__PURE__ */ jsx(Dismiss, {}),
62
- onClick: onDismiss
63
- }
64
- ) }) : null
65
- ]
66
- }
152
+ const shouldRenderIcon = React.useMemo(
153
+ () => icon !== void 0 || tone !== "neutral",
154
+ [icon, tone]
67
155
  );
156
+ return /* @__PURE__ */ jsxs(BannerRoot, { tone, ...rest, ref: forwardedRef, children: [
157
+ shouldRenderIcon ? /* @__PURE__ */ jsx(
158
+ BannerIcon,
159
+ {
160
+ href: typeof icon === "string" ? icon : void 0,
161
+ render: React.isValidElement(icon) ? icon : void 0
162
+ }
163
+ ) : null,
164
+ /* @__PURE__ */ jsx(BannerLabel, { render: React.isValidElement(label) ? label : void 0, children: label }),
165
+ /* @__PURE__ */ jsx(BannerMessage, { children: message }),
166
+ actions != null ? /* @__PURE__ */ jsx(BannerActions, { children: actions }) : null,
167
+ onDismiss ? /* @__PURE__ */ jsx(BannerDismissButton, { onClick: onDismiss }) : null
168
+ ] });
68
169
  });
69
170
  DEV: Banner.displayName = "Banner";
70
171
  var Banner_default = Banner;
71
172
  export {
173
+ BannerActions as Actions,
174
+ BannerDismissButton as DismissButton,
175
+ BannerIcon as Icon,
176
+ BannerLabel as Label,
177
+ BannerMessage as Message,
178
+ BannerRoot as Root,
72
179
  Banner_default as default
73
180
  };
@@ -48,7 +48,10 @@ function DropdownMenuRoot(props) {
48
48
  DEV: DropdownMenuRoot.displayName = "DropdownMenu.Root";
49
49
  const DropdownMenuContent = forwardRef(
50
50
  (props, forwardedRef) => {
51
- const popover = usePopoverApi(useMenuContext());
51
+ const context = useMenuContext();
52
+ const open = useStoreState(context, "open");
53
+ const popoverElement = useStoreState(context, "popoverElement");
54
+ const popoverProps = usePopoverApi({ element: popoverElement, open });
52
55
  return /* @__PURE__ */ jsx(
53
56
  Menu,
54
57
  {
@@ -56,8 +59,8 @@ const DropdownMenuContent = forwardRef(
56
59
  unmountOnHide: true,
57
60
  ...props,
58
61
  gutter: 4,
59
- style: { ...popover.style, ...props.style },
60
- wrapperProps: popover.wrapperProps,
62
+ style: { ...popoverProps.style, ...props.style },
63
+ wrapperProps: { popover: popoverProps.popover },
61
64
  className: cx("\u{1F95D}-dropdown-menu", props.className),
62
65
  ref: forwardedRef
63
66
  }