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,138 @@
1
+ import React, { FC, useCallback, useEffect, useRef } from 'react'
2
+ import { TextField, InputAdornment, IconButton, Fade, Tooltip, styled, Divider, Box } from '@mui/material'
3
+ import SearchIcon from '@mui/icons-material/Search'
4
+ import CancelIcon from '@mui/icons-material/Cancel'
5
+
6
+ interface IProps {
7
+ value?: string
8
+ onChange?: (value: string) => void
9
+ placeholder?: string
10
+ disabledTimer?: boolean
11
+ }
12
+
13
+ const InputDebounceTimer: FC<IProps> = (props) => {
14
+ const refInput = useRef<HTMLInputElement>(null)
15
+ const handleClear = () => {
16
+ props.onChange && props.onChange('')
17
+ if (refInput.current) refInput.current.value = ''
18
+ }
19
+
20
+ const timer = useRef({ timeout: 700, store: 0 })
21
+ const handleChangeCallback = useCallback(
22
+ (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
23
+ const value = e.target.value
24
+ clearTimeout(timer.current.store)
25
+ if (props?.disabledTimer === true) return
26
+ timer.current.store = setTimeout(() => {
27
+ props.onChange && props.onChange(value)
28
+ }, timer.current.timeout) as any
29
+ },
30
+ [props]
31
+ )
32
+
33
+ const handleKeyDown = useCallback(
34
+ (event: React.KeyboardEvent<HTMLInputElement>) => {
35
+ if (event.key === 'Enter') {
36
+ clearTimeout(timer.current.store)
37
+ const value = refInput.current?.value ?? ''
38
+ props.onChange && props.onChange(value)
39
+ }
40
+ },
41
+ [props]
42
+ )
43
+
44
+ const handleClickSearch = useCallback(() => {
45
+ clearTimeout(timer.current.store)
46
+ const value = refInput.current?.value ?? ''
47
+ props.onChange && props.onChange(value)
48
+ }, [props])
49
+
50
+ useEffect(() => {
51
+ if (refInput.current) refInput.current.value = props.value ?? ''
52
+ return () => {}
53
+ }, [props.value])
54
+
55
+ return (
56
+ <WrapTextField className='theme-white'>
57
+ <TextField
58
+ fullWidth
59
+ variant='outlined'
60
+ placeholder={props.placeholder}
61
+ size='small'
62
+ defaultValue={props.value}
63
+ onChange={handleChangeCallback}
64
+ inputRef={refInput}
65
+ onKeyDown={handleKeyDown}
66
+ InputProps={{
67
+ startAdornment: (
68
+ <InputAdornment position='start'>
69
+ <IconButton type='submit' onClick={handleClickSearch}>
70
+ <SearchIcon />
71
+ </IconButton>
72
+ <Divider flexItem orientation='vertical' sx={{ height: '24px', transform: 'translateY(-50%)', mx: '3px' }} />
73
+ </InputAdornment>
74
+ ),
75
+ endAdornment: (
76
+ <InputAdornment position='end'>
77
+ <Fade in={!!props.value}>
78
+ <Tooltip title='Clear'>
79
+ <IconButton onClick={handleClear}>
80
+ <CancelIcon fontSize='small' />
81
+ </IconButton>
82
+ </Tooltip>
83
+ </Fade>
84
+ </InputAdornment>
85
+ )
86
+ }}
87
+ />
88
+ </WrapTextField>
89
+ )
90
+ }
91
+
92
+ export default InputDebounceTimer
93
+
94
+ const WrapTextField = styled(Box)<{ className?: string }>(({ className }) => {
95
+ const isWhite = className?.includes('theme-white')
96
+ return {
97
+ flex: 1,
98
+ '--input-search-text-color': isWhite ? '#000' : '#ffffff',
99
+ '--input-search-label-color': isWhite ? '#404040' : '#b0b0b0',
100
+ '--input-search-bg-color': isWhite ? '#f6f6f6' : 'rgba(255, 255, 255, 0.1)',
101
+ '--color-primary': '#42a5f5',
102
+
103
+ '& .MuiOutlinedInput-root': {
104
+ color: 'var(--input-search-text-color)',
105
+ backgroundColor: 'var(--input-search-bg-color)',
106
+ borderRadius: 8,
107
+ '& fieldset': {
108
+ borderColor: 'transparent'
109
+ },
110
+ '&:hover fieldset': {
111
+ borderColor: 'rgba(66, 165, 245, 0.7)' // $color-primary 70%
112
+ },
113
+ '&.Mui-focused fieldset': {
114
+ borderColor: 'var(--color-primary)'
115
+ }
116
+ },
117
+
118
+ '& .MuiInputBase-input': {
119
+ color: 'var(--input-search-text-color)'
120
+ },
121
+
122
+ '& .MuiInputLabel-root': {
123
+ color: 'var(--input-search-label-color)'
124
+ },
125
+
126
+ '& .MuiInputLabel-root.Mui-focused': {
127
+ color: '#90caf9'
128
+ },
129
+
130
+ '& .MuiInputAdornment-root': {
131
+ color: 'var(--input-search-text-color)'
132
+ },
133
+
134
+ '& .MuiIconButton-root': {
135
+ color: 'var(--input-search-text-color)'
136
+ }
137
+ }
138
+ })
@@ -0,0 +1,35 @@
1
+ import React, { FC } from 'react'
2
+ import { Button, ButtonProps, CircularProgress, Fade, styled } from '@mui/material'
3
+
4
+ export interface LoadingButtonProps extends ButtonProps {
5
+ loading?: boolean
6
+ }
7
+
8
+ export const LoadingButton: FC<LoadingButtonProps> = ({ loading, children, ...props }) => {
9
+ return (
10
+ <ButtonCustom {...props} disabled={props.disabled || loading}>
11
+ <Fade in={!loading}>
12
+ <span>{children}</span>
13
+ </Fade>
14
+ <Fade in={loading}>
15
+ <div className='LoadingButton-progress'>
16
+ <CircularProgress size={24} />
17
+ </div>
18
+ </Fade>
19
+ </ButtonCustom>
20
+ )
21
+ }
22
+
23
+ const ButtonCustom = styled(Button)({
24
+ position: 'relative',
25
+ [`.LoadingButton-progress`]: {
26
+ position: 'absolute',
27
+ top: 0,
28
+ left: 0,
29
+ width: '100%',
30
+ height: '100%',
31
+ display: 'flex',
32
+ alignItems: 'center',
33
+ justifyContent: 'center'
34
+ }
35
+ })
@@ -0,0 +1,30 @@
1
+ import React, { FC } from 'react'
2
+
3
+ const TextEditorPreview: FC<{ children: string }> = (props) => {
4
+ const contentRef = React.useRef<HTMLDivElement | null>(null)
5
+
6
+ React.useEffect(() => {
7
+ // Lấy phần tử template
8
+ const template = document.createElement('template')
9
+ template.innerHTML = `
10
+ <link rel="stylesheet" href="css/quill.snow.css">
11
+ <div class='ql-editor'>${props.children}</div>
12
+ `
13
+
14
+ // Tạo shadow DOM và chèn nội dung từ template
15
+ if (contentRef.current) {
16
+ const shadowRoot = contentRef.current.shadowRoot || contentRef.current.attachShadow({ mode: 'open' })
17
+
18
+ shadowRoot.innerHTML = '' // Reset nội dung cũ
19
+ shadowRoot.appendChild(template.content.cloneNode(true))
20
+ }
21
+ }, [props.children])
22
+
23
+ return (
24
+ <>
25
+ <div ref={contentRef}></div>
26
+ </>
27
+ )
28
+ }
29
+
30
+ export default TextEditorPreview
@@ -0,0 +1,125 @@
1
+ // /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import React, { Component } from 'react'
3
+ import { Box, styled, SxProps, Theme } from '@mui/material'
4
+ /**
5
+ * yarn add react-quill@2.0.0
6
+ */
7
+ import ReactQuill from 'react-quill'
8
+ import 'react-quill/dist/quill.snow.css'
9
+
10
+ export const textEditorClasses = {
11
+ root: 'DinoTextEditor-root',
12
+ fullscreen: 'DinoTextEditor-fullscreen'
13
+ }
14
+
15
+ const EditorToolbar: any = {}
16
+
17
+ EditorToolbar.formats = [
18
+ 'header',
19
+ 'bold',
20
+ 'italic',
21
+ 'underline',
22
+ 'strike',
23
+ 'blockquote',
24
+ 'list',
25
+ 'bullet',
26
+ 'indent',
27
+ 'link',
28
+ 'image',
29
+ 'align',
30
+ 'color',
31
+ 'background'
32
+ ]
33
+
34
+ EditorToolbar.modules = {
35
+ toolbar: [
36
+ [{ header: [1, 2, 3, 4, 5, 6, false] }],
37
+ ['bold', 'italic', 'underline', 'strike', 'blockquote'],
38
+ [{ align: '' }, { align: 'center' }, { align: 'right' }, { align: 'justify' }],
39
+ [{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }],
40
+ [{ color: [] }, { background: [] }],
41
+ ['link', 'image'],
42
+ ['clean']
43
+ ]
44
+ }
45
+
46
+ interface IProps {
47
+ id?: string
48
+ name: string
49
+ sx?: SxProps<Theme>
50
+ readOnly?: boolean
51
+ defautValue?: string
52
+ error?: boolean
53
+ onBlur?: () => void
54
+ }
55
+
56
+ interface IState {
57
+ value: string
58
+ }
59
+
60
+ class TextEditor extends Component<IProps, IState> {
61
+ constructor(props: IProps) {
62
+ super(props)
63
+ this.state = { value: props.defautValue ?? '' }
64
+ }
65
+ handleChange = (value: string) => {
66
+ let str = value
67
+ if (value === '<p><br></p>') str = ''
68
+ this.setState({ value: str })
69
+ }
70
+
71
+ id = 0
72
+ render() {
73
+ const classes = [textEditorClasses.root]
74
+ if (this.props.error) classes.push('error')
75
+ return (
76
+ <Wrapper id={this.props.id} sx={this.props.sx} className={classes.join(' ')}>
77
+ {!this.props.readOnly && <input key={++this.id} name={this.props.name} defaultValue={this.state.value} hidden />}
78
+ <ReactQuill
79
+ readOnly={this.props.readOnly}
80
+ placeholder='Enter the message'
81
+ value={this.state.value}
82
+ modules={EditorToolbar.modules}
83
+ formats={EditorToolbar.formats}
84
+ onChange={this.handleChange}
85
+ onBlur={this.props.onBlur}
86
+ />
87
+ </Wrapper>
88
+ )
89
+ }
90
+ }
91
+
92
+ export default TextEditor
93
+
94
+ export { default as TextEditorPreview } from './text-editor.preview'
95
+
96
+ const borderColor = '#d32f2f'
97
+
98
+ const Wrapper = styled(Box)({
99
+ margin: 0,
100
+ minHeight: '450px',
101
+ display: 'flex',
102
+ '& .quill': { flex: 1 },
103
+ '& .ql-container': {
104
+ height: 'calc(100% - 42px)',
105
+ borderBottomLeftRadius: '6px',
106
+ borderBottomRightRadius: '6px'
107
+ },
108
+ '& .ql-toolbar': {
109
+ borderTopLeftRadius: '6px',
110
+ borderTopRightRadius: '6px'
111
+ },
112
+ '&.error .ql-toolbar': {
113
+ borderTopColor: borderColor,
114
+ borderLeftColor: borderColor,
115
+ borderRightColor: borderColor
116
+ },
117
+ '&.error .ql-container': {
118
+ borderBottomColor: borderColor,
119
+ borderLeftColor: borderColor,
120
+ borderRightColor: borderColor
121
+ },
122
+ '&.error .ql-editor.ql-blank::before': {
123
+ color: borderColor
124
+ }
125
+ })
@@ -0,0 +1,55 @@
1
+ ## 🧩 Cấu trúc tổng quát
2
+
3
+ `DinoForm` gồm hai phần chính: các thành phần nền tảng (Base) và các thành phần nhập liệu (Inputs).
4
+
5
+ ### 1. Base (Nền tảng)
6
+
7
+ | Tên | Kiểu | Mô tả |
8
+ | ---------------------- | ------------------------- | --------------------------------------------------------------- |
9
+ | `ModalWrap` | `React Component` | Lớp bộc khi dùng chung với GlobalModal, Form từ Table. |
10
+ | `BottomBarWrap` | `React Component` | Component thay thế cho BottomBar của FormBase. |
11
+ | `FormValidator` | `class` | Lớp xác thực, cung cấp phương thức tạo schema kiểm tra dữ liệu. |
12
+ | `validator` | `object` | Tạo nhanh FormValidator |
13
+ | `createFormBase` | `React Component Creator` | Tạo form cơ bản |
14
+ | `createFormComfirm` | `React Component Creator` | Tạo hộp thoại xác nhận khi gửi hoặc huỷ biểu mẫu. |
15
+ | `createFormGridLayout` | `React Component Creator` | Tạo layout dạng lưới để tổ chức các input trong form. |
16
+
17
+ ---
18
+
19
+ ### 2. Inputs (Thành phần nhập liệu)
20
+
21
+ | Tên | Kiểu | Mô tả |
22
+ | ------------------------ | ------------------------- | ------------------------------------------------------------ |
23
+ | `createInput` | `React Component Creator` | Input text cơ bản, có thể dùng cho tên, tiêu đề, v.v. |
24
+ | `createInputFile` | `React Component Creator` | Input cho phép người dùng chọn và tải lên file. |
25
+ | `createDatePicker` | `React Component Creator` | Chọn ngày bằng lịch, tương thích với dayjs. |
26
+ | `createAutocompleteChip` | `React Component Creator` | Tạo autocomplete cho phép chọn nhiều giá trị dưới dạng chip. |
27
+ | `createSelectSimple` | `React Component Creator` | Select tĩnh với danh sách tùy chọn truyền trực tiếp. |
28
+ | `createSelectWithApi` | `React Component Creator` | Select động, lấy dữ liệu từ API. |
29
+ | `createTextEditor` | `React Component Creator` | Trình soạn thảo văn bản phong phú (rich text editor). |
30
+
31
+ ---
32
+
33
+ ## 🛠️ Cách sử dụng
34
+
35
+ ### Tạo form cơ bản `createFormBase`
36
+
37
+ ### Tạo form dạng lưới `createFormGridLayout`
38
+
39
+ Tạo layout dạng lưới để tổ chức các input trong form.
40
+
41
+ ```ts
42
+ export interface IDataModel {
43
+ Id: string
44
+ TextValue: string
45
+ DateTimeValue: string
46
+ FileValue: string
47
+ }
48
+
49
+ export const FormInstance = DinoForm.createFormGridLayout<IDataModel>({
50
+ configs: [{ key: 'Id', label: 'Id', placeholder: 'Id' }],
51
+ validate: DinoForm.validator({
52
+ Id: { Rules: [{ rule: SingleRuleValidate.Required }] }
53
+ })
54
+ })
55
+ ```
@@ -0,0 +1,199 @@
1
+ import React, { Component } from 'react'
2
+ import { Autocomplete, Chip, IconButton, styled, TextField, TextFieldProps, Tooltip, Typography } from '@mui/material'
3
+ import RemoveIcon from '@mui/icons-material/Remove'
4
+ import { IFormInputBase } from './types'
5
+ import { getErrorMessage } from './helper'
6
+
7
+ interface IOptions {
8
+ optionItems?: string[]
9
+ limitTags?: number
10
+ separator?: string
11
+ saveLocalStorageByKey?: string
12
+ }
13
+
14
+ interface IProps<T> extends IFormInputBase<T, IOptions> {}
15
+
16
+ interface IState {
17
+ value: string[]
18
+ options: string[]
19
+ }
20
+
21
+ export default function CreateAutocompleteChip<T>(options?: IOptions) {
22
+ const separator = options?.separator || '|'
23
+ const limitTags = options?.limitTags || 2
24
+ const storeItems = options?.saveLocalStorageByKey ? AutocompleteChipStore.initial(options.saveLocalStorageByKey) : undefined
25
+
26
+ return class AutocompleteChip extends Component<IProps<T>, IState> {
27
+ constructor(props: IProps<T>) {
28
+ super(props)
29
+ this.state = {
30
+ value: this.getDefaultValue(),
31
+ options: this.getSelectOptions()
32
+ }
33
+ }
34
+
35
+ refInput: HTMLInputElement | null = null
36
+ render() {
37
+ const { name } = this.props
38
+ return (
39
+ <>
40
+ <Autocomplete
41
+ multiple
42
+ freeSolo
43
+ limitTags={limitTags}
44
+ value={this.state.value}
45
+ onChange={this.handleAddOption}
46
+ options={this.state.options}
47
+ renderOption={(props, option) => (
48
+ <ItemOption {...props} key={option}>
49
+ <Typography variant='subtitle1' sx={{ flex: 1 }}>
50
+ {option}
51
+ </Typography>
52
+ {this.isBtnRemoveOption(option) && (
53
+ <Tooltip title='Remove option' arrow placement='left'>
54
+ <IconButton size='small' onClick={(e) => this.handleRemoveItem(e, option)}>
55
+ <RemoveIcon fontSize='small' />
56
+ </IconButton>
57
+ </Tooltip>
58
+ )}
59
+ </ItemOption>
60
+ )}
61
+ renderTags={(tagValue, getTagProps) => {
62
+ return tagValue.map((option, index) => <Chip label={option} {...getTagProps({ index })} key={option.toString()} />)
63
+ }}
64
+ renderInput={(params) => <TextField {...params} variant='outlined' {...this.getTextFieldProps()} />}
65
+ />
66
+ <input
67
+ ref={(ref) => {
68
+ this.refInput = ref
69
+ }}
70
+ hidden
71
+ type='text'
72
+ name={name?.toString()}
73
+ defaultValue={this.state.value.join(separator)}
74
+ />
75
+ </>
76
+ )
77
+ }
78
+
79
+ handleRemoveItem = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>, value: string) => {
80
+ e.preventDefault()
81
+ e.stopPropagation()
82
+ console.log(value)
83
+ const list = storeItems?.delete(value) ?? []
84
+ this.setState({ options: list })
85
+ }
86
+
87
+ handleAddOption = (_: React.SyntheticEvent, newValue: string[]) => {
88
+ if (options?.saveLocalStorageByKey) {
89
+ const options = storeItems?.setItems(this.state.options, newValue) ?? this.state.options
90
+ this.setState({ value: newValue, options })
91
+ } else {
92
+ this.setState({ value: newValue })
93
+ }
94
+ if (this.refInput) {
95
+ this.refInput.value = newValue.join(separator)
96
+ }
97
+ this.props.name && this.props.onBlur && this.props.onBlur(this.props.name)
98
+ }
99
+
100
+ isBtnRemoveOption = (value: string) => {
101
+ const isSelectedOption = this.state.value.findIndex((x) => x === value) < 0
102
+ const selectOptions = options?.optionItems ?? this.props.slots?.optionItems ?? []
103
+ const isDefault = selectOptions.findIndex((x) => x === value) < 0
104
+ return isDefault && isSelectedOption
105
+ }
106
+
107
+ getSelectOptions = () => {
108
+ let items = options?.optionItems ?? []
109
+ if (options?.saveLocalStorageByKey) {
110
+ items = storeItems?.getItemsAndMerge(items) ?? []
111
+ }
112
+ return items
113
+ }
114
+
115
+ getTextFieldProps = (): TextFieldProps => {
116
+ const { name, label, placeholder, onBlur, messageErrors } = this.props
117
+ const eMessage = getErrorMessage(messageErrors, name)
118
+ return {
119
+ label,
120
+ placeholder: placeholder || 'Add new',
121
+ onBlur: () => {
122
+ if (!name) return
123
+ onBlur && onBlur(name)
124
+ },
125
+ error: eMessage.error,
126
+ helperText: eMessage.message
127
+ }
128
+ }
129
+
130
+ getDefaultValue = (): string[] => {
131
+ const { name, data } = this.props
132
+ const value = name ? data?.[name]?.toString() : ''
133
+ return value?.split(separator).filter((x) => !!x) ?? []
134
+ }
135
+ }
136
+ }
137
+
138
+ const ItemOption = styled('li')({
139
+ display: 'flex',
140
+ alignItems: 'center'
141
+ })
142
+
143
+ type TChipStoreRoot = { [key: string]: string[] }
144
+
145
+ class AutocompleteChipStore {
146
+ private storeKeyRoot = 'autocomplete_chip_store'
147
+ private storeKey: string
148
+ constructor(key: string, defaultValue?: string[]) {
149
+ this.storeKey = key
150
+ if (defaultValue) {
151
+ this.setChild(defaultValue)
152
+ }
153
+ }
154
+
155
+ static initial = (key: string, defaultValue?: string[]) => new AutocompleteChipStore(key, defaultValue)
156
+
157
+ private getRoot = (): TChipStoreRoot => {
158
+ try {
159
+ const res = window.localStorage.getItem(this.storeKeyRoot)
160
+ return JSON.parse(res ?? '{}')
161
+ } catch (error) {
162
+ return {}
163
+ }
164
+ }
165
+
166
+ private getChild = (): string[] => {
167
+ return this.getRoot()[this.storeKey]
168
+ }
169
+
170
+ private setRoot = (value: TChipStoreRoot) => {
171
+ window.localStorage.setItem(this.storeKeyRoot, JSON.stringify(value))
172
+ }
173
+
174
+ private setChild = (value: string[]) => {
175
+ const obj = this.getRoot()
176
+ obj[this.storeKey] = value
177
+ this.setRoot(obj)
178
+ }
179
+
180
+ getItemsAndMerge = (value: string[]): string[] => {
181
+ try {
182
+ return Array.from(new Set([...this.getChild(), ...value])).sort()
183
+ } catch (error) {
184
+ return value
185
+ }
186
+ }
187
+
188
+ setItems = (options: string[], value: string[]): string[] => {
189
+ const list = Array.from(new Set([...options, ...value])).sort()
190
+ this.setChild(list)
191
+ return list
192
+ }
193
+
194
+ delete = (value: string) => {
195
+ const list = this.getChild().filter((x) => x !== value)
196
+ this.setChild(list)
197
+ return list
198
+ }
199
+ }