@package-uploader/ui 1.1.2 → 1.1.3

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.
package/src/index.css CHANGED
@@ -1687,123 +1687,64 @@ body {
1687
1687
  }
1688
1688
 
1689
1689
  /* ========================================
1690
- BLOCK GROUPING PANEL
1690
+ BLOCK CARDS (visual block preview)
1691
1691
  ======================================== */
1692
1692
 
1693
- .bgp-toggle-btn {
1694
- margin-top: 0.5rem;
1695
- display: inline-flex;
1696
- align-items: center;
1697
- gap: 0.375rem;
1698
- background: var(--color-bg);
1699
- border: 1px solid var(--color-border);
1700
- color: var(--color-text-muted);
1701
- transition: all 0.15s;
1702
- }
1703
-
1704
- .bgp-toggle-btn:hover {
1705
- border-color: var(--color-primary);
1706
- color: var(--color-primary);
1707
- }
1708
-
1709
- .bgp-toggle-btn.active {
1710
- border-color: var(--color-primary);
1711
- color: var(--color-primary);
1712
- background: rgba(37, 99, 235, 0.05);
1713
- }
1714
-
1715
- .bgp-toggle-count {
1716
- background: var(--color-primary);
1717
- color: white;
1718
- font-size: 0.625rem;
1719
- padding: 0.0625rem 0.375rem;
1720
- border-radius: 999px;
1721
- font-weight: 600;
1722
- }
1723
-
1724
- .block-grouping-panel {
1725
- margin-top: 0.5rem;
1726
- border: 1px solid var(--color-border);
1727
- border-radius: var(--radius);
1728
- background: var(--color-bg);
1729
- overflow: hidden;
1730
- }
1731
-
1732
- .bgp-header {
1693
+ .block-cards {
1733
1694
  display: flex;
1734
- align-items: center;
1735
- gap: 0.5rem;
1736
- padding: 0.5rem 0.75rem;
1737
- background: var(--color-bg-secondary);
1738
- border-bottom: 1px solid var(--color-border);
1739
- }
1740
-
1741
- .bgp-title {
1742
- font-size: 0.8rem;
1743
- font-weight: 600;
1744
- color: var(--color-text);
1745
- }
1746
-
1747
- .bgp-count {
1748
- font-size: 0.7rem;
1749
- color: var(--color-primary);
1750
- background: rgba(37, 99, 235, 0.1);
1751
- padding: 0.0625rem 0.375rem;
1752
- border-radius: 999px;
1695
+ flex-direction: column;
1696
+ gap: 3px;
1697
+ padding: 0.375rem 0;
1753
1698
  }
1754
1699
 
1755
- /* Schematic preview */
1756
- .bgp-preview {
1757
- padding: 0.5rem;
1700
+ .block-card {
1758
1701
  display: flex;
1759
1702
  flex-direction: column;
1760
1703
  gap: 0.25rem;
1761
- max-height: 300px;
1762
- overflow-y: auto;
1763
- }
1764
-
1765
- .bgp-block {
1766
- display: flex;
1767
- align-items: center;
1768
- gap: 0.375rem;
1769
- padding: 0.375rem 0.5rem;
1704
+ padding: 0.5rem 0.625rem;
1770
1705
  border-radius: var(--radius-sm);
1706
+ border: 1px solid var(--color-border);
1707
+ border-left: 4px solid var(--block-accent, #6b7280);
1708
+ background: var(--color-bg);
1771
1709
  cursor: pointer;
1772
1710
  transition: all 0.15s;
1773
- border: 1px solid transparent;
1774
- font-size: 0.75rem;
1775
1711
  }
1776
1712
 
1777
- .bgp-block:hover:not(.grouped) {
1778
- background: var(--color-bg-secondary);
1779
- border-color: var(--color-border);
1713
+ .block-card:hover:not(.grouped) {
1714
+ border-color: var(--color-primary);
1715
+ border-left-color: var(--block-accent, #6b7280);
1716
+ background: rgba(37, 99, 235, 0.03);
1780
1717
  }
1781
1718
 
1782
- .bgp-block.selected {
1719
+ .block-card.selected {
1783
1720
  background: rgba(37, 99, 235, 0.08);
1784
1721
  border-color: var(--color-primary);
1722
+ border-left-color: var(--block-accent, #6b7280);
1723
+ box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.2);
1785
1724
  }
1786
1725
 
1787
- .bgp-block.grouped {
1726
+ .block-card.grouped {
1788
1727
  cursor: default;
1789
- opacity: 0.7;
1728
+ opacity: 0.75;
1729
+ background: var(--color-bg-secondary);
1790
1730
  }
1791
1731
 
1792
- .bgp-block-color {
1793
- width: 4px;
1794
- height: 1.25rem;
1795
- border-radius: 2px;
1796
- background: var(--block-color, #6b7280);
1797
- flex-shrink: 0;
1732
+ .block-card-header {
1733
+ display: flex;
1734
+ align-items: center;
1735
+ gap: 0.5rem;
1736
+ min-height: 1.25rem;
1798
1737
  }
1799
1738
 
1800
- .bgp-block-label {
1801
- font-weight: 500;
1739
+ .block-card-type {
1740
+ font-size: 0.8rem;
1741
+ font-weight: 600;
1802
1742
  color: var(--color-text);
1803
1743
  white-space: nowrap;
1804
1744
  }
1805
1745
 
1806
- .bgp-block-title {
1746
+ .block-card-title {
1747
+ font-size: 0.75rem;
1807
1748
  color: var(--color-text-muted);
1808
1749
  overflow: hidden;
1809
1750
  text-overflow: ellipsis;
@@ -1812,22 +1753,33 @@ body {
1812
1753
  min-width: 0;
1813
1754
  }
1814
1755
 
1815
- .bgp-block-check {
1756
+ .block-card-check {
1816
1757
  color: var(--color-primary);
1817
- font-weight: 600;
1758
+ font-weight: 700;
1759
+ font-size: 0.875rem;
1818
1760
  flex-shrink: 0;
1819
1761
  margin-left: auto;
1820
1762
  }
1821
1763
 
1822
- /* Group wrapper in preview */
1823
- .bgp-group-wrapper {
1764
+ .block-card .tree-class-input-sm {
1765
+ margin-left: 0;
1766
+ max-width: 100%;
1767
+ margin-top: 0;
1768
+ }
1769
+
1770
+ /* Group container wrapping adjacent block cards */
1771
+ .block-group-container {
1824
1772
  border: 2px dashed var(--color-primary);
1825
1773
  border-radius: var(--radius);
1826
- padding: 0.25rem;
1774
+ padding: 0.375rem;
1827
1775
  margin: 0.25rem 0;
1828
1776
  background: rgba(37, 99, 235, 0.02);
1829
1777
  }
1830
1778
 
1779
+ .block-group-container .block-card {
1780
+ border-left-width: 3px;
1781
+ }
1782
+
1831
1783
  .bgp-group-header {
1832
1784
  display: flex;
1833
1785
  align-items: center;
@@ -1924,7 +1876,9 @@ body {
1924
1876
  /* Create group controls */
1925
1877
  .bgp-create {
1926
1878
  padding: 0.5rem 0.75rem;
1927
- border-top: 1px solid var(--color-border);
1879
+ margin-top: 0.375rem;
1880
+ border: 1px solid var(--color-border);
1881
+ border-radius: var(--radius-sm);
1928
1882
  background: var(--color-bg-secondary);
1929
1883
  display: flex;
1930
1884
  flex-direction: column;
@@ -1,264 +0,0 @@
1
- import { useState } from 'react';
2
- import { useBlockGrouping, type BlockGroup } from '../hooks/useBlockGrouping';
3
-
4
- interface Block {
5
- id: string;
6
- type: string;
7
- family?: string;
8
- variant?: string;
9
- title?: string;
10
- }
11
-
12
- interface BlockGroupingPanelProps {
13
- lessonId: string;
14
- blocks: Block[];
15
- groups: BlockGroup[];
16
- onGroupsChange: (groups: BlockGroup[]) => void;
17
- }
18
-
19
- /** Color palette for block families */
20
- function familyColor(family?: string): string {
21
- switch (family) {
22
- case 'text': return '#3b82f6';
23
- case 'media': return '#22c55e';
24
- case 'interactive': return '#a855f7';
25
- case 'knowledge-check': return '#f59e0b';
26
- case 'quote': return '#ec4899';
27
- default: return '#6b7280';
28
- }
29
- }
30
-
31
- export default function BlockGroupingPanel({
32
- lessonId,
33
- blocks,
34
- groups,
35
- onGroupsChange,
36
- }: BlockGroupingPanelProps) {
37
- const [newGroupClass, setNewGroupClass] = useState('');
38
- const [newGroupLabel, setNewGroupLabel] = useState('');
39
- const [editingGroupId, setEditingGroupId] = useState<string | null>(null);
40
- const [editClass, setEditClass] = useState('');
41
- const [editLabel, setEditLabel] = useState('');
42
-
43
- const lessonBlockIds = blocks.map((b) => b.id);
44
-
45
- const {
46
- selectedBlockIds,
47
- toggleBlock,
48
- clearSelection,
49
- canCreateGroup,
50
- validationError,
51
- createGroup,
52
- removeGroup,
53
- updateGroup,
54
- isBlockGrouped,
55
- } = useBlockGrouping({
56
- lessonBlockIds,
57
- lessonId,
58
- groups,
59
- onGroupsChange,
60
- });
61
-
62
- const handleCreateGroup = () => {
63
- if (!canCreateGroup || !newGroupClass.trim()) return;
64
- createGroup(newGroupClass, newGroupLabel || undefined);
65
- setNewGroupClass('');
66
- setNewGroupLabel('');
67
- };
68
-
69
- const startEditGroup = (group: BlockGroup) => {
70
- setEditingGroupId(group.id);
71
- setEditClass(group.className);
72
- setEditLabel(group.label || '');
73
- };
74
-
75
- const saveEditGroup = () => {
76
- if (editingGroupId) {
77
- updateGroup(editingGroupId, { className: editClass, label: editLabel });
78
- setEditingGroupId(null);
79
- }
80
- };
81
-
82
- const cancelEdit = () => {
83
- setEditingGroupId(null);
84
- };
85
-
86
- // Build blockId -> group lookup
87
- const blockGroupMap = new Map<string, BlockGroup>();
88
- for (const group of groups) {
89
- for (const bid of group.blockIds) {
90
- blockGroupMap.set(bid, group);
91
- }
92
- }
93
-
94
- // Render a single block rectangle
95
- const renderBlock = (block: Block, isGrouped: boolean) => {
96
- const isSelected = selectedBlockIds.has(block.id);
97
- const canSelect = !isGrouped;
98
- const color = familyColor(block.family);
99
- const label = [block.type, block.variant].filter(Boolean).join(' / ');
100
-
101
- return (
102
- <div
103
- key={block.id}
104
- className={`bgp-block${isSelected ? ' selected' : ''}${isGrouped ? ' grouped' : ''}`}
105
- onClick={() => canSelect && toggleBlock(block.id)}
106
- style={{ '--block-color': color } as React.CSSProperties}
107
- title={canSelect ? 'Click to select' : 'Already in a group'}
108
- >
109
- <span className="bgp-block-color" />
110
- <span className="bgp-block-label">{label}</span>
111
- {block.title && (
112
- <span className="bgp-block-title">
113
- {block.title.length > 50 ? block.title.substring(0, 50) + '...' : block.title}
114
- </span>
115
- )}
116
- {isSelected && <span className="bgp-block-check">&#10003;</span>}
117
- </div>
118
- );
119
- };
120
-
121
- // Render group header (with edit/ungroup controls)
122
- const renderGroupHeader = (group: BlockGroup) => {
123
- const isEditing = editingGroupId === group.id;
124
-
125
- if (isEditing) {
126
- return (
127
- <div className="bgp-group-header">
128
- <div className="bgp-group-edit">
129
- <input
130
- type="text"
131
- value={editClass}
132
- onChange={(e) => setEditClass(e.target.value)}
133
- placeholder="CSS class(es)"
134
- className="bgp-edit-input"
135
- />
136
- <input
137
- type="text"
138
- value={editLabel}
139
- onChange={(e) => setEditLabel(e.target.value)}
140
- placeholder="Label (optional)"
141
- className="bgp-edit-input bgp-edit-input-sm"
142
- />
143
- <button className="bgp-btn bgp-btn-save" onClick={saveEditGroup}>Save</button>
144
- <button className="bgp-btn bgp-btn-cancel" onClick={cancelEdit}>Cancel</button>
145
- </div>
146
- </div>
147
- );
148
- }
149
-
150
- return (
151
- <div className="bgp-group-header">
152
- <span className="bgp-group-label">
153
- {group.label || group.className}
154
- </span>
155
- <span className="bgp-group-class">.{group.className}</span>
156
- <button
157
- className="bgp-btn bgp-btn-edit"
158
- onClick={() => startEditGroup(group)}
159
- title="Edit group"
160
- >
161
- Edit
162
- </button>
163
- <button
164
- className="bgp-btn bgp-btn-ungroup"
165
- onClick={() => removeGroup(group.id)}
166
- title="Remove group"
167
- >
168
- Ungroup
169
- </button>
170
- </div>
171
- );
172
- };
173
-
174
- // Build flat display list, rendering groups as nested containers
175
- const renderedGroups = new Set<string>();
176
- const previewItems: React.ReactNode[] = [];
177
-
178
- for (const block of blocks) {
179
- const group = blockGroupMap.get(block.id);
180
-
181
- if (group) {
182
- // Skip blocks already rendered as part of their group
183
- if (renderedGroups.has(group.id)) continue;
184
- renderedGroups.add(group.id);
185
-
186
- // Render the entire group container
187
- const groupBlocks = group.blockIds
188
- .map((bid) => blocks.find((b) => b.id === bid))
189
- .filter(Boolean) as Block[];
190
-
191
- previewItems.push(
192
- <div key={`group-${group.id}`} className="bgp-group-wrapper">
193
- {renderGroupHeader(group)}
194
- {groupBlocks.map((b) => renderBlock(b, true))}
195
- </div>
196
- );
197
- } else {
198
- previewItems.push(renderBlock(block, false));
199
- }
200
- }
201
-
202
- const hasSelection = selectedBlockIds.size > 0;
203
-
204
- return (
205
- <div className="block-grouping-panel">
206
- <div className="bgp-header">
207
- <span className="bgp-title">Block Groups</span>
208
- {groups.length > 0 && (
209
- <span className="bgp-count">{groups.length} group{groups.length !== 1 ? 's' : ''}</span>
210
- )}
211
- </div>
212
-
213
- {/* Schematic preview */}
214
- <div className="bgp-preview">
215
- {previewItems}
216
- </div>
217
-
218
- {/* Create group controls */}
219
- {hasSelection && (
220
- <div className="bgp-create">
221
- <div className="bgp-create-info">
222
- {selectedBlockIds.size} block{selectedBlockIds.size !== 1 ? 's' : ''} selected
223
- {validationError && <span className="bgp-validation-error"> — {validationError}</span>}
224
- </div>
225
- <div className="bgp-create-inputs">
226
- <input
227
- type="text"
228
- value={newGroupClass}
229
- onChange={(e) => setNewGroupClass(e.target.value)}
230
- placeholder="CSS class(es) for wrapper..."
231
- className="bgp-create-class"
232
- onKeyDown={(e) => e.key === 'Enter' && handleCreateGroup()}
233
- />
234
- <input
235
- type="text"
236
- value={newGroupLabel}
237
- onChange={(e) => setNewGroupLabel(e.target.value)}
238
- placeholder="Label (optional)"
239
- className="bgp-create-label"
240
- />
241
- </div>
242
- <div className="bgp-create-actions">
243
- <button
244
- className="btn btn-sm btn-primary"
245
- onClick={handleCreateGroup}
246
- disabled={!canCreateGroup || !newGroupClass.trim()}
247
- >
248
- Create Group
249
- </button>
250
- <button className="btn btn-sm btn-secondary" onClick={clearSelection}>
251
- Cancel
252
- </button>
253
- </div>
254
- </div>
255
- )}
256
-
257
- {!hasSelection && groups.length === 0 && (
258
- <div className="bgp-hint">
259
- Click adjacent blocks above to select them, then create a group to wrap them in a container div.
260
- </div>
261
- )}
262
- </div>
263
- );
264
- }