@performant-software/semantic-components 1.0.18 → 1.0.19-beta.1

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,246 @@
1
+ // @flow
2
+
3
+ import { Element } from '@performant-software/shared-components';
4
+ import type { EditContainerProps } from '@performant-software/shared-components/types';
5
+ import React, {
6
+ useCallback,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState
11
+ } from 'react';
12
+ import {
13
+ Button,
14
+ Form,
15
+ Grid,
16
+ Menu,
17
+ MenuProps,
18
+ Message,
19
+ Ref,
20
+ Sticky
21
+ } from 'semantic-ui-react';
22
+ import _ from 'underscore';
23
+ import i18n from '../i18n/i18n';
24
+ import Toaster from './Toaster';
25
+ import './SimpleEditPage.css';
26
+
27
+ type Props = EditContainerProps & {
28
+ /**
29
+ * Additional class attribute to apply to the root DOM element.
30
+ */
31
+ className?: string,
32
+
33
+ /**
34
+ * Sets the default visible tab. If no value is provided, the first tab will be visible.
35
+ */
36
+ defaultTab?: string,
37
+
38
+ /**
39
+ * Props to provide to the Semantic UI `Menu` component.
40
+ */
41
+ menuProps?: typeof MenuProps,
42
+
43
+ /**
44
+ * Callback fired when the "Cancel" button is clicked.
45
+ */
46
+ onCancel: () => void,
47
+
48
+ /**
49
+ * Callback fired when a tab is clicked.
50
+ *
51
+ * @param tab
52
+ */
53
+ onTabClick?: (tab: string) => void,
54
+
55
+ /**
56
+ * If `true`, the saved toaster will display when the component is mounted.
57
+ */
58
+ saved?: boolean,
59
+
60
+ /**
61
+ * If `true`, the tabs menu will "stick" to the top of the window.
62
+ */
63
+ stickyMenu?: boolean
64
+ };
65
+
66
+ /**
67
+ * This component can be used to render the layout for a form/page with edit capabilities. Use in conjunction with the
68
+ * `withEditPage` higher-order component for a fully fledged record editing environment.
69
+ */
70
+ const SimpleEditPage: any = (props: Props) => {
71
+ const [currentTab, setCurrentTab] = useState();
72
+ const [saved, setSaved] = useState(false);
73
+
74
+ const contentRef = useRef();
75
+
76
+ // $FlowIgnore
77
+ const tabs = Element.findByType(props.children, SimpleEditPage.Tab);
78
+ const tab = useMemo(() => _.find(tabs, (nextTab) => nextTab.key === currentTab), [currentTab, tabs]);
79
+
80
+ /**
81
+ * Memo-izes the class name variable.
82
+ *
83
+ * @type {string}
84
+ */
85
+ const className = useMemo(() => {
86
+ const classNames = ['simple-edit-page'];
87
+
88
+ if (props.className) {
89
+ classNames.push(props.className);
90
+ }
91
+
92
+ return classNames.join(' ');
93
+ }, [props.className]);
94
+
95
+ /**
96
+ * Sets the current tab.
97
+ *
98
+ * @type {(function(*): void)|*}
99
+ */
100
+ const onTabClick = useCallback((item) => {
101
+ const { key } = item;
102
+ setCurrentTab(key);
103
+
104
+ if (props.onTabClick) {
105
+ props.onTabClick(key);
106
+ }
107
+ }, [props.onTabClick]);
108
+
109
+ /**
110
+ * Renders the tab menu component.
111
+ *
112
+ * @type {(function(): (*))|*}
113
+ */
114
+ const renderTabs = useCallback(() => {
115
+ const menu = (
116
+ <Menu
117
+ {...props.menuProps}
118
+ >
119
+ { tabs?.length > 1 && _.map(tabs, (item) => (
120
+ <Menu.Item
121
+ active={item.key === currentTab}
122
+ disabled={props.loading || props.saving}
123
+ key={item.key}
124
+ name={item.props.name}
125
+ onClick={() => onTabClick(item)}
126
+ />
127
+ ))}
128
+ <Menu.Menu
129
+ position='right'
130
+ >
131
+ <Menu.Item
132
+ className='button-container'
133
+ >
134
+ <Button
135
+ content={i18n.t('Common.buttons.save')}
136
+ disabled={props.loading || props.saving}
137
+ onClick={props.onSave}
138
+ primary
139
+ />
140
+ <Button
141
+ basic
142
+ content={i18n.t('Common.buttons.cancel')}
143
+ disabled={props.loading || props.saving}
144
+ onClick={props.onCancel}
145
+ />
146
+ </Menu.Item>
147
+ </Menu.Menu>
148
+ </Menu>
149
+ );
150
+
151
+ if (props.stickyMenu) {
152
+ return (
153
+ <Sticky
154
+ context={contentRef}
155
+ offset={20}
156
+ >
157
+ { menu }
158
+ </Sticky>
159
+ );
160
+ }
161
+
162
+ return menu;
163
+ });
164
+
165
+ useEffect(() => {
166
+ // Sets the default tab to the first tab in the list, or the tab on the URL state
167
+ let defaultTab;
168
+
169
+ if (props.defaultTab) {
170
+ defaultTab = { key: props.defaultTab };
171
+ } else {
172
+ defaultTab = _.first(tabs);
173
+ }
174
+
175
+ if (defaultTab) {
176
+ onTabClick(defaultTab);
177
+ }
178
+
179
+ // Sets the saved indicator based on the URL state
180
+ if (props.saved) {
181
+ setSaved(true);
182
+ }
183
+ }, []);
184
+
185
+ return (
186
+ <Grid
187
+ className={className}
188
+ >
189
+ <Grid.Row>
190
+ <Grid.Column>
191
+ { renderTabs() }
192
+ </Grid.Column>
193
+ </Grid.Row>
194
+ <Grid.Row>
195
+ <Grid.Column>
196
+ <Ref
197
+ innerRef={contentRef}
198
+ >
199
+ <div>
200
+ <Form
201
+ error={!_.isEmpty(props.errors)}
202
+ loading={props.loading || props.saving}
203
+ noValidate
204
+ >
205
+ <Message
206
+ error
207
+ header={i18n.t('Common.errors.save')}
208
+ list={props.errors}
209
+ />
210
+ { tab && tab.props.children }
211
+ </Form>
212
+ { saved && (
213
+ <Toaster
214
+ onDismiss={() => setSaved(false)}
215
+ type={Toaster.MessageTypes.positive}
216
+ >
217
+ <Message.Header
218
+ content={i18n.t('Common.messages.save.header')}
219
+ />
220
+ <Message.Content
221
+ content={i18n.t('Common.messages.save.content')}
222
+ />
223
+ </Toaster>
224
+ )}
225
+ </div>
226
+ </Ref>
227
+ </Grid.Column>
228
+ </Grid.Row>
229
+ </Grid>
230
+ );
231
+ };
232
+
233
+ SimpleEditPage.defaultProps = {
234
+ menuProps: {
235
+ pointing: true,
236
+ secondary: true
237
+ }
238
+ };
239
+
240
+ const Tab = (props: any) => props.children;
241
+ Tab.displayName = 'Tab';
242
+
243
+ // $FlowIgnore
244
+ const SimpleEditPageStatic = Object.assign(SimpleEditPage, { Tab });
245
+
246
+ export default SimpleEditPageStatic;
@@ -14,6 +14,7 @@ export { default as BibliographyForm } from './components/BibliographyForm';
14
14
  export { default as BibliographyList } from './components/BibliographyList';
15
15
  export { default as BibliographySearchInput } from './components/BibliographySearchInput';
16
16
  export { default as BooleanIcon } from './components/BooleanIcon';
17
+ export { default as Breadcrumbs } from './components/Breadcrumbs';
17
18
  export { default as CancelButton } from './components/CancelButton';
18
19
  export { default as ColorButton } from './components/ColorButton';
19
20
  export { default as ColorPickerModal } from './components/ColorPickerModal';
@@ -88,6 +89,7 @@ export { default as Section } from './components/Section';
88
89
  export { default as Selectize } from './components/Selectize';
89
90
  export { default as SelectizeHeader } from './components/SelectizeHeader';
90
91
  export { default as SelectizeImageHeader } from './components/SelectizeImageHeader';
92
+ export { default as SimpleEditPage } from './components/SimpleEditPage';
91
93
  export { default as StyleSelector } from './components/StyleSelector';
92
94
  export { default as TabbedModal } from './components/TabbedModal';
93
95
  export { default as TabsMenu } from './components/TabsMenu';