feat: implement elegant between-step hover-based add functionality

Replace intrusive floating hover buttons with sophisticated between-step add system:
- Add subtle BetweenStepAdd components in spaces between tutorial steps
- Implement smooth opacity transitions: 50% on area hover, 100% on button hover
- Show single dropdown with "📝 Concept Step" and "🎯 Problem Page" options
- Use proper z-index layering to appear above both adjacent steps
- Maintain empty state with traditional dropdown for initial step creation
- Convert step list to ultra-compact CompactStepItem components
- Remove old NewItemDropdown components and hover props from step items

This provides an intuitive, non-intrusive way to add steps precisely between existing items while dramatically reducing visual clutter and improving the user experience.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-21 11:46:24 -05:00
parent d53a52bb2c
commit 89a023971f
2 changed files with 276 additions and 572 deletions

View File

@@ -8,6 +8,7 @@ import { Tutorial, TutorialStep, PracticeStep, TutorialValidation, StepValidatio
import { PracticeStepEditor } from './PracticeStepEditor'
import { generateSingleProblem } from '../../utils/problemGenerator'
import { skillConfigurationToSkillSets, createBasicAllowedConfiguration } from '../../utils/skillConfiguration'
import { EditorLayout, TextInput, NumberInput, FormGroup, CompactStepItem, BetweenStepAdd } from './shared/EditorComponents'
import Resizable from 'react-resizable-layout'
// Modal component for tutorial metadata editing
@@ -386,12 +387,16 @@ function PracticeStepPreview({ step }: PracticeStepPreviewProps) {
setIsGenerating(true)
const generatedProblems = []
const maxToShow = Math.min(6, step.problemCount) // Show up to 6 problems in preview
const seenProblems = new Set<string>() // Track generated problems to avoid duplicates
// Use a basic configuration for generating problems
const config = createBasicAllowedConfiguration()
const { required, target, forbidden } = skillConfigurationToSkillSets(config)
for (let i = 0; i < maxToShow; i++) {
let attempts = 0
const maxAttempts = 200 // Prevent infinite loops
while (generatedProblems.length < maxToShow && attempts < maxAttempts) {
const problem = generateSingleProblem(
{
numberRange: step.numberRange || { min: 1, max: 9 },
@@ -407,8 +412,16 @@ function PracticeStepPreview({ step }: PracticeStepPreviewProps) {
)
if (problem) {
generatedProblems.push(problem)
// Create a unique key for the problem based on terms and answer
const problemKey = `${problem.terms.join('+')}_${problem.answer}`
if (!seenProblems.has(problemKey)) {
seenProblems.add(problemKey)
generatedProblems.push(problem)
}
}
attempts++
}
setProblems(generatedProblems)
@@ -455,12 +468,13 @@ function PracticeStepPreview({ step }: PracticeStepPreviewProps) {
</button>
</div>
{/* Problems Grid */}
{/* Problems Flow */}
{problems.length > 0 ? (
<div className={css({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))',
gap: 4
display: 'flex',
flexWrap: 'wrap',
gap: 3,
overflowX: 'auto'
})}>
{problems.map((problem, index) => (
<div key={problem.id} className={css({
@@ -469,7 +483,9 @@ function PracticeStepPreview({ step }: PracticeStepPreviewProps) {
borderColor: 'purple.200',
borderRadius: 'lg',
p: 3,
textAlign: 'center'
textAlign: 'center',
minWidth: '120px',
flexShrink: 0
})}>
{/* Problem Number */}
<div className={css({
@@ -578,14 +594,14 @@ function PracticeStepPreview({ step }: PracticeStepPreviewProps) {
)
}
// Component for the "New" dropdown positioned between steps
// Component for the "New" dropdown for empty state
interface NewItemDropdownProps {
position: number
onAddStep: (position: number) => void
onAddPracticeStep: (position: number) => void
onAddStep: () => void
onAddPracticeStep: () => void
}
function NewItemDropdown({ position, onAddStep, onAddPracticeStep }: NewItemDropdownProps) {
function NewItemDropdown({ onAddStep, onAddPracticeStep }: NewItemDropdownProps) {
const [isOpen, setIsOpen] = useState(false)
return (
@@ -625,7 +641,7 @@ function NewItemDropdown({ position, onAddStep, onAddPracticeStep }: NewItemDrop
})}>
<button
onClick={() => {
onAddStep(position)
onAddStep()
setIsOpen(false)
}}
className={css({
@@ -644,7 +660,7 @@ function NewItemDropdown({ position, onAddStep, onAddPracticeStep }: NewItemDrop
</button>
<button
onClick={() => {
onAddPracticeStep(position)
onAddPracticeStep()
setIsOpen(false)
}}
className={css({
@@ -1128,17 +1144,16 @@ export function TutorialEditor({
const items = []
// Insert at beginning
// Add BetweenStepAdd at the beginning
items.push(
<NewItemDropdown
key="new-0"
position={0}
onAddStep={(pos) => addStep(pos)}
onAddPracticeStep={(pos) => addPracticeStep(pos)}
<BetweenStepAdd
key="add-0"
onAddStep={() => addStep(0)}
onAddPracticeStep={() => addPracticeStep(0)}
/>
)
// Render each step with dropdown after it
// Render each step with BetweenStepAdd after it
unifiedSteps.forEach((item, index) => {
if (item.type === 'concept') {
const errors = getStepErrors(item.originalIndex)
@@ -1147,154 +1162,40 @@ export function TutorialEditor({
items.push(
<div key={`concept-${item.step.id}`}>
{/* Concept Step Item */}
<div
<CompactStepItem
type="concept"
index={index}
title={item.step.title}
subtitle={`${item.step.problem}${item.step.targetValue}`}
isSelected={isSelected}
hasErrors={errors.length > 0}
hasWarnings={warnings.length > 0}
errorCount={errors.length}
warningCount={warnings.length}
onClick={() => setEditorState(prev => ({
...prev,
selectedStepIndex: item.originalIndex,
selectedPracticeStepId: null
}))}
className={css({
p: 3,
border: '2px solid',
borderColor: isSelected ? 'blue.500' : (errors.length > 0 ? 'red.300' : 'gray.200'),
borderRadius: 'md',
bg: isSelected ? 'blue.50' : (warnings.length > 0 ? 'yellow.50' : (errors.length > 0 ? 'red.50' : 'white')),
cursor: 'pointer',
_hover: { bg: isSelected ? 'blue.100' : 'gray.50' }
})}
onPreview={() => previewStep(item.originalIndex)}
onDelete={() => deleteStep(item.originalIndex)}
>
<div className={css({ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 2 })}>
<div className={css({ flex: 1 })}>
<div className={css({ display: 'flex', alignItems: 'center', gap: 2, mb: 1 })}>
<span className={css({
fontSize: 'xs',
fontWeight: 'bold',
color: 'blue.800',
bg: 'blue.100',
px: 2,
py: 1,
borderRadius: 'sm'
})}>
📝 Step {index + 1}
</span>
{errors.length > 0 && (
<span className={css({
fontSize: 'xs',
color: 'red.600',
bg: 'red.100',
px: 2,
py: 1,
borderRadius: 'sm'
})}>
{errors.length} error{errors.length > 1 ? 's' : ''}
</span>
)}
{warnings.length > 0 && (
<span className={css({
fontSize: 'xs',
color: 'yellow.600',
bg: 'yellow.100',
px: 2,
py: 1,
borderRadius: 'sm'
})}>
{warnings.length} warning{warnings.length > 1 ? 's' : ''}
</span>
)}
</div>
<div className={css({ fontWeight: 'medium', fontSize: 'sm', mb: 1 })}>
{item.step.title}
</div>
<div className={css({ fontSize: 'xs', color: 'gray.600', mb: 1 })}>
{item.step.problem} {item.step.targetValue}
</div>
<div className={css({ fontSize: 'xs', color: 'gray.500' })}>
{item.step.description}
</div>
</div>
{/* Step Actions */}
<div className={css({ display: 'flex', gap: 1, flexShrink: 0 })}>
<button
onClick={(e) => {
e.stopPropagation()
previewStep(item.originalIndex)
}}
className={css({
p: 1,
bg: 'blue.100',
color: 'blue.700',
border: '1px solid',
borderColor: 'blue.300',
borderRadius: 'sm',
fontSize: 'xs',
cursor: 'pointer',
_hover: { bg: 'blue.200' }
})}
title="Preview step"
>
👁
</button>
<button
onClick={(e) => {
e.stopPropagation()
duplicateStep(item.originalIndex)
}}
className={css({
p: 1,
bg: 'green.100',
color: 'green.700',
border: '1px solid',
borderColor: 'green.300',
borderRadius: 'sm',
fontSize: 'xs',
cursor: 'pointer',
_hover: { bg: 'green.200' }
})}
title="Duplicate step"
>
📋
</button>
<button
onClick={(e) => {
e.stopPropagation()
deleteStep(item.originalIndex)
}}
className={css({
p: 1,
bg: 'red.100',
color: 'red.700',
border: '1px solid',
borderColor: 'red.300',
borderRadius: 'sm',
fontSize: 'xs',
cursor: 'pointer',
_hover: { bg: 'red.200' }
})}
title="Delete step"
>
🗑
</button>
</div>
</div>
{/* Error/Warning Details */}
{/* Error/Warning Details - show when there are issues */}
{(errors.length > 0 || warnings.length > 0) && (
<div className={css({ mt: 2, pt: 2, borderTop: '1px solid', borderColor: 'gray.200' })}>
<div className={css({ mt: 1, pt: 1, borderTop: '1px solid', borderColor: 'gray.200' })}>
{errors.map((error, errorIndex) => (
<div key={errorIndex} className={css({ fontSize: 'xs', color: 'red.600', mb: 1 })}>
<div key={errorIndex} className={css({ fontSize: 'xs', color: 'red.600', mb: 0.5 })}>
{error.message}
</div>
))}
{warnings.map((warning, warningIndex) => (
<div key={warningIndex} className={css({ fontSize: 'xs', color: 'orange.600', mb: 1 })}>
<div key={warningIndex} className={css({ fontSize: 'xs', color: 'orange.600', mb: 0.5 })}>
{warning.message}
</div>
))}
</div>
)}
</div>
</CompactStepItem>
</div>
)
} else {
@@ -1303,89 +1204,58 @@ export function TutorialEditor({
items.push(
<div key={`practice-${item.step.id}`}>
{/* Practice Step Card */}
<div
<CompactStepItem
type="practice"
index={index}
title={item.step.title}
subtitle={`${item.step.problemCount} problems • Max ${item.step.maxTerms} terms`}
isSelected={isSelected}
hasErrors={false}
hasWarnings={false}
onClick={() => setEditorState(prev => ({
...prev,
selectedPracticeStepId: item.step.id,
selectedStepIndex: null
}))}
className={css({
p: 3,
border: '2px solid',
borderColor: isSelected ? 'purple.500' : 'purple.200',
borderRadius: 'md',
bg: isSelected ? 'purple.50' : 'white',
cursor: 'pointer',
_hover: { bg: isSelected ? 'purple.100' : 'gray.50' }
})}
>
<div className={css({ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 2 })}>
<div className={css({ flex: 1 })}>
<div className={css({ display: 'flex', alignItems: 'center', gap: 2, mb: 1 })}>
<span className={css({
fontSize: 'xs',
fontWeight: 'bold',
color: 'purple.800',
bg: 'purple.100',
px: 2,
py: 1,
borderRadius: 'sm'
})}>
🎯 Problem Page {index + 1}
</span>
</div>
<div className={css({ fontWeight: 'medium', fontSize: 'sm', mb: 1 })}>
{item.step.title}
</div>
<div className={css({ fontSize: 'xs', color: 'gray.600', mb: 1 })}>
{item.step.problemCount} problems Max {item.step.maxTerms} terms
</div>
<div className={css({ fontSize: 'xs', color: 'gray.500' })}>
{item.step.description}
</div>
</div>
{/* Practice Step Actions */}
<div className={css({ display: 'flex', gap: 1, flexShrink: 0 })}>
<button
onClick={(e) => {
e.stopPropagation()
deletePracticeStep(item.originalIndex)
}}
className={css({
p: 1,
bg: 'red.100',
color: 'red.700',
border: '1px solid',
borderColor: 'red.300',
borderRadius: 'sm',
fontSize: 'xs',
cursor: 'pointer',
_hover: { bg: 'red.200' }
})}
title="Delete practice step"
>
🗑
</button>
</div>
</div>
</div>
onDelete={() => deletePracticeStep(item.originalIndex)}
/>
</div>
)
}
// Add dropdown after each step
// Add BetweenStepAdd after each step
items.push(
<NewItemDropdown
key={`new-${index + 1}`}
position={index + 1}
onAddStep={(pos) => addStep(pos)}
onAddPracticeStep={(pos) => addPracticeStep(pos)}
<BetweenStepAdd
key={`add-${index + 1}`}
onAddStep={() => addStep(index + 1)}
onAddPracticeStep={() => addPracticeStep(index + 1)}
/>
)
})
// Show empty state if no actual steps (only BetweenStepAdd components)
if (unifiedSteps.length === 0) {
return (
<div className={css({
textAlign: 'center',
py: 8,
color: 'gray.500'
})}>
<div className={css({ mb: 4, fontSize: 'lg' })}>📝</div>
<div className={css({ mb: 4, fontWeight: 'medium' })}>
No steps yet
</div>
<div className={css({ mb: 4, fontSize: 'sm' })}>
Add your first concept step or practice step to get started
</div>
<NewItemDropdown
onAddStep={() => addStep(0)}
onAddPracticeStep={() => addPracticeStep(0)}
/>
</div>
)
}
return items
})()}
</div>
@@ -1407,154 +1277,71 @@ export function TutorialEditor({
})}
/>
{/* Main content - Split between Tutorial Player and Step Editor */}
{/* Main content - Split between Step Editor and Preview */}
<div
className={css({
width: `calc(100% - ${position}px - 4px)`,
display: 'flex',
flexDirection: 'column',
flexDirection: 'row',
height: '100%'
})}
>
{/* Unified Step Editor - handles both concept and practice steps */}
{(editorState.selectedStepIndex !== null || editorState.selectedPracticeStepId) && (
{(editorState.selectedStepIndex !== null || editorState.selectedPracticeStepId) ? (
<div className={css({
flex: editorState.selectedPracticeStepId ? '0 0 400px' : '0 0 300px',
flex: '0 0 400px',
bg: 'white',
borderBottom: '1px solid',
borderRight: '1px solid',
borderColor: 'gray.200',
p: 4,
overflowY: 'auto'
overflowY: 'auto',
height: '100%'
})}>
<div className={css({ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 })}>
<h3 className={css({ fontWeight: 'bold', fontSize: 'lg' })}>
{editorState.selectedStepIndex !== null
? `Edit Step ${editorState.selectedStepIndex + 1}`
: 'Edit Practice Step'
}
</h3>
<button
onClick={() => setEditorState(prev => ({
{/* Conditional Editor Content */}
{editorState.selectedStepIndex !== null ? (
/* Concept Step Editor */
<EditorLayout
title={`Edit Step ${editorState.selectedStepIndex + 1}`}
onClose={() => setEditorState(prev => ({
...prev,
selectedStepIndex: null,
selectedPracticeStepId: null
}))}
className={css({
p: 1,
borderRadius: 'sm',
cursor: 'pointer',
_hover: { bg: 'gray.100' }
})}
>
</button>
</div>
{/* Conditional Editor Content */}
{editorState.selectedStepIndex !== null ? (
/* Concept Step Editor */
<div className={css({ display: 'flex', flexDirection: 'column', gap: 3 })}>
{/* Title */}
<div>
<label className={css({ fontSize: 'sm', fontWeight: 'medium', display: 'block', mb: 1 })}>
Title
</label>
<input
type="text"
<FormGroup>
<TextInput
label="Title"
value={tutorial.steps[editorState.selectedStepIndex].title}
onChange={(e) => updateStep(editorState.selectedStepIndex, { title: e.target.value })}
className={css({
w: 'full',
p: 2,
border: '1px solid',
borderColor: 'gray.300',
borderRadius: 'md',
fontSize: 'sm'
})}
onChange={(value) => updateStep(editorState.selectedStepIndex, { title: value })}
/>
</div>
{/* Problem */}
<div>
<label className={css({ fontSize: 'sm', fontWeight: 'medium', display: 'block', mb: 1 })}>
Problem
</label>
<input
type="text"
<TextInput
label="Problem"
value={tutorial.steps[editorState.selectedStepIndex].problem}
onChange={(e) => updateStep(editorState.selectedStepIndex, { problem: e.target.value })}
className={css({
w: 'full',
p: 2,
border: '1px solid',
borderColor: 'gray.300',
borderRadius: 'md',
fontSize: 'sm'
})}
onChange={(value) => updateStep(editorState.selectedStepIndex, { problem: value })}
/>
</div>
{/* Start Value & Target Value */}
<div className={css({ display: 'flex', gap: 2 })}>
<div className={css({ flex: 1 })}>
<label className={css({ fontSize: 'sm', fontWeight: 'medium', display: 'block', mb: 1 })}>
Start Value
</label>
<input
type="number"
<FormGroup columns={2}>
<NumberInput
label="Start Value"
value={tutorial.steps[editorState.selectedStepIndex].startValue}
onChange={(e) => updateStep(editorState.selectedStepIndex, { startValue: parseInt(e.target.value) || 0 })}
className={css({
w: 'full',
p: 2,
border: '1px solid',
borderColor: 'gray.300',
borderRadius: 'md',
fontSize: 'sm'
})}
onChange={(value) => updateStep(editorState.selectedStepIndex, { startValue: value })}
/>
</div>
<div className={css({ flex: 1 })}>
<label className={css({ fontSize: 'sm', fontWeight: 'medium', display: 'block', mb: 1 })}>
Target Value
</label>
<input
type="number"
<NumberInput
label="Target Value"
value={tutorial.steps[editorState.selectedStepIndex].targetValue}
onChange={(e) => updateStep(editorState.selectedStepIndex, { targetValue: parseInt(e.target.value) || 0 })}
className={css({
w: 'full',
p: 2,
border: '1px solid',
borderColor: 'gray.300',
borderRadius: 'md',
fontSize: 'sm'
})}
onChange={(value) => updateStep(editorState.selectedStepIndex, { targetValue: value })}
/>
</div>
</div>
</FormGroup>
{/* Description */}
<div>
<label className={css({ fontSize: 'sm', fontWeight: 'medium', display: 'block', mb: 1 })}>
Description
</label>
<textarea
<TextInput
label="Description"
value={tutorial.steps[editorState.selectedStepIndex].description}
onChange={(e) => updateStep(editorState.selectedStepIndex, { description: e.target.value })}
onChange={(value) => updateStep(editorState.selectedStepIndex, { description: value })}
multiline
rows={3}
className={css({
w: 'full',
p: 2,
border: '1px solid',
borderColor: 'gray.300',
borderRadius: 'md',
fontSize: 'sm',
resize: 'vertical'
})}
/>
</div>
</div>
</FormGroup>
</EditorLayout>
) : editorState.selectedPracticeStepId ? (
/* Practice Step Editor */
(() => {
@@ -1580,13 +1367,14 @@ export function TutorialEditor({
})()
) : null}
</div>
)}
) : null}
{/* Preview Section */}
<div className={css({
flex: 1,
overflowY: 'auto',
minHeight: 0
height: '100%',
minWidth: 0
})}>
{editorState.selectedPracticeStepId ? (
/* Practice Step Preview */

View File

@@ -52,8 +52,8 @@
box-shadow: var(--shadows-xl)
}
.mb_4 {
margin-bottom: var(--spacing-4)
.resize_vertical {
resize: vertical
}
.leading_normal {
@@ -64,6 +64,10 @@
min-height: 100px
}
.d_grid {
display: grid
}
.grid-cols_1fr_1fr_1fr {
grid-template-columns: 1fr 1fr 1fr
}
@@ -80,6 +84,18 @@
color: var(--colors-gray-700)
}
.d_block {
display: block
}
.border_blue\.300 {
border-color: var(--colors-blue-300)
}
.p_2 {
padding: var(--spacing-2)
}
.mt_6 {
margin-top: var(--spacing-6)
}
@@ -104,12 +120,24 @@
cursor: not-allowed
}
.d_grid {
display: grid
.flex-wrap_wrap {
flex-wrap: wrap
}
.grid-cols_repeat\(auto-fit\,_minmax\(120px\,_1fr\)\) {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr))
.gap_3 {
gap: var(--spacing-3)
}
.overflow-x_auto {
overflow-x: auto
}
.border_2px_solid {
border: 2px solid
}
.min-w_120px {
min-width: 120px
}
.text_purple\.600 {
@@ -132,10 +160,6 @@
border-color: var(--colors-gray-400)
}
.pt_1 {
padding-top: var(--spacing-1)
}
.fs_xl {
font-size: var(--font-sizes-xl)
}
@@ -144,6 +168,18 @@
text-align: right
}
.bg_yellow\.100 {
background: var(--colors-yellow-100)
}
.bg_red\.100 {
background: var(--colors-red-100)
}
.bg_green\.100 {
background: var(--colors-green-100)
}
.text_yellow\.800 {
color: var(--colors-yellow-800)
}
@@ -156,6 +192,10 @@
color: var(--colors-green-800)
}
.px_2 {
padding-inline: var(--spacing-2)
}
.rounded_full {
border-radius: var(--radii-full)
}
@@ -172,10 +212,26 @@
padding: var(--spacing-8)
}
.bg_purple\.50 {
background: var(--colors-purple-50)
}
.border_purple\.200 {
border-color: var(--colors-purple-200)
}
.font_semibold {
font-weight: var(--font-weights-semibold)
}
.text_purple\.800 {
color: var(--colors-purple-800)
}
.mb_2 {
margin-bottom: var(--spacing-2)
}
.text_purple\.700 {
color: var(--colors-purple-700)
}
@@ -188,6 +244,10 @@
margin-block: var(--spacing-2)
}
.py_1 {
padding-block: var(--spacing-1)
}
.bg_gray\.100 {
background: var(--colors-gray-100)
}
@@ -208,10 +268,6 @@
transform: translateX(-50%)
}
.mt_1 {
margin-top: var(--spacing-1)
}
.min-w_150px {
min-width: 150px
}
@@ -228,76 +284,72 @@
background: var(--colors-gray-50)
}
.border-r_1px_solid {
border-right: 1px solid
.border-b_1px_solid {
border-bottom: 1px solid
}
.shrink_0 {
flex-shrink: 0
}
.w_full {
width: var(--sizes-full)
}
.p_3 {
padding: var(--spacing-3)
}
.border_1px_solid {
border: 1px solid
}
.border_gray\.300 {
border-color: var(--colors-gray-300)
}
.text_left {
text-align: left
}
.items_center {
align-items: center
}
.justify_space-between {
justify-content: space-between
}
.mb_1 {
margin-bottom: var(--spacing-1)
}
.text_gray\.600 {
color: var(--colors-gray-600)
}
.text_gray\.400 {
color: var(--colors-gray-400)
}
.border_blue\.500 {
border-color: var(--colors-blue-500)
.min-h_0 {
min-height: var(--sizes-0)
}
.bg_red\.50 {
background: var(--colors-red-50)
.font_bold {
font-weight: var(--font-weights-bold)
}
.bg_yellow\.50 {
background: var(--colors-yellow-50)
.mb_3 {
margin-bottom: var(--spacing-3)
}
.bg_blue\.50 {
background: var(--colors-blue-50)
.mt_1 {
margin-top: var(--spacing-1)
}
.text_blue\.800 {
color: var(--colors-blue-800)
}
.text_yellow\.600 {
color: var(--colors-yellow-600)
}
.bg_yellow\.100 {
background: var(--colors-yellow-100)
}
.bg_blue\.100 {
background: var(--colors-blue-100)
}
.text_blue\.700 {
color: var(--colors-blue-700)
}
.border_blue\.300 {
border-color: var(--colors-blue-300)
}
.bg_green\.100 {
background: var(--colors-green-100)
}
.text_green\.700 {
color: var(--colors-green-700)
}
.border_green\.300 {
border-color: var(--colors-green-300)
}
.mt_2 {
margin-top: var(--spacing-2)
}
.pt_2 {
padding-top: var(--spacing-2)
.pt_1 {
padding-top: var(--spacing-1)
}
.border-t_1px_solid {
@@ -308,80 +360,28 @@
color: var(--colors-red-600)
}
.fs_xs {
font-size: var(--font-sizes-xs)
}
.text_orange\.600 {
color: var(--colors-orange-600)
}
.border_purple\.500 {
border-color: var(--colors-purple-500)
.mb_0\.5 {
margin-bottom: var(--spacing-0\.5)
}
.border_purple\.200 {
border-color: var(--colors-purple-200)
.py_8 {
padding-block: var(--spacing-8)
}
.bg_purple\.50 {
background: var(--colors-purple-50)
.fs_lg {
font-size: var(--font-sizes-lg)
}
.p_3 {
padding: var(--spacing-3)
}
.border_2px_solid {
border: 2px solid
}
.items_start {
align-items: start
}
.mb_2 {
margin-bottom: var(--spacing-2)
}
.text_purple\.800 {
color: var(--colors-purple-800)
}
.bg_purple\.100 {
background: var(--colors-purple-100)
}
.px_2 {
padding-inline: var(--spacing-2)
}
.py_1 {
padding-block: var(--spacing-1)
}
.text_gray\.600 {
color: var(--colors-gray-600)
}
.gap_1 {
gap: var(--spacing-1)
}
.shrink_0 {
flex-shrink: 0
}
.bg_red\.100 {
background: var(--colors-red-100)
}
.text_red\.700 {
color: var(--colors-red-700)
}
.border_red\.300 {
border-color: var(--colors-red-300)
}
.fs_xs {
font-size: var(--font-sizes-xs)
.mb_4 {
margin-bottom: var(--spacing-4)
}
.w_4px {
@@ -404,90 +404,26 @@
transition: background-color 0.2s
}
.flex_0_0_400px {
flex: 0 0 400px
.flex_row {
flex-direction: row
}
.flex_0_0_300px {
flex: 0 0 300px
.flex_0_0_400px {
flex: 0 0 400px
}
.bg_white {
background: var(--colors-white)
}
.border-b_1px_solid {
border-bottom: 1px solid
.border-r_1px_solid {
border-right: 1px solid
}
.border_gray\.200 {
border-color: var(--colors-gray-200)
}
.justify_space-between {
justify-content: space-between
}
.items_center {
align-items: center
}
.mb_3 {
margin-bottom: var(--spacing-3)
}
.font_bold {
font-weight: var(--font-weights-bold)
}
.fs_lg {
font-size: var(--font-sizes-lg)
}
.p_1 {
padding: var(--spacing-1)
}
.rounded_sm {
border-radius: var(--radii-sm)
}
.gap_3 {
gap: var(--spacing-3)
}
.gap_2 {
gap: var(--spacing-2)
}
.d_block {
display: block
}
.mb_1 {
margin-bottom: var(--spacing-1)
}
.w_full {
width: var(--sizes-full)
}
.p_2 {
padding: var(--spacing-2)
}
.border_1px_solid {
border: 1px solid
}
.border_gray\.300 {
border-color: var(--colors-gray-300)
}
.resize_vertical {
resize: vertical
}
.flex_1 {
flex: 1 1 0%
}
@@ -496,8 +432,8 @@
overflow-y: auto
}
.min-h_0 {
min-height: var(--sizes-0)
.min-w_0 {
min-width: var(--sizes-0)
}
.p_4 {
@@ -580,8 +516,8 @@
box-shadow: var(--shadows-md)
}
.pos_0 {
position: 0
.columns_2 {
columns: 2
}
.gap_0 {
@@ -604,14 +540,18 @@
flex-direction: column
}
.hover\:bg_purple\.600:is(:hover, [data-hover]) {
background: var(--colors-purple-600)
.hover\:bg_gray\.100:is(:hover, [data-hover]) {
background: var(--colors-gray-100)
}
.hover\:border_gray\.300:is(:hover, [data-hover]) {
border-color: var(--colors-gray-300)
}
.hover\:bg_purple\.600:is(:hover, [data-hover]) {
background: var(--colors-purple-600)
}
.hover\:bg_gray\.200:is(:hover, [data-hover]) {
background: var(--colors-gray-200)
}
@@ -632,42 +572,18 @@
color: var(--colors-purple-700)
}
.hover\:border_gray\.400:is(:hover, [data-hover]) {
border-color: var(--colors-gray-400)
}
.hover\:bg_blue\.100:is(:hover, [data-hover]) {
background: var(--colors-blue-100)
}
.hover\:bg_blue\.200:is(:hover, [data-hover]) {
background: var(--colors-blue-200)
}
.hover\:bg_green\.200:is(:hover, [data-hover]) {
background: var(--colors-green-200)
}
.hover\:bg_purple\.100:is(:hover, [data-hover]) {
background: var(--colors-purple-100)
}
.hover\:bg_gray\.50:is(:hover, [data-hover]) {
background: var(--colors-gray-50)
}
.hover\:bg_red\.200:is(:hover, [data-hover]) {
background: var(--colors-red-200)
.hover\:border_gray\.400:is(:hover, [data-hover]) {
border-color: var(--colors-gray-400)
}
.hover\:bg_blue\.400:is(:hover, [data-hover]) {
background: var(--colors-blue-400)
}
.hover\:bg_gray\.100:is(:hover, [data-hover]) {
background: var(--colors-gray-100)
}
.hover\:bg_blue\.600:is(:hover, [data-hover]) {
background: var(--colors-blue-600)
}