dinocollab-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +54 -0
  2. package/dist/_virtual/_rollupPluginBabelHelpers.js +431 -0
  3. package/dist/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
  4. package/dist/assets/vector-404265a04f4f9c8be1f.webp +0 -0
  5. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@1_d0d2002d9033600b6738d939bd598bc6/node_modules/tslib/tslib.es6.js +46 -0
  6. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@1_d0d2002d9033600b6738d939bd598bc6/node_modules/tslib/tslib.es6.js.map +1 -0
  7. package/dist/src/api-context/alert-global.js +151 -0
  8. package/dist/src/api-context/alert-global.js.map +1 -0
  9. package/dist/src/api-context/drawer-global.js +105 -0
  10. package/dist/src/api-context/drawer-global.js.map +1 -0
  11. package/dist/src/api-context/global-modal.js +87 -0
  12. package/dist/src/api-context/global-modal.js.map +1 -0
  13. package/dist/src/api-context/popover-global.js +102 -0
  14. package/dist/src/api-context/popover-global.js.map +1 -0
  15. package/dist/src/api-context/popover.js +86 -0
  16. package/dist/src/api-context/popover.js.map +1 -0
  17. package/dist/src/api-context/ui.units.js +21 -0
  18. package/dist/src/api-context/ui.units.js.map +1 -0
  19. package/dist/src/components/copy-to-clipboard.js +105 -0
  20. package/dist/src/components/copy-to-clipboard.js.map +1 -0
  21. package/dist/src/components/custom.breadcrumbs.js +61 -0
  22. package/dist/src/components/custom.breadcrumbs.js.map +1 -0
  23. package/dist/src/components/help-tooltip.js +91 -0
  24. package/dist/src/components/help-tooltip.js.map +1 -0
  25. package/dist/src/components/image-with-fallback.js +48 -0
  26. package/dist/src/components/image-with-fallback.js.map +1 -0
  27. package/dist/src/components/text-editor.js +117 -0
  28. package/dist/src/components/text-editor.js.map +1 -0
  29. package/dist/src/form/create.autocomplete.chips.js +218 -0
  30. package/dist/src/form/create.autocomplete.chips.js.map +1 -0
  31. package/dist/src/form/create.date-expired.js +201 -0
  32. package/dist/src/form/create.date-expired.js.map +1 -0
  33. package/dist/src/form/create.date-picker.js +125 -0
  34. package/dist/src/form/create.date-picker.js.map +1 -0
  35. package/dist/src/form/create.form-base.js +135 -0
  36. package/dist/src/form/create.form-base.js.map +1 -0
  37. package/dist/src/form/create.form-comfirm.js +119 -0
  38. package/dist/src/form/create.form-comfirm.js.map +1 -0
  39. package/dist/src/form/create.form-grid-layout.js +177 -0
  40. package/dist/src/form/create.form-grid-layout.js.map +1 -0
  41. package/dist/src/form/create.form-grid-layout.units.js +39 -0
  42. package/dist/src/form/create.form-grid-layout.units.js.map +1 -0
  43. package/dist/src/form/create.input-base.js +260 -0
  44. package/dist/src/form/create.input-base.js.map +1 -0
  45. package/dist/src/form/create.input.file.js +74 -0
  46. package/dist/src/form/create.input.file.js.map +1 -0
  47. package/dist/src/form/create.select-simple.js +104 -0
  48. package/dist/src/form/create.select-simple.js.map +1 -0
  49. package/dist/src/form/create.select-with-api.js +271 -0
  50. package/dist/src/form/create.select-with-api.js.map +1 -0
  51. package/dist/src/form/create.text-editor.js +156 -0
  52. package/dist/src/form/create.text-editor.js.map +1 -0
  53. package/dist/src/form/dino-form.js +42 -0
  54. package/dist/src/form/dino-form.js.map +1 -0
  55. package/dist/src/form/helper.js +157 -0
  56. package/dist/src/form/helper.js.map +1 -0
  57. package/dist/src/form/modal-wrapper.js +75 -0
  58. package/dist/src/form/modal-wrapper.js.map +1 -0
  59. package/dist/src/form/validator.js +186 -0
  60. package/dist/src/form/validator.js.map +1 -0
  61. package/dist/src/hooks/index.js +48 -0
  62. package/dist/src/hooks/index.js.map +1 -0
  63. package/dist/src/index.js +26 -0
  64. package/dist/src/index.js.map +1 -0
  65. package/dist/src/redux/create.hoc-lazy.js +67 -0
  66. package/dist/src/redux/create.hoc-lazy.js.map +1 -0
  67. package/dist/src/redux/dino.js +11 -0
  68. package/dist/src/redux/dino.js.map +1 -0
  69. package/dist/src/redux/types.js +9 -0
  70. package/dist/src/redux/types.js.map +1 -0
  71. package/dist/src/redux/ui.error-page.js +80 -0
  72. package/dist/src/redux/ui.error-page.js.map +1 -0
  73. package/dist/src/redux/vector-404.webp.js +4 -0
  74. package/dist/src/redux/vector-404.webp.js.map +1 -0
  75. package/dist/src/table/context.js +12 -0
  76. package/dist/src/table/context.js.map +1 -0
  77. package/dist/src/table/create.action-row.js +135 -0
  78. package/dist/src/table/create.action-row.js.map +1 -0
  79. package/dist/src/table/create.status-cell.js +49 -0
  80. package/dist/src/table/create.status-cell.js.map +1 -0
  81. package/dist/src/table/create.table.js +233 -0
  82. package/dist/src/table/create.table.js.map +1 -0
  83. package/dist/src/table/custom.filter-operators.js +89 -0
  84. package/dist/src/table/custom.filter-operators.js.map +1 -0
  85. package/dist/src/table/dino.js +129 -0
  86. package/dist/src/table/dino.js.map +1 -0
  87. package/dist/src/table/helpers.js +116 -0
  88. package/dist/src/table/helpers.js.map +1 -0
  89. package/dist/src/table/model-filter.js +23 -0
  90. package/dist/src/table/model-filter.js.map +1 -0
  91. package/dist/src/table/toolbar-pannel.js +134 -0
  92. package/dist/src/table/toolbar-pannel.js.map +1 -0
  93. package/dist/src/table/ui.buttons.js +60 -0
  94. package/dist/src/table/ui.buttons.js.map +1 -0
  95. package/dist/src/table/ui.units.js +201 -0
  96. package/dist/src/table/ui.units.js.map +1 -0
  97. package/dist/src/utils/dayjs-config.js +12 -0
  98. package/dist/src/utils/dayjs-config.js.map +1 -0
  99. package/dist/src/utils/helpers.js +197 -0
  100. package/dist/src/utils/helpers.js.map +1 -0
  101. package/dist/src/utils/json-object.js +38 -0
  102. package/dist/src/utils/json-object.js.map +1 -0
  103. package/dist/src/utils/query-param.js +172 -0
  104. package/dist/src/utils/query-param.js.map +1 -0
  105. package/package.json +52 -0
  106. package/rollup.config.js +39 -0
  107. package/src/@types/global.d.ts +5 -0
  108. package/src/api-context/alert-global.tsx +174 -0
  109. package/src/api-context/drawer-global.tsx +116 -0
  110. package/src/api-context/global-modal.tsx +109 -0
  111. package/src/api-context/index.ts +13 -0
  112. package/src/api-context/popover-global.tsx +107 -0
  113. package/src/api-context/popover.tsx +89 -0
  114. package/src/api-context/ui.units.tsx +10 -0
  115. package/src/components/copy-to-clipboard.tsx +86 -0
  116. package/src/components/custom.breadcrumbs.tsx +67 -0
  117. package/src/components/help-tooltip.tsx +75 -0
  118. package/src/components/image-with-fallback.tsx +51 -0
  119. package/src/components/index.tsx +1 -0
  120. package/src/components/input-debounce-timer.tsx +138 -0
  121. package/src/components/loading-buttons.tsx +35 -0
  122. package/src/components/text-editor.preview.tsx +30 -0
  123. package/src/components/text-editor.tsx +125 -0
  124. package/src/form/README.md +55 -0
  125. package/src/form/create.autocomplete.chips.tsx +199 -0
  126. package/src/form/create.date-expired.tsx +195 -0
  127. package/src/form/create.date-picker.tsx +122 -0
  128. package/src/form/create.form-base.tsx +102 -0
  129. package/src/form/create.form-comfirm.tsx +83 -0
  130. package/src/form/create.form-grid-layout.tsx +170 -0
  131. package/src/form/create.form-grid-layout.units.tsx +37 -0
  132. package/src/form/create.input-base.tsx +222 -0
  133. package/src/form/create.input.file.tsx +76 -0
  134. package/src/form/create.select-simple.tsx +101 -0
  135. package/src/form/create.select-with-api.tsx +213 -0
  136. package/src/form/create.text-editor.tsx +161 -0
  137. package/src/form/dino-form.tsx +40 -0
  138. package/src/form/helper.ts +132 -0
  139. package/src/form/index.ts +12 -0
  140. package/src/form/modal-wrapper.tsx +75 -0
  141. package/src/form/types.ts +16 -0
  142. package/src/form/validator.ts +202 -0
  143. package/src/hooks/index.ts +44 -0
  144. package/src/index.ts +7 -0
  145. package/src/lab/create.autocomplete.simple.tsx +57 -0
  146. package/src/lab/create.dino-store.ts +59 -0
  147. package/src/lab/create.multi-select-dropdown.tsx +189 -0
  148. package/src/lab/create.select-mul-with-api/index.tsx +271 -0
  149. package/src/lab/create.select-mul-with-api/table-custom.tsx +194 -0
  150. package/src/lab/create.select-mul-with-api/types.ts +26 -0
  151. package/src/lab/create.select-mul-with-api/ui.units.tsx +163 -0
  152. package/src/lab/filter-bar/base.tsx +162 -0
  153. package/src/lab/filter-bar/create.filter-bar.tsx +190 -0
  154. package/src/lab/filter-bar/create.filter-menu.tsx +156 -0
  155. package/src/lab/filter-bar/create.filter-panel.tsx +95 -0
  156. package/src/lab/filter-bar/create.filtered.tsx +41 -0
  157. package/src/lab/filter-bar/create.sort-menu.tsx +43 -0
  158. package/src/lab/filter-bar/demo.tsx +50 -0
  159. package/src/lab/filter-bar/index.ts +6 -0
  160. package/src/lab/filter-bar/types.ts +105 -0
  161. package/src/lab/filter-bar/ui.units.tsx +70 -0
  162. package/src/lab/grafana-dashboard/configs.ts +43 -0
  163. package/src/lab/grafana-dashboard/date-time-range/absolute-time-rage.tsx +137 -0
  164. package/src/lab/grafana-dashboard/date-time-range/helpers.ts +126 -0
  165. package/src/lab/grafana-dashboard/date-time-range/index.tsx +62 -0
  166. package/src/lab/grafana-dashboard/date-time-range/menu-wrap.tsx +101 -0
  167. package/src/lab/grafana-dashboard/date-time-range/quick-ranges.tsx +161 -0
  168. package/src/lab/grafana-dashboard/date-time-range/types.ts +9 -0
  169. package/src/lab/grafana-dashboard/date-time-range/units.tsx +18 -0
  170. package/src/lab/grafana-dashboard/helper.ts +25 -0
  171. package/src/lab/grafana-dashboard/hooks.tsx +79 -0
  172. package/src/lab/grafana-dashboard/icons.tsx +67 -0
  173. package/src/lab/grafana-dashboard/index.tsx +120 -0
  174. package/src/lab/grafana-dashboard/top-bar.tsx +62 -0
  175. package/src/lab/grafana-dashboard/top-bar.types.ts +5 -0
  176. package/src/lab/grafana-dashboard/types.ts +8 -0
  177. package/src/lab/media-player.core1.tsx +273 -0
  178. package/src/lab/media-player.muted.tsx +62 -0
  179. package/src/lab/media-player.units.ts +80 -0
  180. package/src/lab/table-grid/create.table-grid.tsx +183 -0
  181. package/src/lab/table-grid/demo.tsx +53 -0
  182. package/src/lab/table-grid/dino.tsx +8 -0
  183. package/src/lab/table-grid/helpers.tsx +11 -0
  184. package/src/lab/table-grid/index.ts +3 -0
  185. package/src/lab/table-grid/item-actions.tsx +138 -0
  186. package/src/lab/table-grid/toolbar-pannel.tsx +98 -0
  187. package/src/lab/table-grid/types.ts +68 -0
  188. package/src/redux/create.hoc-lazy.tsx +80 -0
  189. package/src/redux/dino.ts +9 -0
  190. package/src/redux/index.ts +6 -0
  191. package/src/redux/types.ts +27 -0
  192. package/src/redux/ui.error-page.tsx +62 -0
  193. package/src/redux/ui.units.tsx +41 -0
  194. package/src/redux/vector-404.webp +0 -0
  195. package/src/table/context.tsx +16 -0
  196. package/src/table/create.action-row.tsx +91 -0
  197. package/src/table/create.status-cell.tsx +51 -0
  198. package/src/table/create.table.tsx +239 -0
  199. package/src/table/custom.filter-operators.ts +94 -0
  200. package/src/table/dino.tsx +120 -0
  201. package/src/table/helpers.ts +94 -0
  202. package/src/table/index.ts +13 -0
  203. package/src/table/model-filter.ts +43 -0
  204. package/src/table/toolbar-pannel.tsx +106 -0
  205. package/src/table/types.ts +50 -0
  206. package/src/table/ui.buttons.tsx +54 -0
  207. package/src/table/ui.units.tsx +189 -0
  208. package/src/utils/dayjs-config.ts +13 -0
  209. package/src/utils/helpers.ts +171 -0
  210. package/src/utils/index.ts +7 -0
  211. package/src/utils/json-object.ts +29 -0
  212. package/src/utils/mfe-events.tsx +34 -0
  213. package/src/utils/query-param.ts +129 -0
  214. package/tsconfig.json +20 -0
@@ -0,0 +1,161 @@
1
+ import React, { Component, createRef } from 'react'
2
+ import { Box, BoxProps, IconButton, styled, Tooltip, Typography } from '@mui/material'
3
+ import FullscreenIcon from '@mui/icons-material/Fullscreen'
4
+ import FullscreenExitIcon from '@mui/icons-material/FullscreenExit'
5
+ import { IFormInputBase } from './types'
6
+ import { getErrorMessage } from './helper'
7
+ import TextEditor, { textEditorClasses } from '../components/text-editor'
8
+
9
+ interface IParams {
10
+ id: string
11
+ title: string
12
+ }
13
+
14
+ function CreateTextEditor<T>(params: IParams) {
15
+ interface IProps extends IFormInputBase<T> {}
16
+
17
+ interface IState {
18
+ isFullscreen: boolean
19
+ }
20
+
21
+ class Editor extends Component<IProps, IState> {
22
+ contentRef
23
+ constructor(props: IProps) {
24
+ super(props)
25
+ this.state = {
26
+ isFullscreen: false
27
+ }
28
+ this.contentRef = createRef<HTMLDivElement | null>()
29
+ }
30
+
31
+ componentDidMount() {
32
+ document.addEventListener('fullscreenchange', this.handleFullscreenChange)
33
+ }
34
+
35
+ componentWillUnmount() {
36
+ document.removeEventListener('fullscreenchange', this.handleFullscreenChange)
37
+ }
38
+
39
+ handleFullscreenChange = () => {
40
+ this.setState({ isFullscreen: !!document.fullscreenElement })
41
+ }
42
+
43
+ handleFullscreenToggle = () => {
44
+ if (this.contentRef.current) {
45
+ if (!document.fullscreenElement) {
46
+ this.contentRef.current.requestFullscreen().catch((err) => {
47
+ console.error(`Error attempting to enable full-screen mode: ${err.message}`)
48
+ })
49
+ } else {
50
+ document.exitFullscreen()
51
+ }
52
+ }
53
+ }
54
+
55
+ getWrapClasses = () => {
56
+ const classes = []
57
+ if (this.state.isFullscreen) classes.push(textEditorClasses.fullscreen)
58
+ return classes.join(' ')
59
+ }
60
+
61
+ render() {
62
+ const errorMessage = getErrorMessage(this.props.messageErrors, this.props.name)
63
+ const dValue = (this.props.name ? this.props.data?.[this.props.name] : '')?.toString() ?? ''
64
+
65
+ return (
66
+ <Box id={params.id} ref={this.contentRef} sx={{ backgroundColor: '#fff' }}>
67
+ {this.state.isFullscreen && (
68
+ <WrapTitle>
69
+ <Typography variant='h4' sx={{ fontWeight: 600, flex: 1, textAlign: 'center' }}>
70
+ {params.title}
71
+ </Typography>
72
+ </WrapTitle>
73
+ )}
74
+ <Wrap className={this.getWrapClasses()}>
75
+ <WrapActions>
76
+ <Tooltip title={this.state.isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'} arrow>
77
+ <CustomButton sx={{ color: '#06c' }} onClick={this.handleFullscreenToggle}>
78
+ {this.state.isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}
79
+ </CustomButton>
80
+ </Tooltip>
81
+ </WrapActions>
82
+ <WrapTextEditor>
83
+ <TextEditor
84
+ defautValue={dValue}
85
+ name={this.props.name?.toString() ?? ''}
86
+ readOnly={this.props.disabled}
87
+ error={errorMessage.error}
88
+ onBlur={() => {
89
+ if (!this.props.name) return
90
+ this.props.onBlur?.(this.props.name)
91
+ }}
92
+ />
93
+ </WrapTextEditor>
94
+ </Wrap>
95
+ </Box>
96
+ )
97
+ }
98
+ }
99
+ return Editor
100
+ }
101
+
102
+ export default CreateTextEditor
103
+
104
+ const editorHeight = 64
105
+
106
+ const Wrap = styled(Box)(({ theme }) => ({
107
+ maxWidth: theme.breakpoints.values.xl,
108
+ margin: '0 auto',
109
+ position: 'relative',
110
+ [`&.${textEditorClasses.fullscreen} .${textEditorClasses.root}`]: {
111
+ height: `calc(100vh - ${editorHeight + 10}px)`
112
+ },
113
+ [`&.${textEditorClasses.fullscreen}`]: {
114
+ width: '871px'
115
+ }
116
+ }))
117
+
118
+ const WrapTitle = styled(Box)({
119
+ height: `${editorHeight}px`,
120
+ display: 'flex',
121
+ justifyContent: 'center',
122
+ alignItems: 'center'
123
+ })
124
+
125
+ const WrapActions = styled(({ children, ...props }: BoxProps) => (
126
+ <Box {...props}>
127
+ <Box>{children}</Box>
128
+ </Box>
129
+ ))({
130
+ position: 'sticky',
131
+ top: '-1px',
132
+ zIndex: 2,
133
+ '& > div': {
134
+ height: 0,
135
+ display: 'flex',
136
+ justifyContent: 'right',
137
+ padding: '0 6px'
138
+ }
139
+ })
140
+
141
+ const CustomButton = styled(IconButton)({
142
+ flex: '0 0 auto',
143
+ marginTop: '1px',
144
+ width: '40px',
145
+ height: '40px'
146
+ })
147
+
148
+ const WrapTextEditor = styled(Box)({
149
+ backgroundColor: '#fff',
150
+ width: '100%',
151
+ '& .ql-container': {
152
+ fontFamily: '"Roboto", serif',
153
+ fontSize: '14px'
154
+ },
155
+ '& .ql-toolbar': {
156
+ position: 'sticky',
157
+ top: '-1px',
158
+ backgroundColor: '#fff',
159
+ zIndex: 1
160
+ }
161
+ })
@@ -0,0 +1,40 @@
1
+ import FormValidator from './validator'
2
+ import FormModalWrapper from './modal-wrapper'
3
+ import CreateFormBase from './create.form-base'
4
+ import CreateInputFile from './create.input.file'
5
+ import CreateInputBase from './create.input-base'
6
+ import CreateDatePicker from './create.date-picker'
7
+ import CreateTextEditor from './create.text-editor'
8
+ import CreateDateExpired from './create.date-expired'
9
+ import CreateFormComfirm from './create.form-comfirm'
10
+ import CreateSelectSimple from './create.select-simple'
11
+ import CreateSelectWithApi from './create.select-with-api'
12
+ import CreateFormGridLayout from './create.form-grid-layout'
13
+ import CreateAutocompleteChip from './create.autocomplete.chips'
14
+ import { FormBottomBarWrap } from './create.form-grid-layout.units'
15
+
16
+ class DinoFormBase {
17
+ //#region Base
18
+ ModalWrap = FormModalWrapper
19
+ BottomBarWrap = FormBottomBarWrap
20
+ FormValidator = FormValidator
21
+ validator = FormValidator.initial
22
+ createFormBase = CreateFormBase
23
+ createFormComfirm = CreateFormComfirm
24
+ createFormGridLayout = CreateFormGridLayout
25
+ //#endregion
26
+
27
+ //#region Inputs
28
+ createDateExpired= CreateDateExpired
29
+ createDatePicker = CreateDatePicker
30
+ createAutocompleteChip = CreateAutocompleteChip
31
+ createInput = CreateInputBase
32
+ createInputFile = CreateInputFile
33
+ createSelectSimple = CreateSelectSimple
34
+ createSelectWithApi = CreateSelectWithApi
35
+ createTextEditor = CreateTextEditor
36
+ //#endregion
37
+ }
38
+
39
+ const DinoForm = new DinoFormBase()
40
+ export default DinoForm
@@ -0,0 +1,132 @@
1
+ import { AxiosError } from 'axios'
2
+ import FormValidator, { IModelError, IRuleOption, PartialError } from './validator'
3
+
4
+ type AnyObject = { [key: string]: any }
5
+ const ObjectAssign = <T extends AnyObject>(model: T, ...sources: (Partial<T> | undefined)[]) => {
6
+ if (sources.length < 1) {
7
+ return model
8
+ }
9
+ const temps = sources.filter((x) => x) as T[]
10
+ return temps.reduce((a, b) => {
11
+ a = Object.assign(a, b)
12
+ return a
13
+ }, model)
14
+ }
15
+
16
+ const DeepMerge = <T extends AnyObject>(model: T, ...sources: (Partial<T> | undefined)[]) => {
17
+ sources = sources.filter((x) => !!x)
18
+ if (sources.length < 1) {
19
+ return model
20
+ }
21
+ const temps = [model, ...sources].filter((x) => x)
22
+ const fieldObjects = temps.reduce<string[]>((a, b) => {
23
+ if (!b) return a
24
+ const fields = Object.keys(b).filter((x) => typeof b[x] === 'object' && !Array.isArray(b[x]))
25
+ a.push(...fields)
26
+ return a
27
+ }, [])
28
+
29
+ const target = ObjectAssign<T>(model, ...temps.slice(1))
30
+ const temp = target as any
31
+ const temp2 = temps.slice(1).filter((x) => !!x) as Partial<T>[]
32
+ for (let index = 0; index < fieldObjects.length; index++) {
33
+ const element = fieldObjects[index]
34
+ temp[element] = DeepMerge({}, temp[element], ...temp2.map((x) => x[element]))
35
+ }
36
+ return target
37
+ }
38
+
39
+ export const getErrorMessage = function <TModel>(MessageErrors: PartialError<TModel> | undefined, key?: keyof TModel): IModelError {
40
+ return MessageErrors && (MessageErrors as any)[key] ? { ...(MessageErrors as any)[key][0], error: true } : { error: false, message: '' }
41
+ }
42
+
43
+ type ModelBase = {
44
+ [key: string]: any
45
+ }
46
+
47
+ export const convertFormDataToJson = function <TModel>(form: FormData): Partial<TModel> {
48
+ const data = Array.from(form).reduce<Partial<TModel>>((a: any, b: any[]) => {
49
+ if (!a[b[0]]) {
50
+ a[b[0]] = b[1]
51
+ } else if (Array.isArray(a[b[0]])) {
52
+ a[b[0]].push(b[1])
53
+ } else {
54
+ a[b[0]] = [a[b[0]], b[1]]
55
+ }
56
+ return a
57
+ }, {} as any) as any
58
+
59
+ Object.keys(data).forEach((key) => {
60
+ const value = data[key]
61
+ if (typeof value === 'string' && (value.toString().toLocaleLowerCase() === 'true' || value.toString().toLocaleLowerCase() === 'false')) {
62
+ data[key] = value.toString().toLocaleLowerCase() === 'true'
63
+ }
64
+ })
65
+ return data
66
+ }
67
+
68
+ export const SingleValidate = function <TModel, TPartial = TModel>(
69
+ key: keyof TModel,
70
+ modelState: TPartial,
71
+ MessageErrors: PartialError<TModel>,
72
+ Validator: FormValidator<TPartial>
73
+ ) {
74
+ const messageErrors = Validator.run(modelState) as { [key: string]: any }
75
+ if (messageErrors) {
76
+ let errors = (MessageErrors || {}) as { [key: string]: any }
77
+ const keys = Object.keys(modelState as any)
78
+ .filter((key) => !!(modelState as any)[key])
79
+ .filter((key) => ((modelState as any)[key] instanceof File ? !!((modelState as any)[key] as File).size : false))
80
+ keys.push(key as string)
81
+ keys.forEach((key) => {
82
+ if (messageErrors[key]) {
83
+ errors[key] = messageErrors[key]
84
+ } else {
85
+ delete errors[key]
86
+ }
87
+ })
88
+ return errors
89
+ }
90
+ return null
91
+ }
92
+
93
+ export const GetErrorFromResponse = function <TModel>(error: AxiosError, ModelRender: TModel) {
94
+ const data = error.response?.data as any
95
+ if (data) {
96
+ const keys = Object.keys(ModelRender as any)
97
+ const MessageErrors: PartialError<TModel> | undefined = {}
98
+ keys.forEach((key) => {
99
+ const messages = data[key]
100
+ if (Array.isArray(messages) && messages.length > 0) {
101
+ ;(MessageErrors as any)[key] = [{ message: messages[0] }]
102
+ }
103
+ })
104
+ return MessageErrors
105
+ }
106
+ }
107
+
108
+ export const ClearFieldEmpty = <TModel>(model: Extract<TModel, ModelBase>) => {
109
+ Object.keys(model).forEach((key: keyof Extract<TModel, ModelBase>) => {
110
+ if (!model[key]) delete model[key]
111
+ })
112
+ }
113
+
114
+ export const ValidateMerge = <TModel>(...validateor: (FormValidator<TModel> | undefined)[]): FormValidator<TModel> => {
115
+ const configs = validateor.map((x) => x?.configs).filter((x) => !!x)
116
+
117
+ let temp: any = Object.assign({}, ...configs)
118
+
119
+ for (let index = 0; index < Object.keys(temp).length; index++) {
120
+ const key = Object.keys(temp)[index]
121
+ //merge configs
122
+ temp[key] = DeepMerge({}, ...configs.map((x) => (x ? x[key] : { Rules: [] })))
123
+ //merge rules
124
+ temp[key].Rules = configs
125
+ .map((x) => (x ? x[key] : { Rules: [] }))
126
+ .reduce<IRuleOption<TModel>[]>((a, b) => {
127
+ a.push(...(b?.Rules ?? []))
128
+ return a
129
+ }, [])
130
+ }
131
+ return new FormValidator(temp)
132
+ }
@@ -0,0 +1,12 @@
1
+ export * from './types'
2
+
3
+ export { default as FormValidator } from './validator'
4
+ export * from './validator'
5
+
6
+ export { default as DinoForm } from './dino-form'
7
+
8
+ export { getErrorMessage, convertFormDataToJson } from './helper'
9
+
10
+ export type { ISelectWithApiOption } from './create.select-with-api'
11
+
12
+ export type { InputBaseImage } from './create.input-base'
@@ -0,0 +1,75 @@
1
+ import React, { FC, PropsWithChildren } from 'react'
2
+ import { Box, Breakpoint, IconButton, Paper, styled, SxProps, Theme, Typography } from '@mui/material'
3
+ import CloseIcon from '@mui/icons-material/Close'
4
+ import { MapGlobalModalContext } from '../api-context'
5
+
6
+ interface IProps extends PropsWithChildren {
7
+ title: string
8
+ size?: Breakpoint
9
+ fullHeight?: boolean
10
+ onCloseClick?: () => void
11
+ slots?: {
12
+ sxTopbarProps?: SxProps<Theme>
13
+ beforeTitle?: JSX.Element
14
+ }
15
+ }
16
+ const FormModalWrapper: FC<IProps> = (props) => {
17
+ return MapGlobalModalContext((context) => (
18
+ <Wrap size={props.size} className={props.fullHeight ? 'full-height' : undefined}>
19
+ <TopBar sx={props.slots?.sxTopbarProps}>
20
+ {props.slots?.beforeTitle && (
21
+ <>
22
+ {props.slots.beforeTitle}
23
+ <Box flex={1} />
24
+ </>
25
+ )}
26
+ <Typography component='h3' variant='subtitle1' sx={{ mt: '2px', ml: '8px', fontWeight: 700 }}>
27
+ {props.title}
28
+ </Typography>
29
+ <Box flex={1} />
30
+ <BtnClose
31
+ onClick={() => {
32
+ props.onCloseClick && props.onCloseClick()
33
+ context.close()
34
+ }}
35
+ >
36
+ <CloseIcon />
37
+ </BtnClose>
38
+ </TopBar>
39
+ {props.children}
40
+ </Wrap>
41
+ ))
42
+ }
43
+
44
+ export default FormModalWrapper
45
+
46
+ const Wrap = styled(Paper)<{ size?: Breakpoint }>(({ theme, size }) => ({
47
+ maxHeight: 'calc(100vh - 24px)',
48
+ overflow: 'hidden',
49
+ display: 'flex',
50
+ flexDirection: 'column',
51
+ position: 'relative',
52
+ maxWidth: 'calc(100vw - 24px)',
53
+ width: theme.breakpoints.values[size ?? 'lg'],
54
+ [theme.breakpoints.down('sm')]: { width: 'calc(100vw - 24px)' },
55
+ '&.full-height': {
56
+ height: 'calc(100vh - 24px)'
57
+ }
58
+ }))
59
+
60
+ const BtnClose = styled(IconButton)({
61
+ flex: '0 0 auto',
62
+ color: '#3c3c3c',
63
+ '& svg': { transition: 'all 0.2s' },
64
+ '&:hover svg': { color: '#ff200c' }
65
+ })
66
+
67
+ const TopBar = styled(Box)({
68
+ display: 'flex',
69
+ alignItems: 'center',
70
+ padding: '0 8px 0 8px',
71
+ gap: '6px',
72
+ flex: '0 0 auto',
73
+ boxShadow: 'rgba(145, 158, 171, 0.1) 0px 0px 2px 0px,rgba(145, 158, 171, 0.12) 0px 0px 24px -4px',
74
+ height: '48px'
75
+ })
@@ -0,0 +1,16 @@
1
+ import { PartialError } from './validator'
2
+
3
+ export interface IFormBase<TModel> {
4
+ data?: Partial<TModel>
5
+ messageErrors: PartialError<TModel>
6
+ onBlur: (keyName: keyof TModel) => void
7
+ disabled?: boolean
8
+ }
9
+
10
+ export interface IFormInputBase<TModel, TSlots = any> extends Partial<IFormBase<TModel>> {
11
+ name?: keyof TModel
12
+ label?: React.ReactNode
13
+ placeholder?: string
14
+ defaultValue?: any
15
+ slots?: TSlots
16
+ }
@@ -0,0 +1,202 @@
1
+ import { formatFileSize } from '../utils'
2
+
3
+ declare type ModelBase = { [key: string]: any }
4
+
5
+ export enum SingleRuleValidate {
6
+ Required = 'Required',
7
+ Regex = 'Regex',
8
+ RequiredValue = 'RequiredValue',
9
+ Array = 'Array',
10
+ Number = 'Number',
11
+ Email = 'Email',
12
+ PhoneNumber = 'PhoneNumber',
13
+ Url = 'Url',
14
+ File = 'File',
15
+ FileRequired = 'FileRequired',
16
+ Custom = 'Custom'
17
+ }
18
+
19
+ const MessageDefault = {
20
+ Required: 'The {0} is required',
21
+ Regex: 'The {0} is not match {1}',
22
+ RequiredValue: 'the {0} is not equal {1}',
23
+ Array: 'The {0} is not valid',
24
+ Number: '{0} must be a number',
25
+ Email: 'The {0} is not valid',
26
+ PhoneNumber: 'The {0} is not valid',
27
+ Url: 'The {0} is not valid',
28
+ File: 'File size must be less than {1}',
29
+ FileRequired: 'The {0} is required',
30
+ Custom: 'Error Custom'
31
+ }
32
+
33
+ export interface RuleOptions {
34
+ File: {
35
+ maxSize: number
36
+ // toal size of files
37
+ all?: boolean
38
+ }
39
+ }
40
+
41
+ export const MapRuleOptions = <TK extends keyof RuleOptions>(key: TK, options?: RuleOptions[TK]) => options
42
+
43
+ export interface IRuleOption<TModel = any> {
44
+ rule: SingleRuleValidate
45
+ message?: string
46
+ Value?: RegExp | ((value: any, model: TModel) => boolean) | number | Array<any> | boolean
47
+ options?: any
48
+ }
49
+
50
+ interface IConfigValue<TModel = any> {
51
+ Rules: IRuleOption<TModel>[]
52
+ label?: string
53
+ }
54
+
55
+ export interface IModelError {
56
+ message: string
57
+ error: boolean
58
+ }
59
+
60
+ export type IFormValidatorConfig<TModel> = {
61
+ [key in keyof Extract<TModel, ModelBase>]?: IConfigValue<TModel>
62
+ }
63
+
64
+ export type PartialError<TModel> = {
65
+ [key in keyof TModel]?: IRuleOption<TModel>[]
66
+ }
67
+
68
+ export default class FormValidator<TModel> {
69
+ configs: IFormValidatorConfig<TModel>
70
+ constructor(configs: IFormValidatorConfig<TModel>) {
71
+ this.configs = configs
72
+ }
73
+
74
+ static initial = <T>(configs: IFormValidatorConfig<T>) => new FormValidator(configs)
75
+
76
+ AddRules = (key: keyof TModel, ...rule: IRuleOption<TModel>[]) => {
77
+ let fieldConfig = this.configs[key]
78
+ if (!fieldConfig) {
79
+ fieldConfig = { Rules: [] }
80
+ this.configs[key] = fieldConfig
81
+ }
82
+ fieldConfig.Rules.push(...rule)
83
+ return this
84
+ }
85
+
86
+ Required = (RuleOption: IRuleOption, value: any) => {
87
+ return value === null || value === undefined || value === ''
88
+ }
89
+ Regex = (RuleOption: IRuleOption, value: string, Regex: RegExp) => {
90
+ if (!value) return false
91
+ return !(RuleOption.Value as RegExp).test(value)
92
+ }
93
+ RequiredValue = (RuleOption: IRuleOption, value: any) => {
94
+ if (!value) return false
95
+ return value !== RuleOption.Value
96
+ }
97
+ Custom = (RuleOption: IRuleOption, value: any, model: any) => {
98
+ if (typeof RuleOption.Value === 'function') {
99
+ return RuleOption.Value(value, model)
100
+ }
101
+ return true
102
+ }
103
+ Array = (RuleOption: IRuleOption, value: any) => {
104
+ if (!value) return false
105
+ if (typeof RuleOption.Value === 'number') {
106
+ return Array.isArray(value) ? value.length <= RuleOption.Value : true
107
+ }
108
+ return true
109
+ }
110
+ Number = (RuleOption: IRuleOption, value: any) => {
111
+ if (!value) return false
112
+ return isNaN(Number(value))
113
+ }
114
+ Email = (RuleOption: IRuleOption, value: any) => {
115
+ if (!value) return false
116
+ const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
117
+ return !regex.test(value)
118
+ }
119
+ PhoneNumber = (RuleOption: IRuleOption, value: any) => {
120
+ if (!value) return false
121
+ const phoneRegex = /^(\+?\d{1,4}[\s-]?)?((\(\d{1,4}\))|\d{1,4})[\s-]?\d{1,4}[\s-]?\d{1,9}$/
122
+ return !phoneRegex.test((value ?? '').trim())
123
+ }
124
+ Url = (RuleOption: IRuleOption, value: any) => {
125
+ if (!value) return false
126
+ try {
127
+ new URL(value)
128
+ return false
129
+ } catch (err) {
130
+ return true
131
+ }
132
+ }
133
+ File = (RuleOption: IRuleOption, value: any) => {
134
+ if (!value) return false
135
+ const options = MapRuleOptions('File', RuleOption.options)
136
+ const files: File[] = Array.isArray(value) ? value : [value]
137
+ if (!options) return false
138
+ if (options.all === true) {
139
+ const size = files.reduce((a, b) => {
140
+ a += b.size
141
+ return a
142
+ }, 0)
143
+ return size > options.maxSize
144
+ } else {
145
+ return files.some((x) => x.size > options.maxSize)
146
+ }
147
+ }
148
+ FileRequired = (RuleOption: IRuleOption, value: any) => {
149
+ const files: File[] = Array.isArray(value) ? value : [value]
150
+ return !!files.filter((x) => !!x).some((x) => x.size <= 0)
151
+ }
152
+ RenderMessage = (RuleOption: IRuleOption, key: string, label?: string) => {
153
+ if (!RuleOption.message) {
154
+ RuleOption.message = MessageDefault[RuleOption.rule].replace('{0}', label ?? key)
155
+ switch (RuleOption.rule) {
156
+ case SingleRuleValidate.Regex: {
157
+ RuleOption.message = RuleOption.message.replace('{1}', (RuleOption.Value as RegExp).source)
158
+ break
159
+ }
160
+ case SingleRuleValidate.RequiredValue: {
161
+ RuleOption.message = RuleOption.message.replace('{1}', RuleOption.Value?.toString() ?? '')
162
+ break
163
+ }
164
+ case SingleRuleValidate.File: {
165
+ const options = MapRuleOptions('File', RuleOption.options)
166
+ if (options?.all === true) {
167
+ RuleOption.message = `Total file size must be less than ${formatFileSize((options?.maxSize ?? 0) / 1024)}`
168
+ } else {
169
+ RuleOption.message = RuleOption.message.replace('{1}', formatFileSize((options?.maxSize ?? 0) / 1024))
170
+ }
171
+ break
172
+ }
173
+ default:
174
+ break
175
+ }
176
+ }
177
+ RuleOption.message = RuleOption.message.replace('{0}', key)
178
+ return RuleOption
179
+ }
180
+ run = (model: TModel) => {
181
+ const keys = Object.keys(this.configs)
182
+ const data: PartialError<TModel> = {}
183
+ keys.forEach((key) => {
184
+ const config = this.configs[key]
185
+ if (!config) return
186
+ const tmp = this.Executed(config, (model as any)[key], model)
187
+ if (tmp.length < 1) return
188
+ ;(data as any)[key] = tmp.map((x) => this.RenderMessage(x, key, config.label))
189
+ })
190
+ return data
191
+ }
192
+ Executed = (Config: IConfigValue, valueField: any, model: any) => {
193
+ return Config.Rules.filter((Value) => {
194
+ const action = (this as any)[Value.rule.toString()]
195
+ return action && action(Value, valueField, model)
196
+ })
197
+ }
198
+ }
199
+
200
+ export const CreateFormValidator = <TModel>() => {
201
+ return new FormValidator<Partial<TModel>>({})
202
+ }
@@ -0,0 +1,44 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps*/
2
+ import { useEffect, useState } from 'react'
3
+
4
+ export const useCheckScrolled = () => {
5
+ const [isScrolled, setIsScrolled] = useState(false)
6
+
7
+ useEffect(() => {
8
+ const handleScroll = () => {
9
+ const scrolled = window.scrollY > 0
10
+ if (scrolled !== isScrolled) setIsScrolled(scrolled)
11
+ }
12
+
13
+ window.addEventListener('scroll', handleScroll)
14
+ return () => window.removeEventListener('scroll', handleScroll)
15
+ }, [isScrolled])
16
+
17
+ useEffect(() => {
18
+ const scrolled = window.scrollY > 0
19
+ if (scrolled !== isScrolled) setIsScrolled(scrolled)
20
+ }, [])
21
+
22
+ return { isScrolled }
23
+ }
24
+
25
+ export const useDebounce = <T>(value: T, delay: number): T => {
26
+ const [debouncedValue, setDebouncedValue] = useState<T>(value)
27
+
28
+ useEffect(() => {
29
+ if (delay <= 0) {
30
+ setDebouncedValue(value)
31
+ return
32
+ }
33
+
34
+ const handler = setTimeout(() => {
35
+ setDebouncedValue(value)
36
+ }, delay)
37
+
38
+ return () => {
39
+ clearTimeout(handler)
40
+ }
41
+ }, [value, delay])
42
+
43
+ return debouncedValue
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './api-context'
2
+ export * from './components'
3
+ export * from './form'
4
+ export * from './hooks'
5
+ export * from './redux'
6
+ export * from './table'
7
+ export * from './utils'