goobs-frontend 0.8.27 → 0.8.29

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 (25) hide show
  1. package/package.json +8 -18
  2. package/src/components/Content/Structure/projectboard/useProjectBoard.tsx +1 -1
  3. package/src/components/Form/ProjectBoard/index.tsx +23 -18
  4. package/src/components/MultiSelect/index.tsx +245 -0
  5. package/src/components/MultiSelect/multiselect.stories.tsx +129 -0
  6. package/src/components/ProjectBoard/board/desktop/index.tsx +348 -0
  7. package/src/components/ProjectBoard/board/index.tsx +43 -0
  8. package/src/components/ProjectBoard/board/mobile/index.tsx +364 -0
  9. package/src/components/ProjectBoard/board/tablet/index.tsx +355 -0
  10. package/src/components/ProjectBoard/{AddTask → forms/AddTask}/client.tsx +3 -3
  11. package/src/components/ProjectBoard/forms/ShowTask/client.tsx +1132 -0
  12. package/src/components/ProjectBoard/index.tsx +177 -264
  13. package/src/components/ProjectBoard/jotai/atom.ts +9 -0
  14. package/src/components/ProjectBoard/projectboard.stories.tsx +444 -262
  15. package/src/components/ProjectBoard/types/index.tsx +2 -6
  16. package/src/components/ProjectBoard/utils/useDragandDrop/columns.tsx +19 -13
  17. package/src/components/ProjectBoard/utils/useDragandDrop/tasks.tsx +53 -121
  18. package/src/components/SearchableDropdown/index.tsx +52 -20
  19. package/src/components/SearchableDropdown/searchabledropdown.stories.tsx +5 -0
  20. package/src/components/Searchbar/index.tsx +40 -15
  21. package/src/components/Toolbar/leftCenter/index.tsx +9 -3
  22. package/src/index.ts +9 -8
  23. package/src/components/ProjectBoard/Column/index.tsx +0 -595
  24. package/src/components/ProjectBoard/ManageTask/client.tsx +0 -440
  25. package/src/components/ProjectBoard/ShowTask/client.tsx +0 -434
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.8.27",
3
+ "version": "0.8.29",
4
4
  "type": "module",
5
5
  "description": "A comprehensive React-based UI library built on Material-UI, offering a wide range of customizable components including grids, typography, buttons, cards, forms, navigation, pricing tables, steppers, tooltips, accordions, and more. Designed for building responsive and consistent user interfaces with advanced features like form validation, theming, and code syntax highlighting.",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@
59
59
  "@typescript-eslint/eslint-plugin": "^8.21.0",
60
60
  "@typescript-eslint/parser": "^8.21.0",
61
61
  "chromatic": "^11.25.1",
62
- "eslint": "^9.18.0",
62
+ "eslint": "^9.19.0",
63
63
  "eslint-config-next": "^15.1.6",
64
64
  "eslint-config-prettier": "^10.0.1",
65
65
  "eslint-plugin-prettier": "^5.2.3",
@@ -92,26 +92,17 @@
92
92
  "node.js",
93
93
  "formData",
94
94
  "form",
95
- "form-validation",
96
- "form-submission",
97
95
  "button",
98
96
  "grid",
99
97
  "responsive",
100
- "flexgrid",
101
98
  "typography",
102
- "responsive-grid",
103
- "styled-component",
104
- "input-component",
105
- "input-validation",
106
- "button-validation",
107
- "form-validation",
99
+ "button",
108
100
  "searchbar",
109
101
  "dropdown",
110
- "multilinetextfield",
102
+ "complexeditor",
111
103
  "textfield",
112
104
  "phonenumber",
113
105
  "password",
114
- "email",
115
106
  "number",
116
107
  "card",
117
108
  "pricing-table",
@@ -120,20 +111,19 @@
120
111
  "tooltip",
121
112
  "accordion",
122
113
  "code-copy",
123
- "syntax-highlighting",
124
- "theming",
125
114
  "customizable-components",
126
115
  "react-components",
127
116
  "ui-library",
128
- "design-system",
129
117
  "front-end",
130
- "user-interface",
131
118
  "responsive-design",
132
119
  "typescript"
133
120
  ],
134
121
  "resolutions": {
135
122
  "string-width": "^4.2.3",
136
- "strip-ansi": "^6.0.1"
123
+ "strip-ansi": "^6.0.1",
124
+ "webpack": "^5.0.0",
125
+ "glob": "^9.0.0",
126
+ "rimraf": "^4.0.0"
137
127
  },
138
128
  "eslintConfig": {
139
129
  "extends": [
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
  import React from 'react'
3
- import ProjectBoard from '../../../ProjectBoard'
3
+ import ProjectBoard from '../../../ProjectBoard/'
4
4
  import { ProjectBoardProps } from '../../../ProjectBoard/types'
5
5
  import { columnconfig, cellconfig } from '../../../Grid'
6
6
 
@@ -1,14 +1,17 @@
1
- // src\components\FormProjectBoard\index.tsx
1
+ // src/components/FormProjectBoard/index.tsx
2
+
2
3
  'use client'
3
4
 
4
5
  import React from 'react'
5
6
  import { Box } from '@mui/material'
6
7
  import ContentSection from '../../Content'
8
+ // Import the ProjectBoardProps type from your types folder:
7
9
  import { ProjectBoardProps } from '../../ProjectBoard/types'
8
10
 
9
11
  /**
10
- * Props for FormProjectBoard.
11
- * It's similar to FormDataGrid but for a project board.
12
+ * Props for FormProjectBoard:
13
+ * - A simple container that shows a title, description,
14
+ * and then renders a ProjectBoard via ContentSection.
12
15
  */
13
16
  export interface FormProjectBoardProps {
14
17
  /** Title text displayed above the project board. */
@@ -96,19 +99,9 @@ function FormProjectBoard({
96
99
  gridconfig: { gridwidth: '100%' },
97
100
  },
98
101
  projectboard: {
99
- /**
100
- * We must pass ALL the props that the final ProjectBoard expects:
101
- * - columns
102
- * - tasks
103
- * - rawStatuses, rawSubStatuses, etc.
104
- * - variant
105
- * - boardType
106
- * - onUpdateTask
107
- * - and optionally "company" if used
108
- */
109
- variant: projectboard.variant, // <-- IMPORTANT
110
- boardType: projectboard.boardType, // <-- IMPORTANT
111
-
102
+ // Pass along all props that ProjectBoard needs:
103
+ variant: projectboard.variant,
104
+ boardType: projectboard.boardType,
112
105
  company: projectboard.company,
113
106
 
114
107
  columns: projectboard.columns,
@@ -123,9 +116,21 @@ function FormProjectBoard({
123
116
  rawEmployees: projectboard.rawEmployees,
124
117
  rawSeverityLevels: projectboard.rawSeverityLevels,
125
118
 
126
- onUpdateTask: projectboard.onUpdateTask, // <-- If you want ProjectBoard to update tasks
119
+ // Callbacks / props that may be in ProjectBoardProps:
120
+ onComment: projectboard.onComment,
121
+ onEdit: projectboard.onEdit,
122
+ onDelete: projectboard.onDelete,
123
+ onDuplicate: projectboard.onDuplicate,
124
+ onCloseTask: projectboard.onCloseTask,
125
+ onEditComment: projectboard.onEditComment,
126
+
127
+ // If controlling ShowTask from a parent:
128
+ showTaskOpen: projectboard.showTaskOpen,
129
+
130
+ // For restricting comment edits, etc.
131
+ currentUserName: projectboard.currentUserName,
127
132
 
128
- // If you want to position it in a certain row/column in your custom grid:
133
+ // Example column placement in your grid:
129
134
  columnconfig: {
130
135
  row: 1,
131
136
  column: 1,
@@ -0,0 +1,245 @@
1
+ import * as React from 'react'
2
+ import { Theme, useTheme, styled, SxProps, alpha } from '@mui/material/styles'
3
+ import Box from '@mui/material/Box'
4
+ import OutlinedInput from '@mui/material/OutlinedInput'
5
+ import InputLabel from '@mui/material/InputLabel'
6
+ import MenuItem from '@mui/material/MenuItem'
7
+ import FormControl, { FormControlProps } from '@mui/material/FormControl'
8
+ import Select, { SelectChangeEvent } from '@mui/material/Select'
9
+ import Chip from '@mui/material/Chip'
10
+
11
+ export interface MultiSelectChipProps
12
+ extends Omit<FormControlProps, 'onChange'> {
13
+ label?: React.ReactNode
14
+ options?: string[]
15
+ defaultSelected?: string[]
16
+ onChange?: (values: string[]) => void
17
+
18
+ backgroundcolor?: string
19
+ outlinecolor?: string
20
+ fontcolor?: string
21
+ inputfontcolor?: string
22
+ shrunkfontcolor?: string
23
+ unshrunkfontcolor?: string
24
+ placeholdercolor?: string
25
+
26
+ shrunklabelposition?: 'onNotch' | 'aboveNotch'
27
+ sx?: SxProps
28
+ }
29
+
30
+ const ITEM_HEIGHT = 40
31
+ const ITEM_PADDING_TOP = 8
32
+ const MenuProps = {
33
+ PaperProps: {
34
+ style: {
35
+ maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
36
+ width: 250,
37
+ },
38
+ },
39
+ }
40
+
41
+ function getStyles(
42
+ name: string,
43
+ selectedArray: readonly string[],
44
+ theme: Theme
45
+ ) {
46
+ return {
47
+ fontWeight: selectedArray.includes(name)
48
+ ? theme.typography.fontWeightMedium
49
+ : theme.typography.fontWeightRegular,
50
+ }
51
+ }
52
+
53
+ const StyledFormControl = styled(FormControl, {
54
+ shouldForwardProp: prop =>
55
+ ![
56
+ 'backgroundcolor',
57
+ 'outlinecolor',
58
+ 'fontcolor',
59
+ 'inputfontcolor',
60
+ 'shrunkfontcolor',
61
+ 'unshrunkfontcolor',
62
+ 'placeholdercolor',
63
+ 'shrunklabelposition',
64
+ ].includes(prop as string),
65
+ })<
66
+ Pick<
67
+ MultiSelectChipProps,
68
+ | 'backgroundcolor'
69
+ | 'outlinecolor'
70
+ | 'fontcolor'
71
+ | 'inputfontcolor'
72
+ | 'shrunkfontcolor'
73
+ | 'unshrunkfontcolor'
74
+ | 'placeholdercolor'
75
+ | 'shrunklabelposition'
76
+ > & { hasvalue: string }
77
+ >(
78
+ ({
79
+ backgroundcolor,
80
+ outlinecolor,
81
+ fontcolor,
82
+ inputfontcolor,
83
+ shrunkfontcolor,
84
+ unshrunkfontcolor,
85
+ placeholdercolor,
86
+ shrunklabelposition,
87
+ hasvalue,
88
+ }) => ({
89
+ '& .MuiOutlinedInput-root': {
90
+ minHeight: '40px',
91
+ backgroundColor: backgroundcolor || 'inherit',
92
+ color: fontcolor || 'inherit',
93
+ '& fieldset': {
94
+ borderColor:
95
+ outlinecolor || (hasvalue === 'true' ? 'black' : 'rgba(0,0,0,0.23)'),
96
+ },
97
+ '&:hover fieldset': {
98
+ borderColor:
99
+ outlinecolor || (hasvalue === 'true' ? 'black' : 'rgba(0,0,0,0.23)'),
100
+ },
101
+ '&.Mui-focused fieldset': {
102
+ borderColor:
103
+ outlinecolor || (hasvalue === 'true' ? 'black' : 'rgba(0,0,0,0.23)'),
104
+ },
105
+ '& input': {
106
+ color: inputfontcolor || fontcolor || 'inherit',
107
+ '&::placeholder': {
108
+ color: placeholdercolor || alpha('#000', 0.54),
109
+ },
110
+ },
111
+ '& .MuiSelect-icon': {
112
+ color: inputfontcolor || fontcolor || 'inherit',
113
+ },
114
+ },
115
+ '& .MuiInputLabel-root': {
116
+ color: unshrunkfontcolor || fontcolor || 'inherit',
117
+ '&.Mui-focused': {
118
+ color: shrunkfontcolor || fontcolor || 'inherit',
119
+ },
120
+ '&.MuiInputLabel-shrink': {
121
+ color: shrunkfontcolor || fontcolor || 'inherit',
122
+ ...(shrunklabelposition === 'aboveNotch' && {
123
+ transform: 'translate(0px, -17px) scale(0.75)',
124
+ }),
125
+ ...(shrunklabelposition === 'onNotch' && {
126
+ transform: 'translate(13px, -4px) scale(0.75)',
127
+ }),
128
+ },
129
+ },
130
+ })
131
+ )
132
+
133
+ export default function MultipleSelectChip(props: MultiSelectChipProps) {
134
+ const {
135
+ label = 'Chip',
136
+ options = [],
137
+ defaultSelected = [],
138
+ onChange,
139
+
140
+ backgroundcolor,
141
+ outlinecolor,
142
+ fontcolor,
143
+ inputfontcolor,
144
+ shrunkfontcolor,
145
+ unshrunkfontcolor,
146
+ placeholdercolor,
147
+ shrunklabelposition,
148
+
149
+ sx,
150
+ ...rest
151
+ } = props
152
+
153
+ const theme = useTheme()
154
+ const [selectedValues, setSelectedValues] =
155
+ React.useState<string[]>(defaultSelected)
156
+
157
+ const hasValue = React.useMemo(
158
+ () => (selectedValues.length > 0).toString(),
159
+ [selectedValues]
160
+ )
161
+
162
+ const handleSelectChange = (
163
+ event: SelectChangeEvent<typeof selectedValues>
164
+ ) => {
165
+ const { value } = event.target
166
+ const newValue = typeof value === 'string' ? value.split(',') : value
167
+ setSelectedValues(newValue)
168
+ if (onChange) {
169
+ onChange(newValue)
170
+ }
171
+ }
172
+
173
+ return (
174
+ <Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
175
+ <StyledFormControl
176
+ sx={{ width: 300, ...sx }}
177
+ variant="outlined"
178
+ hasvalue={hasValue}
179
+ backgroundcolor={backgroundcolor}
180
+ outlinecolor={outlinecolor}
181
+ fontcolor={fontcolor}
182
+ inputfontcolor={inputfontcolor}
183
+ shrunkfontcolor={shrunkfontcolor}
184
+ unshrunkfontcolor={unshrunkfontcolor}
185
+ placeholdercolor={placeholdercolor}
186
+ shrunklabelposition={shrunklabelposition}
187
+ {...rest}
188
+ >
189
+ <InputLabel id="multi-select-chip-label">{label}</InputLabel>
190
+ <Select
191
+ labelId="multi-select-chip-label"
192
+ id="multi-select-chip"
193
+ multiple
194
+ /**
195
+ * Remove any fixed height. Let the
196
+ * OutlinedInput's styles control minHeight.
197
+ */
198
+ value={selectedValues}
199
+ onChange={handleSelectChange}
200
+ input={
201
+ <OutlinedInput
202
+ label={label}
203
+ /**
204
+ * Give the input an initial minHeight (e.g. 55px),
205
+ * and allow it to wrap chips, thus expanding the height
206
+ */
207
+ sx={{
208
+ minHeight: 55,
209
+ display: 'flex',
210
+ flexWrap: 'wrap',
211
+ gap: 0.5,
212
+ alignItems: 'center',
213
+ }}
214
+ placeholder={placeholdercolor ? (label as string) : undefined}
215
+ />
216
+ }
217
+ renderValue={selected => (
218
+ <Box
219
+ sx={{
220
+ display: 'flex',
221
+ flexWrap: 'wrap',
222
+ gap: 0.5,
223
+ }}
224
+ >
225
+ {selected.map(val => (
226
+ <Chip key={val} label={val} />
227
+ ))}
228
+ </Box>
229
+ )}
230
+ MenuProps={MenuProps}
231
+ >
232
+ {options.map(name => (
233
+ <MenuItem
234
+ key={name}
235
+ value={name}
236
+ style={getStyles(name, selectedValues, theme)}
237
+ >
238
+ {name}
239
+ </MenuItem>
240
+ ))}
241
+ </Select>
242
+ </StyledFormControl>
243
+ </Box>
244
+ )
245
+ }
@@ -0,0 +1,129 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ // If using the official SB testing library + jest:
3
+ // import { within, userEvent } from '@storybook/testing-library';
4
+ // import { expect } from '@storybook/jest';
5
+ //
6
+ // If you're using the same approach from your example:
7
+ import { within, userEvent, expect } from '@storybook/test'
8
+
9
+ import MultipleSelectChip from './index'
10
+
11
+ const meta: Meta<typeof MultipleSelectChip> = {
12
+ title: 'Components/MultiSelectChip',
13
+ component: MultipleSelectChip,
14
+ parameters: {
15
+ a11y: { disable: false },
16
+ },
17
+ }
18
+ export default meta
19
+
20
+ type Story = StoryObj<typeof MultipleSelectChip>
21
+
22
+ // Example data array
23
+ const NAMES = [
24
+ 'Oliver Hansen',
25
+ 'Van Henry',
26
+ 'April Tucker',
27
+ 'Ralph Hubbard',
28
+ 'Omar Alexander',
29
+ 'Carlos Abbott',
30
+ 'Miriam Wagner',
31
+ 'Bradley Wilkerson',
32
+ 'Virginia Andrews',
33
+ 'Kelly Snyder',
34
+ ]
35
+
36
+ /**
37
+ * 1) Basic usage - No preselected items.
38
+ */
39
+ export const Basic: Story = {
40
+ name: 'Basic (Default)',
41
+ args: {
42
+ label: 'Select a Name',
43
+ options: NAMES,
44
+ defaultSelected: [],
45
+ },
46
+ play: async ({ canvasElement }) => {
47
+ const canvas = within(canvasElement)
48
+
49
+ // 1. Verify label is visible
50
+ expect(canvas.getByLabelText('Select a Name')).toBeInTheDocument()
51
+
52
+ // 2. Click the Select to open the dropdown
53
+ await userEvent.click(canvas.getByLabelText('Select a Name'))
54
+
55
+ // 3. Choose "Van Henry" from the list
56
+ await userEvent.click(canvas.getByText('Van Henry'))
57
+
58
+ // 4. Now "Van Henry" should appear as a chip
59
+ expect(canvas.getByText('Van Henry')).toBeInTheDocument()
60
+ },
61
+ }
62
+
63
+ /**
64
+ * 2) PreSelected
65
+ */
66
+ export const PreSelected: Story = {
67
+ args: {
68
+ label: 'PreSelected Names',
69
+ options: NAMES,
70
+ defaultSelected: ['April Tucker', 'Omar Alexander'],
71
+ },
72
+ play: ({ canvasElement }) => {
73
+ const canvas = within(canvasElement)
74
+ // Both pre-selected chips should be shown
75
+ expect(canvas.getByText('April Tucker')).toBeInTheDocument()
76
+ expect(canvas.getByText('Omar Alexander')).toBeInTheDocument()
77
+ },
78
+ }
79
+
80
+ /**
81
+ * 3) Multiple Selections
82
+ */
83
+ export const MultipleSelections: Story = {
84
+ args: {
85
+ label: 'Pick Multiple',
86
+ options: NAMES,
87
+ defaultSelected: [],
88
+ },
89
+ play: async ({ canvasElement }) => {
90
+ const canvas = within(canvasElement)
91
+
92
+ // Open the menu
93
+ await userEvent.click(canvas.getByLabelText('Pick Multiple'))
94
+ // Select "Van Henry"
95
+ await userEvent.click(canvas.getByText('Van Henry'))
96
+ expect(canvas.getByText('Van Henry')).toBeInTheDocument()
97
+
98
+ // Menu closes after selection, so open again
99
+ await userEvent.click(canvas.getByLabelText('Pick Multiple'))
100
+ await userEvent.click(canvas.getByText('April Tucker'))
101
+ expect(canvas.getByText('April Tucker')).toBeInTheDocument()
102
+ },
103
+ }
104
+
105
+ /**
106
+ * 4) Custom Styles - Example usage of props like backgroundcolor, outlinecolor, etc.
107
+ */
108
+ export const CustomStyles: Story = {
109
+ args: {
110
+ label: 'Custom Colors',
111
+ options: NAMES,
112
+ defaultSelected: ['Kelly Snyder'],
113
+ backgroundcolor: '#f3e5f5', // Light purple
114
+ outlinecolor: '#6a1b9a', // Dark purple
115
+ fontcolor: '#283593', // Indigo for text
116
+ },
117
+ play: async ({ canvasElement }) => {
118
+ const canvas = within(canvasElement)
119
+
120
+ // Confirm that Kelly Snyder is initially selected
121
+ expect(canvas.getByText('Kelly Snyder')).toBeInTheDocument()
122
+
123
+ // Let's open the menu and deselect
124
+ await userEvent.click(canvas.getByLabelText('Custom Colors'))
125
+ await userEvent.click(canvas.getByText('Kelly Snyder'))
126
+
127
+ expect(canvas.queryByText('Kelly Snyder')).not.toBeInTheDocument()
128
+ },
129
+ }