@object-ui/plugin-kanban 3.3.0 → 3.3.2

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 (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +24 -0
  3. package/dist/{KanbanEnhanced-TdUe0kQH.js → KanbanEnhanced-Do9ZB1Mh.js} +35 -32
  4. package/dist/{KanbanImpl-BtlPa7GE.js → KanbanImpl-BdocXM5T.js} +1 -1
  5. package/dist/{chevron-down-B6UH8BbF.js → chevron-down-C0JUlGjk.js} +1 -1
  6. package/dist/index.js +3 -3
  7. package/dist/index.umd.cjs +2 -2
  8. package/dist/{plus-BTqoaaEC.js → plus-CHsXVJSY.js} +1 -1
  9. package/package.json +34 -11
  10. package/.turbo/turbo-build.log +0 -32
  11. package/src/CardTemplates.tsx +0 -123
  12. package/src/InlineQuickAdd.tsx +0 -189
  13. package/src/KanbanEnhanced.tsx +0 -525
  14. package/src/KanbanImpl.tsx +0 -597
  15. package/src/ObjectKanban.EdgeCases.stories.tsx +0 -168
  16. package/src/ObjectKanban.msw.test.tsx +0 -95
  17. package/src/ObjectKanban.stories.tsx +0 -152
  18. package/src/ObjectKanban.tsx +0 -276
  19. package/src/__tests__/KanbanEnhanced.test.tsx +0 -260
  20. package/src/__tests__/KanbanGrouping.test.tsx +0 -164
  21. package/src/__tests__/KanbanSwimlanes.test.tsx +0 -194
  22. package/src/__tests__/ObjectKanbanTitle.test.tsx +0 -93
  23. package/src/__tests__/SwimlanePersistence.test.tsx +0 -159
  24. package/src/__tests__/accessibility.test.tsx +0 -296
  25. package/src/__tests__/dnd-undo-integration.test.tsx +0 -525
  26. package/src/__tests__/performance-benchmark.test.tsx +0 -306
  27. package/src/__tests__/phase13-features.test.tsx +0 -387
  28. package/src/__tests__/view-states.test.tsx +0 -403
  29. package/src/index.test.ts +0 -112
  30. package/src/index.tsx +0 -327
  31. package/src/registration.test.tsx +0 -26
  32. package/src/types.ts +0 -185
  33. package/src/useColumnWidths.ts +0 -125
  34. package/src/useCrossSwimlaneMove.ts +0 -116
  35. package/src/useQuickAddReorder.ts +0 -107
  36. package/tsconfig.json +0 -19
  37. package/vite.config.ts +0 -62
  38. package/vitest.config.ts +0 -12
  39. package/vitest.setup.ts +0 -1
@@ -1,123 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import * as React from "react"
10
- import { Button } from "@object-ui/components"
11
- import { ChevronDown, FileText, Plus } from "lucide-react"
12
- import type { CardTemplate } from "./types"
13
-
14
- const cn = (...classes: (string | undefined)[]) => classes.filter(Boolean).join(' ')
15
-
16
- export interface CardTemplatesProps {
17
- /** Available card templates */
18
- templates: CardTemplate[]
19
- /** Called when a template (or "Custom") is selected */
20
- onSelect: (template: CardTemplate | null) => void
21
- /** Column the template applies to */
22
- columnId: string
23
- }
24
-
25
- /**
26
- * CardTemplates renders a dropdown button that lets users pick a
27
- * predefined card template or start with a blank ("Custom") card.
28
- * The selected template pre-fills the quick-add form values.
29
- */
30
- export const CardTemplates: React.FC<CardTemplatesProps> = ({
31
- templates,
32
- onSelect,
33
- columnId,
34
- }) => {
35
- const [open, setOpen] = React.useState(false)
36
- const containerRef = React.useRef<HTMLDivElement>(null)
37
-
38
- // Close dropdown on outside click
39
- React.useEffect(() => {
40
- if (!open) return
41
- const handler = (e: MouseEvent) => {
42
- if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
43
- setOpen(false)
44
- }
45
- }
46
- document.addEventListener("mousedown", handler)
47
- return () => document.removeEventListener("mousedown", handler)
48
- }, [open])
49
-
50
- // Close on Escape
51
- const handleKeyDown = React.useCallback(
52
- (e: React.KeyboardEvent) => {
53
- if (e.key === "Escape") {
54
- setOpen(false)
55
- }
56
- },
57
- [],
58
- )
59
-
60
- return (
61
- <div ref={containerRef} className="relative" onKeyDown={handleKeyDown}>
62
- <Button
63
- variant="ghost"
64
- size="sm"
65
- className="w-full mt-2 text-muted-foreground hover:text-foreground gap-1"
66
- onClick={() => setOpen(prev => !prev)}
67
- aria-haspopup="listbox"
68
- aria-expanded={open}
69
- aria-label={`Add card to ${columnId}`}
70
- >
71
- <Plus className="h-4 w-4" />
72
- Add Card
73
- <ChevronDown className={cn("h-3 w-3 transition-transform", open ? "rotate-180" : "")} />
74
- </Button>
75
-
76
- {open && (
77
- <div
78
- className="absolute z-50 mt-1 w-full rounded-md border border-border bg-popover p-1 shadow-md"
79
- role="listbox"
80
- aria-label="Card templates"
81
- >
82
- {/* Custom blank card option */}
83
- <button
84
- type="button"
85
- className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
86
- role="option"
87
- aria-selected={false}
88
- onClick={() => {
89
- onSelect(null)
90
- setOpen(false)
91
- }}
92
- >
93
- <Plus className="h-4 w-4 text-muted-foreground" />
94
- <span className="font-mono text-xs">Custom</span>
95
- </button>
96
-
97
- {templates.length > 0 && (
98
- <div className="my-1 h-px bg-border" role="separator" />
99
- )}
100
-
101
- {templates.map(template => (
102
- <button
103
- key={template.id}
104
- type="button"
105
- className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
106
- role="option"
107
- aria-selected={false}
108
- onClick={() => {
109
- onSelect(template)
110
- setOpen(false)
111
- }}
112
- >
113
- <FileText className="h-4 w-4 text-muted-foreground" />
114
- <span className="font-mono text-xs truncate">{template.name}</span>
115
- </button>
116
- ))}
117
- </div>
118
- )}
119
- </div>
120
- )
121
- }
122
-
123
- export default CardTemplates
@@ -1,189 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import * as React from "react"
10
- import { Button, Input } from "@object-ui/components"
11
- import { Check, X } from "lucide-react"
12
- import type { InlineFieldDefinition } from "./types"
13
-
14
- const cn = (...classes: (string | undefined)[]) => classes.filter(Boolean).join(' ')
15
-
16
- export interface InlineQuickAddProps {
17
- /** Column where the card will be added */
18
- columnId: string
19
- /** Field definitions for inline editing */
20
- fields: InlineFieldDefinition[]
21
- /** Called with field values when the form is submitted */
22
- onSubmit: (columnId: string, values: Record<string, any>) => void
23
- /** Called when the form is cancelled */
24
- onCancel: () => void
25
- /** Pre-filled values (e.g. from a card template) */
26
- defaultValues?: Record<string, any>
27
- }
28
-
29
- /**
30
- * InlineQuickAdd renders form fields directly inside the kanban column
31
- * without opening a dialog or modal. Submit with Enter or the Save button;
32
- * cancel with Escape or the Cancel button.
33
- */
34
- export const InlineQuickAdd: React.FC<InlineQuickAddProps> = ({
35
- columnId,
36
- fields,
37
- onSubmit,
38
- onCancel,
39
- defaultValues,
40
- }) => {
41
- const [values, setValues] = React.useState<Record<string, any>>(() => {
42
- const initial: Record<string, any> = {}
43
- for (const field of fields) {
44
- initial[field.name] = defaultValues?.[field.name] ?? field.defaultValue ?? ""
45
- }
46
- return initial
47
- })
48
-
49
- const firstInputRef = React.useRef<HTMLInputElement | HTMLSelectElement>(null)
50
-
51
- React.useEffect(() => {
52
- // Auto-focus first field on mount
53
- const timer = setTimeout(() => firstInputRef.current?.focus(), 0)
54
- return () => clearTimeout(timer)
55
- }, [])
56
-
57
- const handleChange = React.useCallback((name: string, value: any) => {
58
- setValues(prev => ({ ...prev, [name]: value }))
59
- }, [])
60
-
61
- const handleSubmit = React.useCallback(() => {
62
- // Require at least one non-empty value
63
- const hasValue = Object.values(values).some(v =>
64
- typeof v === "string" ? v.trim().length > 0 : v != null
65
- )
66
- if (hasValue) {
67
- onSubmit(columnId, values)
68
- }
69
- }, [columnId, values, onSubmit])
70
-
71
- const handleKeyDown = React.useCallback(
72
- (e: React.KeyboardEvent) => {
73
- if (e.key === "Enter") {
74
- e.preventDefault()
75
- handleSubmit()
76
- } else if (e.key === "Escape") {
77
- e.preventDefault()
78
- onCancel()
79
- }
80
- },
81
- [handleSubmit, onCancel],
82
- )
83
-
84
- return (
85
- <div
86
- className="mt-2 rounded-lg border border-primary/30 bg-card p-3 space-y-2 shadow-sm"
87
- onKeyDown={handleKeyDown}
88
- role="form"
89
- aria-label="Quick add card"
90
- >
91
- {fields.map((field, idx) => (
92
- <div key={field.name} className="space-y-1">
93
- <label
94
- htmlFor={`qa-${columnId}-${field.name}`}
95
- className="text-xs font-mono text-muted-foreground"
96
- >
97
- {field.label ?? field.name}
98
- </label>
99
- {renderField(field, values[field.name], handleChange, idx === 0 ? firstInputRef : undefined, `qa-${columnId}-${field.name}`)}
100
- </div>
101
- ))}
102
- <div className="flex items-center gap-2 pt-1">
103
- <Button
104
- type="button"
105
- size="sm"
106
- className="h-7 text-xs gap-1"
107
- onClick={handleSubmit}
108
- >
109
- <Check className="h-3 w-3" />
110
- Save
111
- </Button>
112
- <Button
113
- type="button"
114
- variant="ghost"
115
- size="sm"
116
- className="h-7 text-xs gap-1"
117
- onClick={onCancel}
118
- >
119
- <X className="h-3 w-3" />
120
- Cancel
121
- </Button>
122
- </div>
123
- </div>
124
- )
125
- }
126
-
127
- /** Renders a single field based on its type. */
128
- function renderField(
129
- field: InlineFieldDefinition,
130
- value: any,
131
- onChange: (name: string, value: any) => void,
132
- ref?: React.Ref<any>,
133
- id?: string,
134
- ) {
135
- const commonClasses = "text-sm h-8"
136
-
137
- switch (field.type) {
138
- case "select":
139
- return (
140
- <select
141
- ref={ref as React.Ref<HTMLSelectElement>}
142
- id={id}
143
- value={value ?? ""}
144
- onChange={e => onChange(field.name, e.target.value)}
145
- className={cn(
146
- commonClasses,
147
- "w-full rounded-md border border-input bg-background px-2 py-1 text-sm ring-offset-background",
148
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
149
- )}
150
- >
151
- <option value="">{field.placeholder ?? `Select ${field.label ?? field.name}...`}</option>
152
- {(field.options ?? []).map(opt => (
153
- <option key={opt.value} value={opt.value}>
154
- {opt.label}
155
- </option>
156
- ))}
157
- </select>
158
- )
159
-
160
- case "number":
161
- return (
162
- <Input
163
- ref={ref as React.Ref<HTMLInputElement>}
164
- id={id}
165
- type="number"
166
- value={value ?? ""}
167
- onChange={e => onChange(field.name, e.target.value === "" ? "" : Number(e.target.value))}
168
- placeholder={field.placeholder ?? `Enter ${field.label ?? field.name}...`}
169
- className={commonClasses}
170
- />
171
- )
172
-
173
- case "text":
174
- default:
175
- return (
176
- <Input
177
- ref={ref as React.Ref<HTMLInputElement>}
178
- id={id}
179
- type="text"
180
- value={value ?? ""}
181
- onChange={e => onChange(field.name, e.target.value)}
182
- placeholder={field.placeholder ?? `Enter ${field.label ?? field.name}...`}
183
- className={commonClasses}
184
- />
185
- )
186
- }
187
- }
188
-
189
- export default InlineQuickAdd