@squeletteapp/widget-react 0.1.0 → 1.0.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.
@@ -0,0 +1,11 @@
1
+ export interface ChangelogBannerProps {
2
+ debug?: boolean;
3
+ }
4
+ /**
5
+ * ChangelogBanner - Renders a banner for the latest changelog entry
6
+ *
7
+ * This component automatically fetches the latest changelog entry,
8
+ * checks if it should be shown, and displays a banner at the bottom
9
+ * of the screen. Must be used within a ChangelogProvider.
10
+ */
11
+ export declare function ChangelogBanner({ debug }: ChangelogBannerProps): null;
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import { useChangelogContext } from '../context';
4
+ /**
5
+ * ChangelogBanner - Renders a banner for the latest changelog entry
6
+ *
7
+ * This component automatically fetches the latest changelog entry,
8
+ * checks if it should be shown, and displays a banner at the bottom
9
+ * of the screen. Must be used within a ChangelogProvider.
10
+ */
11
+ export function ChangelogBanner({ debug = false }) {
12
+ const { store, module, theme, base, slug } = useChangelogContext();
13
+ const widgetRef = React.useRef(null);
14
+ // Store initial theme in ref to use in creation
15
+ const initialThemeRef = React.useRef(theme);
16
+ // Create widget once on mount (theme changes handled separately via setTheme)
17
+ React.useEffect(() => {
18
+ let mounted = true;
19
+ module
20
+ .createChangelogBannerWidget(store, {
21
+ base,
22
+ contentTheme: initialThemeRef.current,
23
+ slug,
24
+ debug,
25
+ })
26
+ .then((widget) => {
27
+ if (mounted && widget) {
28
+ widgetRef.current = widget;
29
+ }
30
+ });
31
+ return () => {
32
+ mounted = false;
33
+ if (widgetRef.current) {
34
+ widgetRef.current.destroy();
35
+ widgetRef.current = null;
36
+ }
37
+ };
38
+ }, [slug, store, module, base, debug]);
39
+ // Update theme when it changes (separate effect to avoid recreating widget)
40
+ React.useEffect(() => {
41
+ widgetRef.current?.setTheme(theme);
42
+ }, [theme]);
43
+ return null;
44
+ }
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ type Position = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw' | 'center';
3
+ export interface ChangelogEntriesListDropdownProps {
4
+ children: React.ReactElement;
5
+ position?: Position;
6
+ }
7
+ /**
8
+ * ChangelogEntriesListDropdown - Renders a dropdown list of changelog entries
9
+ *
10
+ * This component wraps a child element (trigger) and opens the dropdown
11
+ * when the child is clicked. Must be used within a ChangelogProvider.
12
+ */
13
+ export declare function ChangelogEntriesListDropdown({ children, position, }: ChangelogEntriesListDropdownProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
14
+ export {};
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import { useChangelogContext } from '../context';
4
+ /**
5
+ * ChangelogEntriesListDropdown - Renders a dropdown list of changelog entries
6
+ *
7
+ * This component wraps a child element (trigger) and opens the dropdown
8
+ * when the child is clicked. Must be used within a ChangelogProvider.
9
+ */
10
+ export function ChangelogEntriesListDropdown({ children, position, }) {
11
+ const { store, module, theme, base, slug } = useChangelogContext();
12
+ const widgetRef = React.useRef(null);
13
+ const anchorId = React.useId();
14
+ const cleanAnchorId = `sq-dropdown-${anchorId.replace(/:/g, '')}`;
15
+ // Store initial theme in ref to use in creation
16
+ const initialThemeRef = React.useRef(theme);
17
+ // Create widget once on mount (theme changes handled separately via setTheme)
18
+ React.useEffect(() => {
19
+ const widget = module.createChangelogEntriesListDropdownWidget(`#${cleanAnchorId}`, store, {
20
+ base,
21
+ contentTheme: initialThemeRef.current,
22
+ slug,
23
+ position,
24
+ });
25
+ widget.preload();
26
+ widgetRef.current = widget;
27
+ return () => {
28
+ widget.destroy();
29
+ widgetRef.current = null;
30
+ };
31
+ }, [slug, store, module, base, cleanAnchorId, position]);
32
+ // Update theme when it changes (separate effect to avoid recreating widget)
33
+ React.useEffect(() => {
34
+ widgetRef.current?.setTheme(theme);
35
+ }, [theme]);
36
+ const handleClick = React.useCallback(() => {
37
+ widgetRef.current?.open();
38
+ }, []);
39
+ // Clone the child and add the anchor id and click handler
40
+ return React.cloneElement(children, {
41
+ id: cleanAnchorId,
42
+ onClick: (e) => {
43
+ const originalOnClick = children.props.onClick;
44
+ if (originalOnClick) {
45
+ originalOnClick(e);
46
+ }
47
+ handleClick();
48
+ },
49
+ });
50
+ }
@@ -0,0 +1,11 @@
1
+ export interface ChangelogEntryModalProps {
2
+ ticketId: string;
3
+ onOpenChange?: (isOpen: boolean) => void;
4
+ }
5
+ /**
6
+ * ChangelogEntryModal - Renders a modal for a single changelog entry
7
+ *
8
+ * This component manages its own widget lifecycle and automatically
9
+ * cleans up on unmount. Must be used within a ChangelogProvider.
10
+ */
11
+ export declare function ChangelogEntryModal({ ticketId, onOpenChange, }: ChangelogEntryModalProps): null;
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import { useChangelogContext } from '../context';
4
+ /**
5
+ * ChangelogEntryModal - Renders a modal for a single changelog entry
6
+ *
7
+ * This component manages its own widget lifecycle and automatically
8
+ * cleans up on unmount. Must be used within a ChangelogProvider.
9
+ */
10
+ export function ChangelogEntryModal({ ticketId, onOpenChange, }) {
11
+ const { store, module, theme, base, slug } = useChangelogContext();
12
+ const widgetRef = React.useRef(null);
13
+ React.useEffect(() => {
14
+ const widget = module.createChangelogEntryWidget(ticketId, {
15
+ base,
16
+ contentTheme: theme,
17
+ slug,
18
+ });
19
+ if (onOpenChange) {
20
+ widget.onOpenChange(onOpenChange);
21
+ }
22
+ widgetRef.current = widget;
23
+ store.mount(ticketId, widget);
24
+ return () => {
25
+ store.unmount(ticketId);
26
+ widgetRef.current = null;
27
+ };
28
+ }, [ticketId, slug, store, module, base, onOpenChange, theme]);
29
+ // Update theme when it changes
30
+ React.useEffect(() => {
31
+ widgetRef.current?.setTheme(theme);
32
+ }, [theme]);
33
+ return null;
34
+ }
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import type { ContentTheme } from './types';
3
+ type WidgetStore = {
4
+ getState: () => Record<string, unknown>;
5
+ mount: (source: string, widget: unknown) => Promise<void>;
6
+ unmount: (source: string) => void;
7
+ open: (source: string) => Promise<void>;
8
+ setTheme: (theme: ContentTheme) => void;
9
+ };
10
+ type WidgetModule = {
11
+ createChangelogStore: () => WidgetStore;
12
+ createChangelogEntryWidget: (ticketId: string, options: {
13
+ base?: string;
14
+ contentTheme?: ContentTheme;
15
+ slug?: string;
16
+ }) => unknown;
17
+ createChangelogBannerWidget: (store: WidgetStore, options: {
18
+ base?: string;
19
+ contentTheme?: ContentTheme;
20
+ slug: string;
21
+ debug?: boolean;
22
+ }) => Promise<unknown>;
23
+ createChangelogEntriesListDropdownWidget: (anchor: string, store: WidgetStore, options: {
24
+ base?: string;
25
+ contentTheme?: ContentTheme;
26
+ slug?: string;
27
+ position?: 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw' | 'center';
28
+ }) => unknown;
29
+ };
30
+ type ChangelogContextValue = {
31
+ store: WidgetStore;
32
+ module: WidgetModule;
33
+ theme: ContentTheme;
34
+ base?: string;
35
+ slug: string;
36
+ };
37
+ export interface ChangelogProviderProps {
38
+ children: React.ReactNode;
39
+ slug: string;
40
+ theme?: ContentTheme;
41
+ base?: string;
42
+ }
43
+ /**
44
+ * ChangelogProvider - Provides changelog store and configuration to child components
45
+ *
46
+ * This provider handles:
47
+ * - Creating and managing the widget store (client-side only)
48
+ * - Syncing theme changes to all widgets
49
+ * - Providing base URL and slug to all child components
50
+ */
51
+ export declare function ChangelogProvider({ children, slug, theme, base, }: ChangelogProviderProps): import("react/jsx-runtime").JSX.Element | null;
52
+ export declare function useChangelogContext(): ChangelogContextValue;
53
+ export {};
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ const ChangelogContext = React.createContext(null);
5
+ /**
6
+ * ChangelogProvider - Provides changelog store and configuration to child components
7
+ *
8
+ * This provider handles:
9
+ * - Creating and managing the widget store (client-side only)
10
+ * - Syncing theme changes to all widgets
11
+ * - Providing base URL and slug to all child components
12
+ */
13
+ export function ChangelogProvider({ children, slug, theme = 'light', base, }) {
14
+ const [context, setContext] = React.useState(null);
15
+ // Initialize store on client side only
16
+ React.useEffect(() => {
17
+ import('@squeletteapp/widget').then((mod) => {
18
+ const widgetModule = mod;
19
+ setContext({
20
+ store: widgetModule.createChangelogStore(),
21
+ module: widgetModule,
22
+ });
23
+ });
24
+ }, []);
25
+ // Update theme when it changes
26
+ React.useEffect(() => {
27
+ context?.store.setTheme(theme);
28
+ }, [context, theme]);
29
+ // Don't render children until store is ready (client-side only)
30
+ if (!context) {
31
+ return null;
32
+ }
33
+ return (_jsx(ChangelogContext.Provider, { value: { store: context.store, module: context.module, theme, base, slug }, children: children }));
34
+ }
35
+ export function useChangelogContext() {
36
+ const context = React.useContext(ChangelogContext);
37
+ if (!context) {
38
+ throw new Error('useChangelogContext must be used within a ChangelogProvider');
39
+ }
40
+ return context;
41
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { useFeedbackWidget, useRoadmapWidget, useChangelogWidget, useWidget, type WidgetOptions, type WidgetInstance, } from './hooks';
2
- export { FeedbackWidget, RoadmapWidget, ChangelogWidget, type FeedbackWidgetProps, type RoadmapWidgetProps, type ChangelogWidgetProps, } from './components';
3
- export { createFeedbackWidget, createRoadmapWidget, createChangelogWidget, createWidget, } from '@squeletteapp/widget';
1
+ export { ChangelogProvider, useChangelogContext, type ChangelogProviderProps, } from './context';
2
+ export { ChangelogEntryModal, type ChangelogEntryModalProps, } from './components/changelog-entry-modal';
3
+ export { ChangelogEntriesListDropdown, type ChangelogEntriesListDropdownProps, } from './components/changelog-entries-list-dropdown';
4
+ export { ChangelogBanner, type ChangelogBannerProps, } from './components/changelog-banner';
5
+ export type { ContentTheme } from './types';
package/dist/index.esm.js CHANGED
@@ -1,175 +1,158 @@
1
- // src/hooks.ts
2
- import { useEffect, useRef, useCallback } from "react";
3
- import {
4
- createFeedbackWidget,
5
- createRoadmapWidget,
6
- createChangelogWidget,
7
- createWidget
8
- } from "@squeletteapp/widget";
9
- function useFeedbackWidget(workspaceSlug, boardSlug, options) {
10
- const widgetRef = useRef(null);
11
- const initWidget = useCallback(() => {
12
- if (widgetRef.current) {
13
- widgetRef.current.destroy();
14
- }
15
- widgetRef.current = createFeedbackWidget(workspaceSlug, boardSlug, options);
16
- return widgetRef.current;
17
- }, [workspaceSlug, boardSlug, options]);
18
- useEffect(() => {
19
- const widget = initWidget();
20
- return () => {
21
- if (widget) {
22
- widget.destroy();
23
- }
24
- };
25
- }, [initWidget]);
26
- return widgetRef.current;
27
- }
28
- function useRoadmapWidget(workspaceSlug, boardSlug, options) {
29
- const widgetRef = useRef(null);
30
- const initWidget = useCallback(() => {
31
- if (widgetRef.current) {
32
- widgetRef.current.destroy();
33
- }
34
- widgetRef.current = createRoadmapWidget(workspaceSlug, boardSlug, options);
35
- return widgetRef.current;
36
- }, [workspaceSlug, boardSlug, options]);
37
- useEffect(() => {
38
- const widget = initWidget();
39
- return () => {
40
- if (widget) {
41
- widget.destroy();
42
- }
43
- };
44
- }, [initWidget]);
45
- return widgetRef.current;
46
- }
47
- function useChangelogWidget(workspaceSlug, options) {
48
- const widgetRef = useRef(null);
49
- const initWidget = useCallback(() => {
50
- if (widgetRef.current) {
51
- widgetRef.current.destroy();
52
- }
53
- widgetRef.current = createChangelogWidget(workspaceSlug, options);
54
- return widgetRef.current;
55
- }, [workspaceSlug, options]);
56
- useEffect(() => {
57
- const widget = initWidget();
58
- return () => {
59
- if (widget) {
60
- widget.destroy();
61
- }
62
- };
63
- }, [initWidget]);
64
- return widgetRef.current;
65
- }
66
- function useWidget(options) {
67
- const widgetRef = useRef(null);
68
- const initWidget = useCallback(() => {
69
- if (widgetRef.current) {
70
- widgetRef.current.destroy();
71
- }
72
- widgetRef.current = createWidget(options);
73
- return widgetRef.current;
74
- }, [options]);
75
- useEffect(() => {
76
- const widget = initWidget();
77
- return () => {
78
- if (widget) {
79
- widget.destroy();
80
- }
81
- };
82
- }, [initWidget]);
83
- return widgetRef.current;
84
- }
85
-
86
- // src/components.tsx
87
- import { useId } from "react";
1
+ // src/context.tsx
2
+ import * as React from "react";
88
3
  import { jsx } from "react/jsx-runtime";
89
- function FeedbackWidget({
90
- workspaceSlug,
91
- boardSlug,
4
+ var ChangelogContext = React.createContext(null);
5
+ function ChangelogProvider({
92
6
  children,
93
- className,
94
- ...options
7
+ slug,
8
+ theme = "light",
9
+ base
95
10
  }) {
96
- const buttonId = useId();
97
- const widget = useFeedbackWidget(workspaceSlug, boardSlug, {
98
- ...options,
99
- buttonSelector: `#${buttonId}`
100
- });
11
+ const [context, setContext] = React.useState(null);
12
+ React.useEffect(() => {
13
+ import("@squeletteapp/widget").then((mod) => {
14
+ const widgetModule = mod;
15
+ setContext({
16
+ store: widgetModule.createChangelogStore(),
17
+ module: widgetModule
18
+ });
19
+ });
20
+ }, []);
21
+ React.useEffect(() => {
22
+ context?.store.setTheme(theme);
23
+ }, [context, theme]);
24
+ if (!context) {
25
+ return null;
26
+ }
101
27
  return /* @__PURE__ */ jsx(
102
- "button",
28
+ ChangelogContext.Provider,
103
29
  {
104
- id: buttonId,
105
- className,
106
- type: "button",
30
+ value: { store: context.store, module: context.module, theme, base, slug },
107
31
  children
108
32
  }
109
33
  );
110
34
  }
111
- function RoadmapWidget({
112
- workspaceSlug,
113
- boardSlug,
114
- children,
115
- className,
116
- ...options
35
+ function useChangelogContext() {
36
+ const context = React.useContext(ChangelogContext);
37
+ if (!context) {
38
+ throw new Error("useChangelogContext must be used within a ChangelogProvider");
39
+ }
40
+ return context;
41
+ }
42
+
43
+ // src/components/changelog-entry-modal.tsx
44
+ import * as React2 from "react";
45
+ function ChangelogEntryModal({
46
+ ticketId,
47
+ onOpenChange
117
48
  }) {
118
- const buttonId = useId();
119
- const widget = useRoadmapWidget(workspaceSlug, boardSlug, {
120
- ...options,
121
- buttonSelector: `#${buttonId}`
122
- });
123
- return /* @__PURE__ */ jsx(
124
- "button",
125
- {
126
- id: buttonId,
127
- className,
128
- type: "button",
129
- children
49
+ const { store, module, theme, base, slug } = useChangelogContext();
50
+ const widgetRef = React2.useRef(null);
51
+ React2.useEffect(() => {
52
+ const widget = module.createChangelogEntryWidget(ticketId, {
53
+ base,
54
+ contentTheme: theme,
55
+ slug
56
+ });
57
+ if (onOpenChange) {
58
+ widget.onOpenChange(onOpenChange);
130
59
  }
131
- );
60
+ widgetRef.current = widget;
61
+ store.mount(ticketId, widget);
62
+ return () => {
63
+ store.unmount(ticketId);
64
+ widgetRef.current = null;
65
+ };
66
+ }, [ticketId, slug, store, module, base, onOpenChange, theme]);
67
+ React2.useEffect(() => {
68
+ widgetRef.current?.setTheme(theme);
69
+ }, [theme]);
70
+ return null;
132
71
  }
133
- function ChangelogWidget({
134
- workspaceSlug,
72
+
73
+ // src/components/changelog-entries-list-dropdown.tsx
74
+ import * as React3 from "react";
75
+ function ChangelogEntriesListDropdown({
135
76
  children,
136
- className,
137
- ...options
77
+ position
138
78
  }) {
139
- const buttonId = useId();
140
- const widget = useChangelogWidget(workspaceSlug, {
141
- ...options,
142
- buttonSelector: `#${buttonId}`
143
- });
144
- return /* @__PURE__ */ jsx(
145
- "button",
146
- {
147
- id: buttonId,
148
- className,
149
- type: "button",
150
- children
79
+ const { store, module, theme, base, slug } = useChangelogContext();
80
+ const widgetRef = React3.useRef(null);
81
+ const anchorId = React3.useId();
82
+ const cleanAnchorId = `sq-dropdown-${anchorId.replace(/:/g, "")}`;
83
+ const initialThemeRef = React3.useRef(theme);
84
+ React3.useEffect(() => {
85
+ const widget = module.createChangelogEntriesListDropdownWidget(
86
+ `#${cleanAnchorId}`,
87
+ store,
88
+ {
89
+ base,
90
+ contentTheme: initialThemeRef.current,
91
+ slug,
92
+ position
93
+ }
94
+ );
95
+ widget.preload();
96
+ widgetRef.current = widget;
97
+ return () => {
98
+ widget.destroy();
99
+ widgetRef.current = null;
100
+ };
101
+ }, [slug, store, module, base, cleanAnchorId, position]);
102
+ React3.useEffect(() => {
103
+ widgetRef.current?.setTheme(theme);
104
+ }, [theme]);
105
+ const handleClick = React3.useCallback(() => {
106
+ widgetRef.current?.open();
107
+ }, []);
108
+ return React3.cloneElement(children, {
109
+ id: cleanAnchorId,
110
+ onClick: (e) => {
111
+ const originalOnClick = children.props.onClick;
112
+ if (originalOnClick) {
113
+ originalOnClick(e);
114
+ }
115
+ handleClick();
151
116
  }
152
- );
117
+ });
153
118
  }
154
119
 
155
- // src/index.ts
156
- import {
157
- createFeedbackWidget as createFeedbackWidget2,
158
- createRoadmapWidget as createRoadmapWidget2,
159
- createChangelogWidget as createChangelogWidget2,
160
- createWidget as createWidget2
161
- } from "@squeletteapp/widget";
120
+ // src/components/changelog-banner.tsx
121
+ import * as React4 from "react";
122
+ function ChangelogBanner({ debug = false }) {
123
+ const { store, module, theme, base, slug } = useChangelogContext();
124
+ const widgetRef = React4.useRef(null);
125
+ const initialThemeRef = React4.useRef(theme);
126
+ React4.useEffect(() => {
127
+ let mounted = true;
128
+ module.createChangelogBannerWidget(store, {
129
+ base,
130
+ contentTheme: initialThemeRef.current,
131
+ slug,
132
+ debug
133
+ }).then((widget) => {
134
+ if (mounted && widget) {
135
+ widgetRef.current = widget;
136
+ }
137
+ });
138
+ return () => {
139
+ mounted = false;
140
+ if (widgetRef.current) {
141
+ widgetRef.current.destroy();
142
+ widgetRef.current = null;
143
+ }
144
+ };
145
+ }, [slug, store, module, base, debug]);
146
+ React4.useEffect(() => {
147
+ widgetRef.current?.setTheme(theme);
148
+ }, [theme]);
149
+ return null;
150
+ }
162
151
  export {
163
- ChangelogWidget,
164
- FeedbackWidget,
165
- RoadmapWidget,
166
- createChangelogWidget2 as createChangelogWidget,
167
- createFeedbackWidget2 as createFeedbackWidget,
168
- createRoadmapWidget2 as createRoadmapWidget,
169
- createWidget2 as createWidget,
170
- useChangelogWidget,
171
- useFeedbackWidget,
172
- useRoadmapWidget,
173
- useWidget
152
+ ChangelogBanner,
153
+ ChangelogEntriesListDropdown,
154
+ ChangelogEntryModal,
155
+ ChangelogProvider,
156
+ useChangelogContext
174
157
  };
175
158
  //# sourceMappingURL=index.esm.js.map