@startlibs/components 1.0.4 → 1.1.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.
Files changed (115) hide show
  1. package/lib/BasicBulletList.mjs +9 -0
  2. package/lib/BasicBulletList.mjs.map +1 -0
  3. package/lib/Button.mjs +95 -0
  4. package/lib/Button.mjs.map +1 -0
  5. package/lib/ContextMenu.mjs +452 -0
  6. package/lib/ContextMenu.mjs.map +1 -0
  7. package/lib/Dialog.mjs +232 -0
  8. package/lib/Dialog.mjs.map +1 -0
  9. package/lib/Draggable.mjs +880 -0
  10. package/lib/Draggable.mjs.map +1 -0
  11. package/lib/ErrorBoundary.mjs +108 -0
  12. package/lib/ErrorBoundary.mjs.map +1 -0
  13. package/lib/FillLastLineResizer.mjs +238 -0
  14. package/lib/FillLastLineResizer.mjs.map +1 -0
  15. package/lib/Flip.mjs +604 -0
  16. package/lib/Flip.mjs.map +1 -0
  17. package/lib/FlipList.mjs +186 -0
  18. package/lib/FlipList.mjs.map +1 -0
  19. package/lib/FormatDate.mjs +25 -0
  20. package/lib/FormatDate.mjs.map +1 -0
  21. package/lib/Icon.mjs +50 -0
  22. package/lib/Icon.mjs.map +1 -0
  23. package/lib/Image.mjs +41 -0
  24. package/lib/Image.mjs.map +1 -0
  25. package/lib/Link.mjs +116 -0
  26. package/lib/Link.mjs.map +1 -0
  27. package/lib/Loading.mjs +10 -0
  28. package/lib/Loading.mjs.map +1 -0
  29. package/lib/LoadingDiv.mjs +83 -0
  30. package/lib/LoadingDiv.mjs.map +1 -0
  31. package/lib/Notifications.mjs +63 -0
  32. package/lib/Notifications.mjs.map +1 -0
  33. package/lib/PageLoader.mjs +42 -0
  34. package/lib/PageLoader.mjs.map +1 -0
  35. package/lib/PersistentNotifications.mjs +130 -0
  36. package/lib/PersistentNotifications.mjs.map +1 -0
  37. package/lib/Popup.mjs +49 -0
  38. package/lib/Popup.mjs.map +1 -0
  39. package/lib/SplitColumnsContainer.mjs +43 -0
  40. package/lib/SplitColumnsContainer.mjs.map +1 -0
  41. package/lib/SwitchDiv.mjs +61 -0
  42. package/lib/SwitchDiv.mjs.map +1 -0
  43. package/lib/TextButton.mjs +41 -0
  44. package/lib/TextButton.mjs.map +1 -0
  45. package/lib/ToastNotifications.mjs +215 -0
  46. package/lib/ToastNotifications.mjs.map +1 -0
  47. package/lib/Tooltip.mjs +160 -0
  48. package/lib/Tooltip.mjs.map +1 -0
  49. package/lib/TransitionDiv.mjs +56 -0
  50. package/lib/TransitionDiv.mjs.map +1 -0
  51. package/lib/WarningDialog.mjs +84 -0
  52. package/lib/WarningDialog.mjs.map +1 -0
  53. package/lib/alt/buttons/OnClickLoadingButton.mjs +36 -0
  54. package/lib/alt/buttons/OnClickLoadingButton.mjs.map +1 -0
  55. package/lib/alt/dialogs/DialogWithImageHeader.mjs +14 -0
  56. package/lib/alt/dialogs/DialogWithImageHeader.mjs.map +1 -0
  57. package/lib/alt/dialogs/DialogWithSidebar.mjs +106 -0
  58. package/lib/alt/dialogs/DialogWithSidebar.mjs.map +1 -0
  59. package/lib/alt/dialogs/MobileBubbleDialog.mjs +49 -0
  60. package/lib/alt/dialogs/MobileBubbleDialog.mjs.map +1 -0
  61. package/lib/containers/DialogContainer.mjs +413 -0
  62. package/lib/containers/DialogContainer.mjs.map +1 -0
  63. package/lib/containers/Reposition.mjs +35 -0
  64. package/lib/containers/Reposition.mjs.map +1 -0
  65. package/lib/containers/usePositioning.mjs +294 -0
  66. package/lib/containers/usePositioning.mjs.map +1 -0
  67. package/lib/index.cjs +5000 -0
  68. package/lib/index.cjs.map +1 -0
  69. package/lib/index.mjs +37 -0
  70. package/lib/index.mjs.map +1 -0
  71. package/lib/useDialogWithToggle.mjs +18 -0
  72. package/lib/useDialogWithToggle.mjs.map +1 -0
  73. package/lib/willUseIsSticky.mjs +123 -0
  74. package/lib/willUseIsSticky.mjs.map +1 -0
  75. package/package.json +44 -29
  76. package/src/BasicBulletList.jsx +21 -0
  77. package/src/Button.jsx +288 -0
  78. package/src/ContextMenu.jsx +621 -0
  79. package/src/Dialog.jsx +413 -0
  80. package/src/Draggable.jsx +801 -0
  81. package/src/ErrorBoundary.jsx +75 -0
  82. package/src/FillLastLineResizer.jsx +226 -0
  83. package/src/Flip.jsx +560 -0
  84. package/src/FlipList.jsx +136 -0
  85. package/src/FormatDate.jsx +9 -0
  86. package/src/Icon.jsx +49 -0
  87. package/src/Image.jsx +23 -0
  88. package/src/Link.jsx +68 -0
  89. package/src/Loading.jsx +29 -0
  90. package/src/LoadingDiv.jsx +75 -0
  91. package/src/Notifications.jsx +89 -0
  92. package/src/PageLoader.jsx +48 -0
  93. package/src/PersistentNotifications.jsx +182 -0
  94. package/src/Popup.jsx +49 -0
  95. package/src/SplitColumnsContainer.jsx +63 -0
  96. package/src/SwitchDiv.jsx +30 -0
  97. package/src/TextButton.jsx +34 -0
  98. package/src/ToastNotifications.jsx +298 -0
  99. package/src/Tooltip.jsx +158 -0
  100. package/src/TransitionDiv.jsx +34 -0
  101. package/src/WarningDialog.jsx +70 -0
  102. package/src/alt/buttons/OnClickLoadingButton.jsx +16 -0
  103. package/src/alt/dialogs/DialogWithImageHeader.jsx +42 -0
  104. package/src/alt/dialogs/DialogWithSidebar.jsx +135 -0
  105. package/src/alt/dialogs/MobileBubbleDialog.jsx +56 -0
  106. package/src/containers/DialogContainer.jsx +423 -0
  107. package/src/containers/Reposition.jsx +24 -0
  108. package/src/containers/usePositioning.jsx +305 -0
  109. package/src/index.js +35 -0
  110. package/src/useDialogWithToggle.jsx +12 -0
  111. package/src/willUseIsSticky.jsx +113 -0
  112. package/.babelrc +0 -27
  113. package/lib/index.js +0 -5429
  114. package/rollup.config.js +0 -52
  115. package/yarn-error.log +0 -3724
package/src/Dialog.jsx ADDED
@@ -0,0 +1,413 @@
1
+ import React from 'react';
2
+ import _ from 'lodash/fp'
3
+ import styled, {css} from 'styled-components'
4
+ import {darken} from 'polished'
5
+
6
+ import {callIfFunction, customTheme, getColor, media} from '@startlibs/utils'
7
+ import {StartlibsConsumer, useEnsureRef} from '@startlibs/core'
8
+
9
+ import {Button} from './Button'
10
+ import {Icon} from './Icon'
11
+ import {LoadingDiv} from './LoadingDiv'
12
+ import {fixDoubledLastMarginBottom} from './SplitColumnsContainer'
13
+ import {DialogContainer} from './containers/DialogContainer'
14
+ import {willUseIsSticky} from './willUseIsSticky'
15
+
16
+ export const DialogIcon = styled(Icon)`
17
+ font-size: 60px;
18
+ width: 8rem;
19
+ text-align: center;
20
+ color: #d2d2d2;
21
+ right: 0;
22
+ top: 1rem;
23
+ position: absolute;
24
+ ${media.desktop`
25
+ top: 0
26
+ `}
27
+ `
28
+
29
+ const CloseIcon = styled(Icon).attrs(_.assign({icon: 'x'}))`
30
+ position: absolute;
31
+ width: 32px;
32
+ height: 32px;
33
+ font-size: 24px;
34
+ line-height: 32px;
35
+ cursor: pointer;
36
+ border-radius: 50%;
37
+ text-align: center;
38
+ color: rgba(0,0,0,0.3);
39
+ transition: 0.2s linear;
40
+ z-index: 1000;
41
+ top: 1rem;
42
+ right: 1rem;
43
+ &:hover {
44
+ background-color: rgba(0,0,0,0.075);
45
+ }
46
+ `
47
+
48
+ const DialogIllustration = styled.img`
49
+ max-width: 16rem;
50
+ position: absolute;
51
+ right: 1rem;
52
+ bottom: 0;
53
+ ${media.mobile`
54
+ display: none;
55
+ `}
56
+ `
57
+
58
+ const DialogContent = styled((props) =>
59
+ <div {...props}>
60
+ {fixDoubledLastMarginBottom(props.children)}
61
+ {props.icon && <DialogIcon icon={props.icon}/>}
62
+ {props.illustration && <DialogIllustration src={props.illustration}/>}
63
+ </div>
64
+ )`
65
+ outline:none;
66
+ width: 100%;
67
+ position: relative;
68
+ min-height: 5rem;
69
+ ${props => props.icon && css`
70
+ padding-right: 8rem;
71
+ min-height: 6rem;
72
+ `}
73
+ ${props => props.illustration && css`
74
+ padding-right: 15rem;
75
+ ${media.mobile`
76
+ padding-right: 1.5rem;
77
+ `}
78
+ `}
79
+ p {
80
+ font-size: 14px;
81
+ }
82
+ `
83
+
84
+ const DialogTitle = styled.h2`
85
+ margin-bottom: 1rem;
86
+ padding-bottom: 1rem;
87
+ ${props => props.fixTitleMargin && `
88
+ padding-bottom: 0;
89
+ `}
90
+ ${media.mobile`
91
+ font-size: 19px;
92
+ `}
93
+ `
94
+
95
+ const DialogFooter = styled.div`
96
+ display: block;
97
+ padding-top: 1rem;
98
+ margin-top: 1rem;
99
+ align-items: stretch;
100
+ text-align: right;
101
+ ${props => props.sticky && css`
102
+ position: sticky;
103
+ background: white;
104
+ width: inherit;
105
+ z-index: 50;
106
+ ${media.desktop`
107
+ bottom: -3rem;
108
+ margin-left: -2rem;
109
+ margin-right: -2rem;
110
+ margin-bottom: -1.5rem;
111
+ margin-top: 0.5rem;
112
+ padding: 1.5rem 2rem;
113
+ border-radius: 0 0 1rem 1rem;
114
+ `}
115
+ ${media.mobile`
116
+ bottom: -0.5rem;
117
+ padding: 0.75rem 1.75rem;
118
+ margin-bottom: -1rem;
119
+ margin-left: -1.75rem;
120
+ margin-right: -1.75rem;
121
+ `}
122
+ ${props => props.isSticky && `
123
+ box-shadow: 0 0px 2px 0 rgba(0,0,0,0.1), 0 0px 5px 1px rgba(0,0,0,0.15);
124
+ border-radius: 0;
125
+ `}
126
+ `}
127
+ ${media.max(370)`
128
+ ${Button} {
129
+ min-width: unset;
130
+ padding-left: 1.25rem;
131
+ padding-right: 1.25rem;
132
+ }
133
+ `}
134
+ // Footer's plain (non-colored) buttons use the "light" look; colored variants keep their color.
135
+ // ponytail: for a plain footer button that shows a loading spinner, pass \`light\` explicitly so the spinner renders dark instead of white-on-light.
136
+ ${Button}:not(.styled-button-colored) {
137
+ background: ${props => darken(0.03, getColor('bluishGray')(props))};
138
+ color: ${getColor('gray90')};
139
+ &:hover {
140
+ background: ${props => darken(0.06, getColor('bluishGray')(props))};
141
+ }
142
+ &:active {
143
+ background: ${props => darken(0.09, getColor('bluishGray')(props))};
144
+ }
145
+ }
146
+ `
147
+
148
+ const FooterAuxiliaryContainer = styled.div`
149
+ flex-grow: 1;
150
+ align-self: center;
151
+ display: inline-block;
152
+ padding-right: 1rem;
153
+ float: left;
154
+ `
155
+
156
+ // THIS IS NEEDED FOR LOADING ANIMATION ON DialogWithImageHeader
157
+
158
+ export const DialogContentWrapper = styled(LoadingDiv)`
159
+ padding: 2rem;
160
+ margin: -2rem;
161
+ ${media.mobile`
162
+ padding: 1.75rem;
163
+ margin: -1.75rem;
164
+ `}
165
+ `
166
+ const DialogAlwaysVisibleDiv = styled.div`
167
+ padding: 2rem 2rem 0;
168
+ margin: -2rem;
169
+ ${media.mobile`
170
+ padding: 1.75rem 1.75rem 0;
171
+ margin: -1.75rem;
172
+ `}
173
+ `
174
+ // END
175
+
176
+ const Dialog = styled(React.forwardRef(({className, skipWrap, wrapChildren, stickyFooter, fixTitleMargin, skipCloseIcon, isLoading, footer, Content, children, title, illustration, icon, auxiliarActions, container = true, onClose, ...props}, outerRef) => {
177
+ const ref = useEnsureRef(outerRef)
178
+ const [WithIsSticky] = willUseIsSticky()
179
+
180
+ const handleClose = React.useCallback(async () => {
181
+ if (onClose) await onClose().catch(console.error)
182
+ if (props.closeDialog) props.closeDialog()
183
+ }, [onClose, props.closeDialog])
184
+
185
+ const alwaysVisible = (confirmCloseDialog) => <>
186
+ {
187
+ props.closeDialog && !skipCloseIcon &&
188
+ <CloseIcon className="JS-dialog-close" onClick={confirmCloseDialog}/>
189
+ }
190
+ {
191
+ title &&
192
+ <DialogTitle fixTitleMargin>{callIfFunction(title)}</DialogTitle>
193
+ }
194
+ </>
195
+
196
+ const content = <>
197
+ {
198
+ Content &&
199
+ <DialogContent icon={icon} illustration={illustration}>{callIfFunction(Content)}</DialogContent>
200
+ }
201
+ {
202
+ !Content && children &&
203
+ <DialogContent icon={icon} illustration={illustration}>{wrapChildren ? wrapChildren(children) : callIfFunction(children)}</DialogContent>
204
+ }
205
+ {
206
+ Content && children && (wrapChildren ? wrapChildren(children) : callIfFunction(children))
207
+ }
208
+ {
209
+ footer &&
210
+ <WithIsSticky bottom>{(sentinel, isSticky) => <>
211
+ <DialogFooter isSticky={isSticky} sticky={stickyFooter}>
212
+ {auxiliarActions && <FooterAuxiliaryContainer>{callIfFunction(auxiliarActions)}</FooterAuxiliaryContainer>}
213
+ {callIfFunction(footer)}
214
+ </DialogFooter>
215
+ {sentinel()}
216
+ </>
217
+ }</WithIsSticky>
218
+ }
219
+ </>
220
+
221
+ const dialog = (confirmCloseDialog) => <div
222
+ className={className + " JS-has-wc JS-dialog"}
223
+ ref={ref}
224
+ >
225
+ {skipWrap
226
+ ? <>
227
+ {alwaysVisible(confirmCloseDialog)}
228
+ {content}
229
+ </>
230
+ : <>
231
+ <DialogAlwaysVisibleDiv>
232
+ {alwaysVisible(confirmCloseDialog)}
233
+ </DialogAlwaysVisibleDiv>
234
+ <DialogContentWrapper isLoading={isLoading}>
235
+ {content}
236
+ </DialogContentWrapper>
237
+ </>
238
+ }
239
+ </div>
240
+
241
+ return container
242
+ ? <DialogContainer {...props} DialogErrorContent={DialogErrorContent}
243
+ ref={ref}
244
+ title={title}
245
+ closeDialog={handleClose}
246
+ >{({confirmCloseDialog}) => dialog(confirmCloseDialog)}</DialogContainer>
247
+ : dialog(handleClose)
248
+ }))`
249
+ @supports (position:sticky) {
250
+ will-change: transform, opacity;
251
+ &.JS-wc-off {
252
+ will-change: auto;
253
+ }
254
+ }
255
+
256
+ border-radius: 24px;
257
+ padding: 2rem;
258
+ background-color: white;
259
+ width: 100%;
260
+ max-width: 45rem;
261
+ /* Override opcional via prop. Fica ANTES do media.mobile de propósito: no mobile
262
+ o max-width:100% de lá continua ganhando. Inerte sem a prop → zero efeito em
263
+ quem já ajusta a largura por css="max-width:...". */
264
+ ${props => props.$maxWidth && css`max-width: ${props.$maxWidth};`}
265
+ margin: auto;
266
+ position: relative;
267
+ align-self: baseline;
268
+ display: block;
269
+ overflow: clip;
270
+ > h3:first-child {
271
+ margin-top: 0.5rem;
272
+ }
273
+ p {
274
+ line-height: 140%;
275
+ }
276
+ ${media.mobile`
277
+ padding: 1.75rem;
278
+ max-width: 100%;
279
+ ${props => props.fullscreenOnMobile && `
280
+ border-radius: 0;
281
+ min-height: 100%;
282
+ `};
283
+ `}
284
+ ${props => props.closeDialog && css`
285
+ ${DialogTitle} {
286
+ padding-right: 2rem;
287
+ }
288
+ `}
289
+ ${customTheme('Dialog')}
290
+ `
291
+
292
+
293
+ export {
294
+ FooterAuxiliaryContainer,
295
+ DialogTitle,
296
+ DialogFooter,
297
+ DialogContent,
298
+ CloseIcon,
299
+ Dialog
300
+ }
301
+
302
+ export const ConfirmDialog = (props) => <Dialog {...props} placement="Confirm-Dialog">{props.children}</Dialog>
303
+
304
+ export const DefaultConfirmDialog = (props) => (
305
+ <StartlibsConsumer>{({confirmDialogLabels}) => {
306
+ const handleCancel = async () => {
307
+ if (props.onCancel) props.onCancel()
308
+ if (props.onClose) await props.onClose().catch(console.error)
309
+ }
310
+
311
+ return (
312
+ <ConfirmDialog
313
+ title={confirmDialogLabels.title}
314
+ closeDialog={handleCancel}
315
+ onClose={props.onClose}
316
+ onConfirm={props.onConfirm}
317
+ footer={<>
318
+ <Button onClick={handleCancel}>{confirmDialogLabels.cancel}</Button>
319
+ <Button
320
+ hover="success" isLoading={props.isLoading}
321
+ onClick={props.onSaveInstead}
322
+ >{confirmDialogLabels.confirm}</Button>
323
+ <Button autoFocus alert onClick={props.onConfirm}>{confirmDialogLabels.discard}</Button>
324
+ </>}
325
+ >
326
+ <p>{confirmDialogLabels.content}</p>
327
+ </ConfirmDialog>
328
+ )}}
329
+ </StartlibsConsumer>)
330
+
331
+ // Estado de erro genérico da dialog: badge circular centralizado (mesma linguagem
332
+ // dos error pages da app) no lugar do ícone cinza flutuando no canto. Cores em rgba
333
+ // (não getColor) pra não depender dos tokens de tema da app que consome a lib.
334
+ const DialogErrorLayout = styled.div`
335
+ text-align: center;
336
+ /* max-width (não <br/>) força a quebra do título em ~2 linhas como o error page,
337
+ mas deixa telas estreitas quebrarem sozinhas. Ajuste fino se o corte não bater. */
338
+ ${DialogTitle} {
339
+ max-width: 18rem;
340
+ margin-left: auto;
341
+ margin-right: auto;
342
+ }
343
+ ${DialogContent} {
344
+ min-height: 0; /* mata o vão vazio que sobrava acima do botão */
345
+ p {
346
+ max-width: 20rem;
347
+ margin-left: auto;
348
+ margin-right: auto;
349
+ }
350
+ }
351
+ ${DialogFooter} {
352
+ text-align: center;
353
+ margin-top: 0;
354
+ padding-top: 0.5rem;
355
+ }
356
+ `
357
+
358
+ const DialogErrorBadge = styled.div`
359
+ width: 72px;
360
+ height: 72px;
361
+ margin: 0 auto 1.5rem;
362
+ border-radius: 50%;
363
+ background: rgba(0, 0, 0, 0.06);
364
+ display: flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ ${Icon} {
368
+ font-size: 40px;
369
+ color: rgba(0, 0, 0, 0.4);
370
+ }
371
+ `
372
+
373
+ export const DialogErrorContent = ({ closeDialog, onClose, title }) => {
374
+ const handleClose = React.useCallback(async () => {
375
+ if (onClose) await onClose().catch(console.error)
376
+ if (closeDialog) closeDialog()
377
+ }, [onClose, closeDialog])
378
+
379
+ return (
380
+ <StartlibsConsumer>
381
+ {({ ErrorDialog: { title, content, close } }) => (
382
+ <Dialog as="div" $maxWidth="30rem">
383
+ {closeDialog && <CloseIcon onClick={handleClose} />}
384
+ <DialogErrorLayout>
385
+ <DialogErrorBadge><Icon icon="warning"/></DialogErrorBadge>
386
+ {title && <DialogTitle fixTitleMargin>{title}</DialogTitle>}
387
+ <DialogContent>{content}</DialogContent>
388
+ <DialogFooter>
389
+ <Button highlight rounded onClick={handleClose}>{close}</Button>
390
+ </DialogFooter>
391
+ </DialogErrorLayout>
392
+ </Dialog>
393
+ )}
394
+ </StartlibsConsumer>
395
+ )
396
+ }
397
+
398
+ export const DialogError = ({ closeDialog, onClose }) => {
399
+ const handleClose = React.useCallback(async () => {
400
+ if (onClose) await onClose().catch(console.error)
401
+ if (closeDialog) closeDialog()
402
+ }, [onClose, closeDialog])
403
+
404
+ return (
405
+ <StartlibsConsumer>
406
+ {({ ErrorDialog: { title } }) => (
407
+ <Dialog closeDialog={handleClose} title={title}>
408
+ <DialogErrorContent closeDialog={handleClose} onClose={onClose} />
409
+ </Dialog>
410
+ )}
411
+ </StartlibsConsumer>
412
+ )
413
+ }