@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.
- package/CHANGELOG.md +7 -0
- package/dist/StackspotAIWidget.js +1 -1
- package/dist/app-metadata.json +6 -6
- package/dist/components/ComponentNavigator.d.ts +38 -0
- package/dist/components/ComponentNavigator.d.ts.map +1 -0
- package/dist/components/ComponentNavigator.js +33 -0
- package/dist/components/ComponentNavigator.js.map +1 -0
- package/dist/components/ListResource.d.ts +29 -0
- package/dist/components/ListResource.d.ts.map +1 -0
- package/dist/components/ListResource.js +17 -0
- package/dist/components/ListResource.js.map +1 -0
- package/dist/components/RightPanelForm.d.ts.map +1 -1
- package/dist/components/RightPanelForm.js +29 -1
- package/dist/components/RightPanelForm.js.map +1 -1
- package/dist/components/Selector/index.js +5 -5
- package/dist/components/Selector/index.js.map +1 -1
- package/dist/components/Selector/styled.d.ts +3 -1
- package/dist/components/Selector/styled.d.ts.map +1 -1
- package/dist/components/Selector/styled.js +2 -1
- package/dist/components/Selector/styled.js.map +1 -1
- package/dist/components/WorkspaceTabNavigator.d.ts +17 -0
- package/dist/components/WorkspaceTabNavigator.d.ts.map +1 -0
- package/dist/components/WorkspaceTabNavigator.js +95 -0
- package/dist/components/WorkspaceTabNavigator.js.map +1 -0
- package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
- package/dist/components/form/DescribedCheckboxGroup.js +23 -2
- package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/state/ChatEntry.d.ts +4 -0
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
- package/dist/views/Agents/AgentsPanel.js +19 -11
- package/dist/views/Agents/AgentsPanel.js.map +1 -1
- package/dist/views/Agents/AgentsTab.d.ts +9 -3
- package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
- package/dist/views/Agents/AgentsTab.js +25 -7
- package/dist/views/Agents/AgentsTab.js.map +1 -1
- package/dist/views/Agents/dictionary.d.ts +1 -1
- package/dist/views/Agents/dictionary.d.ts.map +1 -1
- package/dist/views/Agents/dictionary.js +2 -0
- package/dist/views/Agents/dictionary.js.map +1 -1
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +5 -5
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.js +2 -2
- package/dist/views/Chat/StepsList.js.map +1 -1
- package/dist/views/ChatHistory/utils.d.ts.map +1 -1
- package/dist/views/ChatHistory/utils.js +12 -3
- package/dist/views/ChatHistory/utils.js.map +1 -1
- package/dist/views/KnowledgeSources.d.ts +12 -0
- package/dist/views/KnowledgeSources.d.ts.map +1 -1
- package/dist/views/KnowledgeSources.js +20 -6
- package/dist/views/KnowledgeSources.js.map +1 -1
- package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
- package/dist/views/MessageInput/AgentSelector.js +11 -7
- package/dist/views/MessageInput/AgentSelector.js.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.js +2 -2
- package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.js +12 -4
- package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
- package/dist/views/MessageInput/dictionary.d.ts +1 -1
- package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
- package/dist/views/MessageInput/dictionary.js +6 -4
- package/dist/views/MessageInput/dictionary.js.map +1 -1
- package/dist/views/Stacks.d.ts +9 -0
- package/dist/views/Stacks.d.ts.map +1 -1
- package/dist/views/Stacks.js +37 -14
- package/dist/views/Stacks.js.map +1 -1
- package/dist/views/Workspaces/WorkspacesTab.d.ts +20 -0
- package/dist/views/Workspaces/WorkspacesTab.d.ts.map +1 -0
- package/dist/views/Workspaces/WorkspacesTab.js +64 -0
- package/dist/views/Workspaces/WorkspacesTab.js.map +1 -0
- package/dist/views/{Workspaces.d.ts → Workspaces/index.d.ts} +1 -1
- package/dist/views/Workspaces/index.d.ts.map +1 -0
- package/dist/views/Workspaces/index.js +67 -0
- package/dist/views/Workspaces/index.js.map +1 -0
- package/package.json +3 -3
- package/src/app-metadata.json +6 -6
- package/src/components/ComponentNavigator.tsx +103 -0
- package/src/components/ListResource.tsx +60 -0
- package/src/components/RightPanelForm.tsx +29 -1
- package/src/components/Selector/index.tsx +5 -5
- package/src/components/Selector/styled.ts +3 -2
- package/src/components/WorkspaceTabNavigator.tsx +175 -0
- package/src/components/form/DescribedCheckboxGroup.tsx +38 -7
- package/src/index.ts +2 -0
- package/src/state/ChatEntry.ts +4 -0
- package/src/views/Agents/AgentsPanel.tsx +21 -11
- package/src/views/Agents/AgentsTab.tsx +42 -9
- package/src/views/Agents/dictionary.ts +3 -0
- package/src/views/Chat/ChatMessage.tsx +6 -5
- package/src/views/Chat/StepsList.tsx +2 -2
- package/src/views/ChatHistory/utils.ts +14 -3
- package/src/views/KnowledgeSources.tsx +37 -14
- package/src/views/MessageInput/AgentSelector.tsx +20 -8
- package/src/views/MessageInput/ButtonGroup.tsx +3 -3
- package/src/views/MessageInput/QuickCommandSelector.tsx +19 -6
- package/src/views/MessageInput/dictionary.ts +6 -4
- package/src/views/Stacks.tsx +57 -17
- package/src/views/Workspaces/WorkspacesTab.tsx +120 -0
- package/src/views/Workspaces/index.tsx +76 -0
- package/dist/views/Workspaces.d.ts.map +0 -1
- package/dist/views/Workspaces.js +0 -103
- package/dist/views/Workspaces.js.map +0 -1
- 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":";AAAA,OAAO,EAAc,YAAY,EAAE,MAAM,8BAA8B,CAAA;AACvE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/C;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,EAAE;IAC7B,MAAM,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IACrC,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,CAAA;IAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAG,cAAc,EAAE,CAAA;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,KAAK,WAAW;YAAE,IAAI,CAC7B,KAAC,eAAe,MAAM,IAAI,CAAC,EAAE,CAAI,EACjC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAC9F,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACvB,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,MAAM,eAAe,GAAG,GAAG,EAAE;IAC3B,MAAM,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,cAAc,EAAE,CAAA;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAA;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;IACvC,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,EAAE,CAAA;IAEjC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QAC3C,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QAChC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QAChC,KAAK,EAAE,CAAA;IACT,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAA;QAClD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACjC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IACnC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAGV,OAAO,KAAC,cAAc,IAAe,IAAI,EAAE;YACzC;gBACE,KAAK,EAAE,CAAC,CAAC,SAAS;gBAClB,OAAO,EAAE,KAAC,aAAa,IAAgB,UAAU,EAAC,UAAU,EAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,IAA7F,UAAU,CAAuF;aAC9H;YACD;gBACE,KAAK,EAAE,CAAC,CAAC,GAAG;gBACZ,OAAO,EAAE,KAAC,aAAa,IAAW,UAAU,EAAC,KAAK,EAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,IAAnF,KAAK,CAAkF;aACpH;SACF,IAT2B,IAAI,CAAC,EAAE,CAUjC,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,UAAU,GAAG;IACjB,EAAE,EAAE;QACF,KAAK,EAAE,MAAM;QACb,WAAW,EAAE,mIAAmI;QAChJ,eAAe,EAAE,mCAAmC;QACpD,GAAG,EAAE,KAAK;QACV,SAAS,EAAE,WAAW;KACvB;IACD,EAAE,EAAE;QACF,KAAK,EAAE,MAAM;QACb,WAAW,EAAE,qIAAqI;QAClJ,GAAG,EAAE,OAAO;QACZ,SAAS,EAAE,WAAW;KACvB;CACmB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/ai-chat-widget",
|
|
3
|
-
"version": "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.
|
|
16
|
-
"@stack-spot/portal-network": "^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",
|
package/src/app-metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/ai-chat-widget",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"date": "
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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>(
|
|
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: '
|
|
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: '
|
|
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}>
|
|
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
|