@stack-spot/ai-chat-widget 1.17.1 → 1.18.0-beta.10

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.
Files changed (110) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/StackspotAIWidget.js +1 -1
  3. package/dist/app-metadata.json +6 -6
  4. package/dist/components/ComponentNavigator.d.ts +38 -0
  5. package/dist/components/ComponentNavigator.d.ts.map +1 -0
  6. package/dist/components/ComponentNavigator.js +33 -0
  7. package/dist/components/ComponentNavigator.js.map +1 -0
  8. package/dist/components/ListResource.d.ts +29 -0
  9. package/dist/components/ListResource.d.ts.map +1 -0
  10. package/dist/components/ListResource.js +17 -0
  11. package/dist/components/ListResource.js.map +1 -0
  12. package/dist/components/RightPanelForm.d.ts.map +1 -1
  13. package/dist/components/RightPanelForm.js +29 -1
  14. package/dist/components/RightPanelForm.js.map +1 -1
  15. package/dist/components/Selector/index.js +5 -5
  16. package/dist/components/Selector/index.js.map +1 -1
  17. package/dist/components/Selector/styled.d.ts +3 -1
  18. package/dist/components/Selector/styled.d.ts.map +1 -1
  19. package/dist/components/Selector/styled.js +2 -1
  20. package/dist/components/Selector/styled.js.map +1 -1
  21. package/dist/components/WorkspaceTabNavigator.d.ts +17 -0
  22. package/dist/components/WorkspaceTabNavigator.d.ts.map +1 -0
  23. package/dist/components/WorkspaceTabNavigator.js +95 -0
  24. package/dist/components/WorkspaceTabNavigator.js.map +1 -0
  25. package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
  26. package/dist/components/form/DescribedCheckboxGroup.js +23 -2
  27. package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/state/ChatEntry.d.ts +4 -0
  33. package/dist/state/ChatEntry.d.ts.map +1 -1
  34. package/dist/state/ChatEntry.js.map +1 -1
  35. package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
  36. package/dist/views/Agents/AgentsPanel.js +19 -11
  37. package/dist/views/Agents/AgentsPanel.js.map +1 -1
  38. package/dist/views/Agents/AgentsTab.d.ts +9 -3
  39. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  40. package/dist/views/Agents/AgentsTab.js +25 -7
  41. package/dist/views/Agents/AgentsTab.js.map +1 -1
  42. package/dist/views/Agents/dictionary.d.ts +1 -1
  43. package/dist/views/Agents/dictionary.d.ts.map +1 -1
  44. package/dist/views/Agents/dictionary.js +2 -0
  45. package/dist/views/Agents/dictionary.js.map +1 -1
  46. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  47. package/dist/views/Chat/ChatMessage.js +5 -5
  48. package/dist/views/Chat/ChatMessage.js.map +1 -1
  49. package/dist/views/Chat/StepsList.js +2 -2
  50. package/dist/views/Chat/StepsList.js.map +1 -1
  51. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  52. package/dist/views/ChatHistory/utils.js +12 -3
  53. package/dist/views/ChatHistory/utils.js.map +1 -1
  54. package/dist/views/KnowledgeSources.d.ts +12 -0
  55. package/dist/views/KnowledgeSources.d.ts.map +1 -1
  56. package/dist/views/KnowledgeSources.js +20 -6
  57. package/dist/views/KnowledgeSources.js.map +1 -1
  58. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  59. package/dist/views/MessageInput/AgentSelector.js +11 -7
  60. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  61. package/dist/views/MessageInput/ButtonGroup.js +2 -2
  62. package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
  63. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  64. package/dist/views/MessageInput/QuickCommandSelector.js +12 -4
  65. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  66. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  67. package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
  68. package/dist/views/MessageInput/dictionary.js +6 -4
  69. package/dist/views/MessageInput/dictionary.js.map +1 -1
  70. package/dist/views/Stacks.d.ts +9 -0
  71. package/dist/views/Stacks.d.ts.map +1 -1
  72. package/dist/views/Stacks.js +37 -14
  73. package/dist/views/Stacks.js.map +1 -1
  74. package/dist/views/Workspaces/WorkspacesTab.d.ts +20 -0
  75. package/dist/views/Workspaces/WorkspacesTab.d.ts.map +1 -0
  76. package/dist/views/Workspaces/WorkspacesTab.js +64 -0
  77. package/dist/views/Workspaces/WorkspacesTab.js.map +1 -0
  78. package/dist/views/{Workspaces.d.ts → Workspaces/index.d.ts} +1 -1
  79. package/dist/views/Workspaces/index.d.ts.map +1 -0
  80. package/dist/views/Workspaces/index.js +67 -0
  81. package/dist/views/Workspaces/index.js.map +1 -0
  82. package/package.json +3 -3
  83. package/src/app-metadata.json +6 -6
  84. package/src/components/ComponentNavigator.tsx +103 -0
  85. package/src/components/ListResource.tsx +60 -0
  86. package/src/components/RightPanelForm.tsx +29 -1
  87. package/src/components/Selector/index.tsx +5 -5
  88. package/src/components/Selector/styled.ts +3 -2
  89. package/src/components/WorkspaceTabNavigator.tsx +175 -0
  90. package/src/components/form/DescribedCheckboxGroup.tsx +38 -7
  91. package/src/index.ts +2 -0
  92. package/src/state/ChatEntry.ts +4 -0
  93. package/src/views/Agents/AgentsPanel.tsx +21 -11
  94. package/src/views/Agents/AgentsTab.tsx +42 -9
  95. package/src/views/Agents/dictionary.ts +3 -0
  96. package/src/views/Chat/ChatMessage.tsx +6 -5
  97. package/src/views/Chat/StepsList.tsx +2 -2
  98. package/src/views/ChatHistory/utils.ts +14 -3
  99. package/src/views/KnowledgeSources.tsx +37 -14
  100. package/src/views/MessageInput/AgentSelector.tsx +20 -8
  101. package/src/views/MessageInput/ButtonGroup.tsx +3 -3
  102. package/src/views/MessageInput/QuickCommandSelector.tsx +19 -6
  103. package/src/views/MessageInput/dictionary.ts +6 -4
  104. package/src/views/Stacks.tsx +57 -17
  105. package/src/views/Workspaces/WorkspacesTab.tsx +120 -0
  106. package/src/views/Workspaces/index.tsx +76 -0
  107. package/dist/views/Workspaces.d.ts.map +0 -1
  108. package/dist/views/Workspaces.js +0 -103
  109. package/dist/views/Workspaces.js.map +0 -1
  110. package/src/views/Workspaces.tsx +0 -137
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useTranslate } from '@stack-spot/portal-translate';
3
+ import { useCallback, useEffect, useRef } from 'react';
4
+ import { RightPanelTabs } from '../../components/RightPanelTabs.js';
5
+ import { useCurrentChat, useWidget, useWidgetState } from '../../context/hooks.js';
6
+ import { useRightPanel } from '../../right-panel/hooks.js';
7
+ import { WorkspacesTab } from './WorkspacesTab.js';
8
+ /**
9
+ * Renders the Workspace selection form in the Right Panel if this is the panel that is currently opened.
10
+ */
11
+ export const Workspaces = () => {
12
+ const t = useTranslate(dictionary);
13
+ const panel = useWidgetState('panel');
14
+ const { open } = useRightPanel();
15
+ const widget = useWidget();
16
+ const chat = useCurrentChat();
17
+ useEffect(() => {
18
+ if (panel === 'workspace')
19
+ open(_jsx(WorkspacesPanel, {}, chat.id), { title: t.title, description: t.description, onClose: () => widget.set('panel', undefined) });
20
+ }, [panel, t, chat.id]);
21
+ return null;
22
+ };
23
+ const WorkspacesPanel = () => {
24
+ const t = useTranslate(dictionary);
25
+ const chat = useCurrentChat();
26
+ const allKS = useRef(chat.get('knowledgeSources') ?? []);
27
+ const agent = useRef(chat.get('agent'));
28
+ const stack = useRef(chat.get('stack'));
29
+ const { close } = useRightPanel();
30
+ const onSubmit = useCallback(() => {
31
+ chat.set('knowledgeSources', allKS.current);
32
+ chat.set('stack', stack.current);
33
+ chat.set('agent', agent.current);
34
+ close();
35
+ }, [chat]);
36
+ useEffect(() => {
37
+ allKS.current = chat.get('knowledgeSources') ?? [];
38
+ agent.current = chat.get('agent');
39
+ stack.current = chat.get('stack');
40
+ }, [chat]);
41
+ return _jsx(RightPanelTabs, { tabs: [
42
+ {
43
+ title: t.favorites,
44
+ content: _jsx(WorkspacesTab, { visibility: "favorite", allKS: allKS, agent: agent, stack: stack, onSubmit: onSubmit }, "favorite"),
45
+ },
46
+ {
47
+ title: t.all,
48
+ content: _jsx(WorkspacesTab, { visibility: "all", allKS: allKS, agent: agent, stack: stack, onSubmit: onSubmit }, "all"),
49
+ },
50
+ ] }, chat.id);
51
+ };
52
+ const dictionary = {
53
+ en: {
54
+ title: 'Spot',
55
+ description: 'By selecting a spot, its Knowledge Sources (KSs), Agents, Quick Commands and Stacks Ai will be consulted to generate the answers.',
56
+ noSearchResults: "Your search didn't yield results.",
57
+ all: 'All',
58
+ favorites: 'Favorites',
59
+ },
60
+ pt: {
61
+ title: 'Spot',
62
+ description: 'Ao selecionar um spot, seus Knowledge Sources (KSs), Agentes, Quick Commands e Stacks Ai serão consultados para gerar as respostas.',
63
+ all: 'Todos',
64
+ favorites: 'Favoritos',
65
+ },
66
+ };
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/views/Workspaces/index.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-spot/ai-chat-widget",
3
- "version": "1.17.1",
3
+ "version": "1.18.0-beta.10",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,8 +12,8 @@
12
12
  "peerDependencies": {
13
13
  "@citric/core": "^6.4.0",
14
14
  "@stack-spot/portal-components": "^2.22.1",
15
- "@citric/icons": "^5.9.0",
16
- "@stack-spot/portal-network": "^0.115.0",
15
+ "@citric/icons": "^5.11.0",
16
+ "@stack-spot/portal-network": "^0.119.1",
17
17
  "@citric/ui": "^6.7.0",
18
18
  "@stack-spot/portal-theme": "^1.0.0",
19
19
  "@stack-spot/portal-translate": "^1.1.0",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stack-spot/ai-chat-widget",
3
- "version": "1.17.1",
4
- "date": "Mon Apr 28 2025 22:15:02 GMT+0000 (Coordinated Universal Time)",
3
+ "version": "1.18.0-beta.10",
4
+ "date": "Fri May 02 2025 11:09:02 GMT-0300 (Brasilia Standard Time)",
5
5
  "dependencies": [
6
6
  {
7
7
  "name": "@stack-spot/app-metadata",
@@ -93,11 +93,11 @@
93
93
  },
94
94
  {
95
95
  "name": "@citric/icons",
96
- "version": "5.9.0(react@18.2.0)"
96
+ "version": "5.11.0(react@18.2.0)"
97
97
  },
98
98
  {
99
99
  "name": "@citric/ui",
100
- "version": "6.7.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.9.0(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
100
+ "version": "6.7.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.11.0(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0))"
101
101
  },
102
102
  {
103
103
  "name": "@dagrejs/dagre",
@@ -109,11 +109,11 @@
109
109
  },
110
110
  {
111
111
  "name": "@stack-spot/portal-components",
112
- "version": "2.22.1(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.9.0(react@18.2.0))(@citric/ui@6.7.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.9.0(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-theme@1.1.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.11)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
112
+ "version": "2.22.1(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.11.0(react@18.2.0))(@citric/ui@6.7.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.11.0(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-theme@1.1.0(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.11)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
113
113
  },
114
114
  {
115
115
  "name": "@stack-spot/portal-network",
116
- "version": "0.115.0(@stack-spot/auth@5.3.2)(@stack-spot/opa@2.5.0(@stack-spot/auth@5.3.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@stack-spot/portal-translate@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@tanstack/react-query@5.59.16(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
116
+ "version": "0.119.1(@stack-spot/auth@5.3.2)(@stack-spot/opa@2.5.0(@stack-spot/auth@5.3.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@stack-spot/portal-translate@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@tanstack/react-query@5.59.16(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
117
117
  },
118
118
  {
119
119
  "name": "@stack-spot/portal-theme",
@@ -0,0 +1,103 @@
1
+ import { Flex } from '@citric/core'
2
+ import { ArrowLeft } from '@citric/icons'
3
+ import { IconButton } from '@citric/ui'
4
+ import { createContext, useCallback, useContext, useMemo, useState } from 'react'
5
+
6
+ /**
7
+ * Map of string keys to React components.
8
+ */
9
+ export type NavigationMap = Record<string, React.ComponentType<any>>
10
+
11
+ /**
12
+ * Navigation component describing which component to render and its props.
13
+ */
14
+ export type NavigationComponent<T extends NavigationMap, K extends keyof T = keyof T> = {
15
+ /** Component key to render. */
16
+ component: K,
17
+
18
+ /** Props for the component. */
19
+ props?: T[K] extends React.ComponentType<infer P> ? P : never,
20
+
21
+ /** Render in fullscreen mode. */
22
+ fullScreen?: boolean,
23
+ }
24
+
25
+ export interface NavigationContextType<T extends NavigationMap> {
26
+ /** Navigate to a new component. */
27
+ navigate: <K extends keyof T>(item: NavigationComponent<T, K>) => Promise<void>,
28
+
29
+ /** Go back to previous component. */
30
+ goBack: () => void,
31
+
32
+ /** True if can go back. */
33
+ canGoBack: boolean,
34
+
35
+ /** Current component. */
36
+ currentItem: NavigationComponent<T>,
37
+ }
38
+
39
+ export interface ComponentNavigatorProps<T extends NavigationMap, K extends keyof T> {
40
+ /** Initial component to render. */
41
+ initialItem: NavigationComponent<T, K>,
42
+
43
+ /** Map of available components. */
44
+ components: T,
45
+
46
+ /** Optional title renderer. */
47
+ renderTitle?: (item: NavigationComponent<T, keyof T>) => React.ReactNode,
48
+
49
+ /** Optional CSS class. */
50
+ className?: string,
51
+ }
52
+
53
+ const NavigationContext = createContext<NavigationContextType<NavigationMap> | null>(null)
54
+
55
+ export function ComponentNavigator<T extends NavigationMap, K extends keyof T>({
56
+ initialItem,
57
+ components,
58
+ renderTitle,
59
+ className,
60
+ }: ComponentNavigatorProps<T, K>) {
61
+ const [navigationStack, setNavigationStack] = useState<NavigationComponent<T>[]>([initialItem])
62
+ const currentItem = navigationStack[navigationStack.length - 1]
63
+
64
+ const navigate = useCallback((item: NavigationComponent<T>) => { setNavigationStack((prev) => [...prev, item]) }, [])
65
+ const canGoBack = navigationStack.length > 1
66
+ const goBack = useCallback(() => {
67
+ if (canGoBack) {
68
+ setNavigationStack((prev) => prev.slice(0, -1))
69
+ }
70
+ }, [canGoBack])
71
+
72
+ const navigationContext = useMemo(() => ({ navigate, goBack, canGoBack, currentItem }), [navigate, goBack, canGoBack, currentItem])
73
+ const Component = components[currentItem.component]
74
+ const isFullScreen = currentItem.fullScreen
75
+
76
+ if (!Component) {
77
+ // eslint-disable-next-line no-console
78
+ console.error(`Componente not found: ${String(currentItem.component)}`)
79
+ }
80
+
81
+ return (<NavigationContext.Provider value={navigationContext as NavigationContextType<NavigationMap>}>
82
+ <div className={`content-navigator ${className || isFullScreen ? 'full' : ''}`} role="navigation">
83
+ {canGoBack && (
84
+ <Flex alignItems="center" w={12} sx={{ gap: '4px' }}>
85
+ <IconButton onClick={goBack} appearance="square" className="back-button" aria-label="Back">
86
+ <ArrowLeft />
87
+ </IconButton>
88
+ {renderTitle?.(currentItem)}
89
+ </Flex>
90
+ )}
91
+ <Component {...(currentItem.props as any)} />
92
+ </div>
93
+ </NavigationContext.Provider>
94
+ )
95
+ }
96
+
97
+ export function useComponentNavigation<T extends NavigationMap>(): NavigationContextType<T> {
98
+ const context = useContext(NavigationContext)
99
+ if (!context) {
100
+ throw new Error('useComponentNavigation should be used inside ComponentNavigator')
101
+ }
102
+ return context
103
+ }
@@ -0,0 +1,60 @@
1
+ import { Box, Flex, Text } from '@citric/core'
2
+ import { WithStyle } from '@stack-spot/portal-theme'
3
+ import { useMemo } from 'react'
4
+
5
+ /**
6
+ *
7
+ * @template T Type of list item.
8
+ */
9
+ interface ListGroupProps<T> extends WithStyle {
10
+
11
+ /** Items to show. */
12
+ list: T[],
13
+
14
+ /** Render the label for an item. */
15
+ renderLabel: (item: T) => React.ReactNode,
16
+
17
+ /** Optional: element before label. */
18
+ renderBeforeElement?: (item: T) => React.ReactNode,
19
+
20
+ /** Optional: element after label. */
21
+ renderAfterElement?: (item: T) => React.ReactNode,
22
+
23
+ /** Optional: custom class name per item. */
24
+ optionClassName?: (item: T) => string | undefined,
25
+
26
+ /** Optional: custom style per item. */
27
+ optionStyle?: (item: T) => React.CSSProperties | undefined,
28
+
29
+ /** Unique key for each item. */
30
+ keygen: (item: T) => React.Key,
31
+ }
32
+
33
+ /**
34
+ * Renders a customizable list of items.
35
+ *
36
+ * @template T Type of list item.
37
+ */
38
+ export function ListResource<T>({ list, renderLabel, renderBeforeElement, renderAfterElement, className, keygen, style }
39
+ : ListGroupProps<T>) {
40
+ const items = useMemo(() =>
41
+ list.map((listItem) => {
42
+ const label = renderLabel(listItem)
43
+ const content = typeof label === 'string' ? <Text>{label}</Text> : label
44
+ return (
45
+ <li key={keygen(listItem)}>
46
+ <Flex alignItems="center">
47
+ {renderBeforeElement?.(listItem)}
48
+ {content}
49
+ {renderAfterElement?.(listItem)}
50
+ </Flex>
51
+ </li>
52
+ )
53
+ }), [list])
54
+
55
+ return (
56
+ <Box as="ul" m={0} p={0} w={12} style={style} className={className}>
57
+ {items}
58
+ </Box>
59
+ )
60
+ }
@@ -1,4 +1,5 @@
1
1
  import { Card } from '@citric/ui'
2
+ import { theme } from '@stack-spot/portal-theme'
2
3
  import { styled } from 'styled-components'
3
4
  import { panelAnimationTime } from '../right-panel/constants'
4
5
  import { PropsOf } from '../types'
@@ -13,12 +14,39 @@ const Form = styled.form`
13
14
  flex: 1;
14
15
  gap: 20px;
15
16
 
16
- > .content {
17
+ > .content, .content-navigator .content, .content-navigator:not(.full) {
17
18
  display: flex;
18
19
  flex-direction: column;
19
20
  gap: 8px;
20
21
  flex: 1;
21
22
  overflow: hidden;
23
+ width: 100%;
24
+ }
25
+
26
+ .content-navigator {
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: flex-start;
30
+ ul {
31
+ list-style: none;
32
+ }
33
+ }
34
+
35
+ .content-navigator.full {
36
+ position: absolute;
37
+ top: 0;
38
+ left: 0;
39
+ padding: 25px;
40
+ gap: 16px;
41
+ width: calc(100% - 50px);
42
+ height: calc(100% - 100px);
43
+ background: ${theme.color.light[300]};
44
+
45
+ ~ .workspace-submit {
46
+ position: absolute;
47
+ left: 25px;
48
+ bottom: 25px;
49
+ }
22
50
  }
23
51
 
24
52
  > button {
@@ -152,7 +152,7 @@ const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect, fa
152
152
  const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentProps<any>) => {
153
153
  const t = useTranslate(dictionary)
154
154
  const ref = useRef<HTMLDivElement>(null)
155
- const [visibility, setVisibility] = useState<SectionVisibility | undefined>('favorite')
155
+ const [visibility, setVisibility] = useState<SectionVisibility | undefined>()
156
156
  const { resourceName, icon, onSelect, sections } = selectorConfig
157
157
  const onSelectItem = useCallback((slug: string) => {
158
158
  onSelect(slug)
@@ -186,8 +186,8 @@ const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentP
186
186
  </header>
187
187
  <div className="body">
188
188
  <ul className="tabs">
189
- {sections.map(createSectionItem)}
190
189
  {createSectionItem()}
190
+ {sections.map(createSectionItem)}
191
191
  </ul>
192
192
  <FallbackBoundary message={interpolate(t.error, selectorConfig.resourceName)} mini>
193
193
  <List filter={filter} visibility={visibility} selectorConfig={selectorConfig} onSelect={onSelectItem} favorite={favorite} />
@@ -248,7 +248,7 @@ export const Selector = <T, >({ inputRef, selectorConfig, favorite }: SelectorPr
248
248
  }, [shouldRender])
249
249
 
250
250
  return (
251
- <SelectorBox>
251
+ <SelectorBox tabsCount={selectorConfig.sections.length}>
252
252
  <Fading visible={shouldRender} ref={selectorRef} className="box-selector">
253
253
  <SelectorContent
254
254
  favorite={favorite}
@@ -269,7 +269,7 @@ const dictionary = {
269
269
  account: 'Account',
270
270
  shared: 'Shared',
271
271
  builtIn: 'Built-in',
272
- workspace: 'Workspace',
272
+ workspace: 'Spot',
273
273
  error: 'Could not load the $0s.',
274
274
  noData: 'You don\'t have any $0 yet.',
275
275
  noResults: 'There are no $0s to show here.',
@@ -281,7 +281,7 @@ const dictionary = {
281
281
  personal: 'Pessoal',
282
282
  account: 'Conta',
283
283
  shared: 'Compartilhado',
284
- workspace: 'Workspace',
284
+ workspace: 'Spot',
285
285
  builtIn: 'Embutido',
286
286
  error: 'Não foi possível carregar os $0s.',
287
287
  noData: 'Você ainda não possui $0s.',
@@ -2,7 +2,7 @@ import { theme } from '@stack-spot/portal-theme'
2
2
  import { styled } from 'styled-components'
3
3
 
4
4
 
5
- export const SelectorBox = styled.div`
5
+ export const SelectorBox = styled.div<{tabsCount: number}>`
6
6
  position: absolute;
7
7
  bottom: 0;
8
8
 
@@ -53,6 +53,8 @@ export const SelectorBox = styled.div`
53
53
  display: flex;
54
54
  flex-direction: row;
55
55
  align-items: center;
56
+ align-items: flex-start;
57
+ height: ${({ tabsCount }) => 34 + (tabsCount * 34)}px;
56
58
  }
57
59
 
58
60
  ul {
@@ -105,7 +107,6 @@ export const SelectorBox = styled.div`
105
107
  gap: 2px;
106
108
  overflow-y: auto;
107
109
  flex: 1;
108
- max-height: 170px;
109
110
 
110
111
  li {
111
112
  display: flex;
@@ -0,0 +1,175 @@
1
+ import { Box, Flex, IconBox, Image, Text } from '@citric/core'
2
+ import { ArrowRight, Search, Spaces, Times } from '@citric/icons'
3
+ import { Avatar, IconButton } from '@citric/ui'
4
+ import { Placeholder } from '@stack-spot/portal-components/Placeholder'
5
+ import { workspaceAiClient } from '@stack-spot/portal-network'
6
+ import { WorkspaceResponse, WorkspaceVisibilityLevelEnum } from '@stack-spot/portal-network/api/workspace-ai'
7
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
8
+ import { memo, useMemo, useState } from 'react'
9
+ import { useRightPanel } from '../right-panel/hooks'
10
+ import { ButtonFavorite } from './ButtonFavorite'
11
+ import { ComponentNavigator, ComponentNavigatorProps, NavigationComponent, NavigationMap, useComponentNavigation } from './ComponentNavigator'
12
+ import { IconInput } from './IconInput'
13
+ import { ListResource } from './ListResource'
14
+
15
+ interface CardSpaceProps {
16
+ onClick: VoidFunction,
17
+ name: string,
18
+ icon: React.ReactElement,
19
+ logoUrl?: string | null,
20
+ }
21
+
22
+ export const CardSpace = ({ onClick, name, icon, logoUrl }: CardSpaceProps) =>
23
+ <Flex
24
+ onClick={onClick}
25
+ flex={1}
26
+ alignItems="center"
27
+ justifyContent="space-between"
28
+ mr={2}
29
+ bg="light.400"
30
+ r="sm"
31
+ p={3}
32
+ sx={{ cursor: 'pointer' }}
33
+ >
34
+ <Flex alignContent="center" alignItems="center" sx={{ gap: '8px', m: 1 }} >
35
+ <Avatar size="xxs" appearance="square" sx={{ bg: 'light.600', r: 'xxs' }}>
36
+ {logoUrl ? <Image src={logoUrl} /> : <IconBox> {icon} </IconBox>}
37
+ </Avatar>
38
+ <Text appearance="body2">{name}</Text>
39
+ </Flex>
40
+ <IconButton><ArrowRight /></IconButton>
41
+ </Flex>
42
+
43
+ interface WorkspaceSourcesTabProps {
44
+ visibility: WorkspaceVisibilityLevelEnum,
45
+ onClick: (workspace: WorkspaceResponse) => void,
46
+ }
47
+
48
+ const WorkspaceSourcesTab = ({ visibility, onClick }: WorkspaceSourcesTabProps) => {
49
+ const t = useTranslate(dictionary)
50
+ const [filter, setFilter] = useState('')
51
+ const workspaces = workspaceAiClient.workspacesAi.useQuery({ visibility })
52
+ const listFavorites = workspaceAiClient.workspacesAi.useQuery({ visibility: 'favorite' })
53
+ const [addFavorite, pendingAddFav] = workspaceAiClient.addFavoriteWorkspaceAi.useMutation()
54
+ const [removeFavorite, pendingRemoveFav] = workspaceAiClient.removeFavoriteWorkspaceAi.useMutation()
55
+
56
+ // eslint-disable-next-line no-async-promise-executor
57
+ const onAddFavorite = async (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
58
+ try {
59
+ await addFavorite({ workspaceId: idOrSlug })
60
+ await workspaceAiClient.workspacesAi.invalidate()
61
+ if (!pendingAddFav) {
62
+ resolve(true)
63
+ }
64
+ } catch (error) {
65
+ // eslint-disable-next-line no-console
66
+ console.error(error)
67
+ reject(error)
68
+ }
69
+ })
70
+
71
+ // eslint-disable-next-line no-async-promise-executor
72
+ const onRemoveFavorite = (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
73
+ try {
74
+ await removeFavorite({ workspaceId: idOrSlug })
75
+ await workspaceAiClient.workspacesAi.invalidate()
76
+ if (!pendingRemoveFav) {
77
+ resolve(true)
78
+ }
79
+ } catch (error) {
80
+ // eslint-disable-next-line no-console
81
+ console.error(error)
82
+ reject(error)
83
+ }
84
+ })
85
+
86
+ const filtered = useMemo(
87
+ // Recreate the list so that the favorites list is taken into account
88
+ () => filter ? workspaces.filter(w => w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : [...workspaces],
89
+ [workspaces, filter, listFavorites],
90
+ )
91
+
92
+ return (
93
+ <>
94
+ <Box w={12}>
95
+ <IconInput icon={<Search />} value={filter} onChange={setFilter} className="search" />
96
+ </Box>
97
+
98
+ {!!filtered.length &&
99
+ <ListResource
100
+ list={filtered}
101
+ keygen={w => w.id}
102
+ style={{ gap: '6px', display: 'flex', flexDirection: 'column' }}
103
+ renderLabel={w => <CardSpace name={w.name} logoUrl={w.logo} icon={<Spaces />} onClick={() => onClick(w)} />}
104
+ renderAfterElement={(w) =>
105
+ <ButtonFavorite favorite={{ idOrSlug: w?.id, listFavorites, onAddFavorite, onRemoveFavorite }} />}
106
+ optionClassName={w => (filter && !w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
107
+ ? 'filtered-out'
108
+ : ''
109
+ }
110
+ className="option-list"
111
+ />
112
+ }
113
+ <Box w={12}>
114
+ {!!workspaces.length && !filtered.length &&
115
+ <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} className="no-data-placeholder" />}
116
+ {!workspaces.length && <Placeholder title={t.noData} description={t.noDataDescription} />}
117
+ </Box>
118
+ </>
119
+ )
120
+ }
121
+
122
+
123
+ const WorkspaceHeader = <T extends NavigationMap, K extends keyof T>({ data }: { data: NavigationComponent<T, K> }) => {
124
+ const { close: closeRightPanel } = useRightPanel()
125
+ const workspaceId = (data.props as any)['workspaceId']
126
+ if (!workspaceId) return
127
+
128
+ const workspace = workspaceAiClient.workspaceAi.useQuery({ id: workspaceId })
129
+ return <Flex justifyContent="space-between" alignItems="center" flex={1}>
130
+ {data.component === 'workspaceResource' ? 'Spots' : workspace.name}
131
+ {data.fullScreen && <IconButton title={'t.close'} aria-label={'t.close'} onClick={closeRightPanel}> <Times /> </IconButton>}
132
+ </Flex>
133
+ }
134
+
135
+ interface WorkspaceTabNavigatorProps {
136
+ getNavigateParam: (workspace: WorkspaceResponse) => NavigationComponent<NavigationMap, string>,
137
+ visibility?: WorkspaceVisibilityLevelEnum,
138
+ className?: string,
139
+ }
140
+ export function WorkspaceTabNavigator<T extends NavigationMap, K extends keyof T>({ components, getNavigateParam, visibility, className }:
141
+ Omit<ComponentNavigatorProps<T, K>, 'initialItem'> & WorkspaceTabNavigatorProps) {
142
+
143
+ const workspaceTabComponents = useMemo(() => ({
144
+ workspace: memo(function WorkspacesTab() {
145
+ const { navigate } = useComponentNavigation()
146
+ return (<WorkspaceSourcesTab visibility={visibility ?? 'all'} onClick={(w) => navigate(getNavigateParam(w))} />)
147
+ }),
148
+ ...components,
149
+ }), [components])
150
+
151
+ return <ComponentNavigator
152
+ initialItem={{ component: 'workspace' }}
153
+ components={workspaceTabComponents}
154
+ className={className}
155
+ renderTitle={(data) => <WorkspaceHeader data={data} />}
156
+ />
157
+ }
158
+
159
+ const dictionary = {
160
+ en: {
161
+ noSearchResults: "Your search didn't yield results.",
162
+ noSearchResultsDescription: 'Please, try another search term.',
163
+ noData: 'There are no spaces yet.',
164
+ noDataDescription: 'Use the AI portal to create new spaces.',
165
+ apply: 'Apply',
166
+ },
167
+ pt: {
168
+ noSearchResults: 'Sua busca não produziu resultados',
169
+ noSearchResultsDescription: 'Por favor, tente outra busca.',
170
+ noData: 'Ainda não há spaces.',
171
+ noDataDescription: 'Use o Portal AI para criar novos spaces.',
172
+ apply: 'Aplicar',
173
+ },
174
+ } satisfies Dictionary
175
+
@@ -1,5 +1,6 @@
1
- import { Checkbox, Text } from '@citric/core'
1
+ import { Checkbox, Flex, Text } from '@citric/core'
2
2
  import { listToClass } from '@stack-spot/portal-theme'
3
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
3
4
  import { useMemo } from 'react'
4
5
  import { Accordion } from '../Accordion'
5
6
  import { RadioCheckBox } from './styled'
@@ -9,30 +10,40 @@ import { CheckProps } from './types'
9
10
  * Renders a checkbox group where each option has a label and a description.
10
11
  * The description in placed under the label and checkbox as an accordion.
11
12
  */
12
- export function DescribedCheckboxGroup<T>({
13
+ export function DescribedCheckboxGroup<T>({
13
14
  keygen,
14
15
  onChange,
15
16
  options,
16
17
  renderDescription,
17
18
  renderLabel,
18
- renderBeforeElement,
19
- renderAfterElement,
19
+ renderBeforeElement,
20
+ renderAfterElement,
20
21
  optionClassName,
21
22
  optionStyle,
22
23
  value,
23
24
  className,
24
25
  style }: CheckProps<T>) {
26
+ const t = useTranslate(dictionary)
27
+ const allSelected = options.length > 0 && options.every(option => value.includes(option))
28
+
29
+ const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
30
+ if (e.target.checked) {
31
+ onChange(options)
32
+ } else {
33
+ onChange([])
34
+ }
35
+ }
36
+
25
37
  const items = useMemo(() => options.map((option) => {
26
38
  const label = renderLabel(option)
27
39
  const description = renderDescription(option)
28
-
29
40
  const header = (
30
41
  <label>
31
42
  <Checkbox
32
43
  checked={value.includes(option)}
33
44
  onChange={(e) => {
34
45
  if (e.target.checked && !value.includes(option)) onChange([...value, option])
35
- else onChange(value.filter(item => item !== option))
46
+ else onChange(value.filter(item => item !== option))
36
47
  }}
37
48
  />
38
49
  {typeof label === 'string' ? <Text>{label}</Text> : label}
@@ -55,5 +66,25 @@ export function DescribedCheckboxGroup<T>({
55
66
  )
56
67
  }), [options, value])
57
68
 
58
- return <RadioCheckBox style={style} className={className}>{items}</RadioCheckBox>
69
+ return <RadioCheckBox style={style} className={className}>
70
+ <Flex as="li" alignItems="center" ml={3} pl={1}>
71
+ <Checkbox
72
+ checked={allSelected}
73
+ onChange={handleSelectAll}
74
+ />
75
+ <Text>{allSelected ? t.removeAll : t.selectAll}</Text>
76
+ </Flex>
77
+ {items}
78
+ </RadioCheckBox>
59
79
  }
80
+
81
+ const dictionary = {
82
+ en: {
83
+ selectAll: 'Select all',
84
+ removeAll: 'Remove all',
85
+ },
86
+ pt: {
87
+ selectAll: 'Selecionar todos',
88
+ removeAll: 'Remover todos',
89
+ },
90
+ } satisfies Dictionary