```
**🔧 Prevention:**
1. **Target specific files** instead of entire directories
2. **Use `--dry` run first** to verify only expected files change
3. **Review all diffs carefully** before committing
4. **Create cleanup commits** if formatting issues slip through
**🔧 Cleanup Pattern:**
```typescript
// Find files with formatting artifacts
git diff HEAD~1 HEAD --name-only | grep -v "target-component"
// Manual cleanup needed for unintended changes
```
### Documentation Reversion Issues
**⚠️ Problem:** Linters, formatters, or branch switches can revert documentation changes.
**🔧 Prevention:**
- **Commit documentation separately** from code changes
- **Verify documentation persists** after all code changes
- **Check git status** before final PR creation
- **Re-add documentation** if reverted by automation
### Component Package Dependencies
**⚠️ Problem:** Components in different packages need different import handling.
```typescript
// Icon is in @vibe/icon but also re-exported from @vibe/core
// Both need to be handled correctly
```
**🔧 Solution:**
- Check component package structure first
- Handle both direct imports and re-exports
- Test codemod on actual usage patterns
### Build System Validation
**⚠️ Problem:** Not all components build even before changes, causing false positives.
**🔧 Approach:**
- **Test individual packages** first (`yarn workspace @vibe/icon build`)
- **Focus on changed packages** rather than full monorepo build
- **Separate build issues** from breaking change issues
### Cross-Package Component Updates
**⚠️ Problem:** Breaking changes often affect components across multiple packages, not just the main @vibe/core package.
**Real Example - Icon Migration:**
Icon component is in `@vibe/icon` but used throughout:
- `packages/components/tooltip/` - standalone package using Icon
- `packages/core/src/components/` - 25+ core components using Icon
- `packages/components/icon-button/` - IconButton using Icon internally
- `packages/mcp/` - documentation and examples
**🔧 Comprehensive Search Strategy:**
```bash
# 1. Find ALL files with old prop usage across entire monorepo
grep -r "iconType\|iconSize\|iconLabel" packages/ --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js"
# 2. Identify usage by package type
find packages/components -name "*.tsx" -o -name "*.ts" | xargs grep -l "iconType="
find packages/core/src -name "*.tsx" -o -name "*.ts" | xargs grep -l "iconSize="
# 3. Check supporting files (docs, examples, tests)
grep -r "iconLabel=" packages/mcp packages/docs
```
**🔧 Update Strategy:**
1. **Component Package First** - Update the source component and its internal hooks
2. **Core Package Components** - Systematically update all core components (20-30 files typical)
3. **Standalone Packages** - Update components that depend on the changed component
4. **Supporting Files** - Update documentation, examples, and MCP tools
5. **Build Verification** - Test each package individually before full build
### TypeScript Build Errors in Related Components
**⚠️ Problem:** Breaking changes can reveal existing TypeScript errors in related components.
**Real Example - IconButton Props:**
```typescript
// DatePickerHeader was using deprecated Button static properties
// Fixed to use string literals
```
**🔧 Resolution Approach:**
1. **Isolate the error** - determine if it's related to your breaking change or pre-existing
2. **Check component API** - verify correct prop values for the component
3. **Use string literals** instead of deprecated static enum properties
4. **Test build** after each fix to catch additional issues
### Documentation and Example Updates
**⚠️ Problem:** Code examples in documentation and MCP tools need updating with new API.
**🔧 Required Updates:**
- **MCP Documentation** - Update usage examples with new props
- **Component Stories** - Storybook examples using the component
- **README files** - Any inline code examples
- **Test files** - Update snapshot tests and component tests
## Quality Gates
**⚠️ These are hard gates, not suggestions. Do NOT proceed past a gate until every item passes.**
**Before committing (all must pass with exit code 0):**
- [ ] `yarn lint` — zero errors
- [ ] `yarn build` — zero errors
- [ ] `yarn test` — zero failures
- [ ] Migration guide entry complete
**Before creating PR:**
- [ ] All of the above still pass after final commit
- [ ] Codemod tested (if applicable)
- [ ] Documentation updated
## Integration Points
- **Package Dependencies:** Update `package.json` files as needed
- **TypeScript:** Ensure type consistency across packages
- **Build System:** Verify Rollup configurations handle changes
- **Storybook:** Update stories to reflect new API
- **Documentation:** Sync with component documentation site
================================================
FILE: .claude/skills/vibe-breaking-change/references/codemod-best-practices.md
================================================
# Codemod Best Practices for Vibe Breaking Changes
## Lessons Learned from Real Implementation
This guide covers critical lessons learned from implementing the Icon component prop renames codemod, including major pitfalls to avoid.
## ❌ Common Mistakes to Avoid
### 1. Using Non-Existent Functions
**WRONG:**
```typescript
import { renameProp } from "../../../src/utils"; // ❌ Doesn't exist
elements.forEach(elementPath => {
if (isPropExists(j, elementPath, "oldProp")) {
renameProp(j, elementPath, "oldProp", "newProp"); // ❌ Function doesn't exist
}
});
```
**CORRECT:**
```typescript
import { migratePropsNames } from "../../../src/utils"; // ✅ Real function
elements.forEach(elementPath => {
migratePropsNames(j, elementPath, filePath, componentName, {
oldProp: "newProp",
anotherOld: "anotherNew"
}); // ✅ Handles all props in one efficient call
});
```
### 2. Wrong Import Path Detection
**WRONG:**
```typescript
// This only finds "monday-ui-react-core" imports, not "@vibe/core"
const imports = getCoreImportsForFile(root);
```
**CORRECT:**
```typescript
import { NEW_CORE_IMPORT_PATH } from "../../../src/consts";
// This finds "@vibe/core" imports correctly
const imports = getImports(root, NEW_CORE_IMPORT_PATH);
```
### 3. Inefficient Multiple Loops
**WRONG:**
```typescript
elements.forEach(elementPath => {
// Separate check for each prop - inefficient!
if (isPropExists(j, elementPath, "iconLabel")) {
renameProp(j, elementPath, "iconLabel", "label");
}
if (isPropExists(j, elementPath, "iconType")) {
renameProp(j, elementPath, "iconType", "type");
}
if (isPropExists(j, elementPath, "iconSize")) {
renameProp(j, elementPath, "iconSize", "size");
}
});
```
**CORRECT:**
```typescript
elements.forEach(elementPath => {
// Single call handles all prop renames efficiently
migratePropsNames(j, elementPath, filePath, componentName, {
iconLabel: "label",
iconType: "type",
iconSize: "size"
});
});
```
## 🔧 Proven Codemod Pattern
Based on successful v2-v3 codemods, use this proven pattern:
```typescript
import {
wrap,
getImports,
getComponentNameOrAliasFromImports,
findComponentElements,
migratePropsNames
} from "../../../src/utils";
import { NEW_CORE_IMPORT_PATH } from "../../../src/consts";
import { TransformationContext } from "../../../types";
function transform({ j, root, filePath }: TransformationContext) {
// 1. Find imports from correct package
const imports = getImports(root, NEW_CORE_IMPORT_PATH);
// 2. Check if component is imported
const componentName = getComponentNameOrAliasFromImports(j, imports, "ComponentName");
if (!componentName) return;
// 3. Find all component elements
const elements = findComponentElements(root, componentName);
if (!elements.length) return;
// 4. Migrate props efficiently
elements.forEach(elementPath => {
migratePropsNames(j, elementPath, filePath, componentName, {
oldProp1: "newProp1",
oldProp2: "newProp2",
oldProp3: "newProp3"
});
});
}
export default wrap(transform);
```
## ⚠️ JSCodeshift Formatting Artifacts
### The Problem
JSCodeshift can add unnecessary parentheses around JSX elements, even in files that **don't use the target component**.
**Example:**
```jsx
// Original code
Content
// After codemod (WRONG!)
(
Content
)
```
### Root Cause
JSCodeshift parses and rebuilds the AST, which can introduce formatting changes even when no transforms are applied.
### Prevention Strategies
#### 1. Target Specific Files
```bash
# WRONG - transforms entire directory
npx jscodeshift -t transform.js packages/core/src/components/
# BETTER - target specific files known to use the component
npx jscodeshift -t transform.js packages/core/src/components/Button/Button.tsx packages/core/src/components/IconButton/IconButton.tsx
```
#### 2. Use Dry Run First
```bash
# Always run dry first to see what changes
npx jscodeshift -t transform.js packages/ --dry
# Review the output carefully
# Only proceed if changes look correct
npx jscodeshift -t transform.js packages/
```
#### 3. Filter by Import Detection
```typescript
function transform({ j, root, filePath }: TransformationContext) {
// Only proceed if file actually imports the component
const imports = getImports(root, NEW_CORE_IMPORT_PATH);
const componentName = getComponentNameOrAliasFromImports(j, imports, "ComponentName");
if (!componentName) return; // ✅ Exit early, no changes
// ... rest of transform
}
```
### Cleanup Process
If formatting artifacts slip through:
```bash
# 1. Identify files with unintended changes
git diff HEAD~1 HEAD --name-only | grep -E "(Checkbox|AvatarGroup|Dropdown)"
# 2. Review each file
git diff HEAD~1 HEAD -- path/to/file.tsx
# 3. Manual cleanup
# Remove extra parentheses: (
→
# Fix indentation as needed
# 4. Commit cleanup
git add .
git commit -m "fix: remove codemod formatting artifacts"
```
## 🧪 Testing Best Practices
### Test Development
```typescript
// Cover edge cases in tests
defineInlineTest(
transform,
{},
prependImport('const element = ;'),
prependImport('const element = ;'),
"should rename all three props"
);
// Test aliased imports
defineInlineTest(
transform,
{},
`import { Icon as VibeIcon } from "@vibe/core";
const element = ;`,
`import { Icon as VibeIcon } from "@vibe/core";
const element = ;`,
"should work with aliased imports"
);
```
### Expected Results
- **7-8 out of 8 tests pass** - indicates working codemod
- **1 formatting test failure** - expected jscodeshift artifact (acceptable)
- **0 tests pass** - indicates broken codemod logic
## 📝 Documentation Integration
### Migration Guide Entry
```markdown
## ComponentName API Changes
### Breaking Changes
**Props Renamed:**
- `oldProp` → `newProp`
- `anotherOld` → `anotherNew`
### Automated Migration
```bash
npx @vibe/codemod component-name-props-update src/
```
### Manual Migration
[Include before/after examples]
```
### Changelog Entry
```markdown
#### ComponentName v2.0.0
- **BREAKING**: Renamed props for better consistency
- **Migration**: Use `npx @vibe/codemod component-name-props-update`
- **Task**: Monday.com #[task-id]
```
## 🚀 Success Criteria
A successful codemod implementation should:
1. **✅ Follow established patterns** from v2-v3 codemods
2. **✅ Use correct utility functions** (migratePropsNames, getImports)
3. **✅ Handle import paths correctly** (NEW_CORE_IMPORT_PATH)
4. **✅ Be efficient** (single loop, batch prop updates)
5. **✅ Have comprehensive tests** (7+ passing test cases)
6. **✅ Minimize formatting artifacts** (targeted file updates)
7. **✅ Include cleanup process** (for any artifacts that slip through)
## 🔄 Iteration Process
1. **RED**: Write failing tests first
2. **GREEN**: Implement minimal working codemod
3. **REFACTOR**: Fix issues, improve efficiency
4. **CLEANUP**: Remove formatting artifacts
5. **DOCUMENT**: Update migration guide and changelog
6. **VALIDATE**: Test on real codebase examples
================================================
FILE: .claude/skills/vibe-breaking-change/references/codemod-examples.md
================================================
# Codemod Examples for Vibe Breaking Changes
## Simple Prop Rename
```typescript
// Transform: oldProp → newProp
import type { Transform } from 'jscodeshift';
const transform: Transform = (file, api) => {
const j = api.jscodeshift;
const root = j(file.source);
// Transform JSX prop usage
root
.find(j.JSXElement)
.filter(path => {
const name = path.value.openingElement?.name;
return name && name.type === 'JSXIdentifier' && name.name === 'ComponentName';
})
.forEach(path => {
const attributes = path.value.openingElement?.attributes || [];
attributes.forEach(attr => {
if (
attr.type === 'JSXAttribute' &&
attr.name?.type === 'JSXIdentifier' &&
attr.name.name === 'oldProp'
) {
attr.name.name = 'newProp';
}
});
});
return root.toSource();
};
export default transform;
```
## Enum to String Union Migration
```typescript
// Transform: ComponentName.SIZE.LARGE → "large"
import type { Transform } from 'jscodeshift';
const transform: Transform = (file, api) => {
const j = api.jscodeshift;
const root = j(file.source);
// Replace enum member access with string literal
root
.find(j.MemberExpression, {
object: {
type: 'MemberExpression',
object: { name: 'ComponentName' },
property: { name: 'SIZE' }
}
})
.forEach(path => {
const property = path.value.property;
if (property.type === 'Identifier') {
const stringValue = property.name.toLowerCase();
j(path).replaceWith(j.literal(stringValue));
}
});
return root.toSource();
};
export default transform;
```
## Complex Import Path Update
```typescript
// Transform: from old package to new package structure
import type { Transform } from 'jscodeshift';
const transform: Transform = (file, api) => {
const j = api.jscodeshift;
const root = j(file.source);
// Update import declarations
root
.find(j.ImportDeclaration)
.filter(path => {
return path.value.source.value?.includes('@vibe/core/components/ComponentName');
})
.forEach(path => {
if (path.value.source.type === 'Literal') {
path.value.source.value = '@vibe/component-name';
}
});
return root.toSource();
};
export default transform;
```
## Testing Codemod Changes
```bash
# Test codemod on specific file
npx jscodeshift -t packages/codemod/src/transforms/component-prop-rename.ts packages/core/src/components/Button/__tests__/Button.test.tsx --dry
# Test on directory
npx jscodeshift -t packages/codemod/src/transforms/component-prop-rename.ts packages/core/src/components/ --dry
# Apply transformation
npx jscodeshift -t packages/codemod/src/transforms/component-prop-rename.ts packages/core/src/components/
# Add to codemod CLI
# Edit packages/codemod/src/index.ts to include new transform
```
## Codemod Integration Checklist
- [ ] Transform handles JSX usage
- [ ] Transform handles TypeScript interfaces
- [ ] Transform handles prop destructuring
- [ ] Transform handles spread props appropriately
- [ ] Test cases cover edge cases
- [ ] Dry run shows expected changes
- [ ] Added to codemod CLI interface
- [ ] Documentation includes usage example
================================================
FILE: .claude/skills/vibe-breaking-change/references/dependency-analysis.md
================================================
# Dependency Analysis for Breaking Changes
## Component Dependency Mapping
### Finding Direct Dependencies
```bash
# Find components importing the target component
grep -r "import.*ComponentName" packages/core/src/components/
grep -r "import.*ComponentName" packages/components/
# Find usage in TypeScript files
grep -r "ComponentName" packages/*/src/**/*.tsx
grep -r "ComponentName" packages/*/src/**/*.ts
# Find usage in test files
grep -r "ComponentName" packages/*/src/**/__tests__/*.tsx
grep -r "ComponentName" packages/*/src/**/__stories__/*.tsx
```
### Finding Indirect Dependencies
```bash
# Check for components that extend or compose target component
grep -r "extends.*ComponentNameProps" packages/
grep -r "ComponentName.*Props" packages/
# Find re-exports
grep -r "export.*ComponentName" packages/*/src/index.ts
grep -r "export.*from.*ComponentName" packages/
```
### Analyzing Package Dependencies
```bash
# Check package.json dependencies
find packages/ -name "package.json" -exec grep -l "@vibe/component-name\|ComponentName" {} \;
# Check workspace dependencies
yarn workspace @vibe/core info
yarn workspaces info
```
## Impact Assessment Matrix
| Component | Import Type | Usage Pattern | Breaking Impact | Update Required |
|-----------|-------------|---------------|-----------------|-----------------|
| ButtonGroup | Direct | Props passed through | High | Yes - API change |
| Dialog | Indirect | Used in composition | Medium | Yes - prop update |
| Form | Test only | Test utilities | Low | Maybe - test update |
## Component Relationship Types
### Direct Usage
- Component imports and uses target component
- Props passed directly to target component
- **Impact:** High - requires immediate update
### Composed Usage
- Component wraps or extends target component
- May add additional props or behavior
- **Impact:** Medium - requires careful testing
### Re-export Usage
- Package re-exports target component
- May not use component directly
- **Impact:** Low - update exports only
### Test/Story Usage
- Only used in test files or Storybook
- No runtime impact on users
- **Impact:** Minimal - update tests/stories
## Dependency Update Strategy
### Phase 1: Core Component Update
1. Update target component with breaking change
2. Update component types and interfaces
3. Ensure component builds and basic tests pass
### Phase 2: Direct Dependencies
1. Update components with direct imports
2. Update prop passing and usage patterns
3. Fix compilation errors
### Phase 3: Indirect Dependencies
1. Update composed components
2. Update complex usage patterns
3. Verify behavioral consistency
### Phase 4: Package Dependencies
1. Update package.json versions if needed
2. Update workspace dependencies
3. Verify cross-package compatibility
## Validation Commands
```bash
# Check TypeScript compilation across all packages
lerna run type-check
# Check for remaining references to old API
grep -r "oldProp" packages/ --include="*.ts" --include="*.tsx"
# Verify no broken imports
yarn workspace @vibe/core build
lerna run build
# Check for usage in Storybook
grep -r "oldProp" packages/**/__stories__/
```
## Common Dependency Patterns
### Button Dependencies
- ButtonGroup, IconButton, SplitButton
- Form components (FieldButton, FormButton)
- Dialog components (DialogButton)
### Icon Dependencies
- All components using icons
- IconButton, Button with icons
- Menu items, navigation components
### Layout Dependencies
- Grid system components
- Container components
- Responsive utilities
### Hook Dependencies
- Components using shared hooks
- Custom hook implementations
- Test utilities using hooks
## Risk Mitigation
### High-Risk Changes
- Core utility changes (hooks, constants)
- Base component changes (Button, Icon)
- Type system changes (VibeComponentProps)
### Medium-Risk Changes
- Layout component changes
- Form component changes
- Complex composite components
### Low-Risk Changes
- Leaf components (no dependencies)
- Style-only changes
- Test utility changes
================================================
FILE: .claude/skills/vibe-breaking-change/references/pr-templates.md
================================================
# PR and Documentation Templates
## PR Template for Breaking Changes
### PR Title Format
Follow [Conventional Commits](https://www.conventionalcommits.org/). Use the appropriate type (`feat`, `fix`, `refactor`, etc.):
```
(): brief description
Examples:
feat(Button): remove deprecated size prop
refactor(Dialog): update API for better accessibility
fix(Form): replace onSubmit with onFormSubmit
```
### PR Description Template
```markdown
## Summary
• Brief description of the breaking change
• Why this change was necessary
• Impact on existing users
## Breaking Changes
**[ComponentName] API Changes:**
- ❌ Removed: `oldProp` prop
- ✅ Added: `newProp` prop with enhanced functionality
- 🔄 Changed: `existingProp` now requires different value format
## Migration Path
### Automated Migration (Codemod Available)
```bash
npx @vibe/codemod component-name-api-update
```
### Manual Migration
**Before:**
```jsx
```
**After:**
```jsx
```
## Task Link
[Monday.com Task](https://monday.monday.com/boards//pulses/)
## Testing
- [ ] All component tests pass
- [ ] Integration tests updated and passing
- [ ] Dependent components tested
- [ ] Storybook stories render correctly
- [ ] Codemod tested on real codebase examples
- [ ] Performance impact assessed
## Documentation
- [ ] Migration guide updated
- [ ] API documentation updated
- [ ] Changelog entry added
- [ ] Codemod documentation added
## Validation
- [ ] Full monorepo build passes
- [ ] All linting checks pass
- [ ] TypeScript compilation successful
- [ ] No broken imports or exports
- [ ] Manual testing completed
## Reviewer Notes
- Focus on [specific areas of concern]
- Pay attention to [migration complexity/edge cases]
- Verify [specific functionality still works]
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
```
## Commit Message Template
Follow [Conventional Commits](https://www.conventionalcommits.org/). Use the appropriate type (`feat`, `fix`, `refactor`, etc.):
```
(): brief description
Detailed explanation of what changed and why.
Include context about the problem being solved.
BREAKING CHANGE: Specific description of what breaks.
Explain the old behavior vs new behavior.
Migration:
- Old usage: ComponentName.oldProp
- New usage: ComponentName.newProp
- Codemod: npx @vibe/codemod component-migration
Affects:
- ComponentName API
- Dependent components: ButtonGroup, Dialog
- Documentation: Migration guide updated
Co-Authored-By: Claude Opus 4.6
```
## Migration Guide Entry Template
```markdown
## [ComponentName] API Changes
### Overview
Brief explanation of what changed and why the breaking change was necessary.
### Breaking Changes
#### Removed `oldProp` prop
- **Impact**: High - affects all usage
- **Reason**: Simplifies API and improves performance
- **Migration**: Replace with `newProp`
#### Changed `existingProp` format
- **Impact**: Medium - affects complex usage
- **Reason**: Better type safety and validation
- **Migration**: Convert values using utility function
### Before and After
#### Basic Usage
```jsx
// Before
// After
```
#### Advanced Usage
```jsx
// Before
handleOldWay(data)}
/>
// After
handleNewWay(data)}
/>
```
### Migration Strategies
#### Automated Migration
Use the provided codemod for straightforward cases:
```bash
npx @vibe/codemod componentname-api-update src/
```
The codemod handles:
- Simple prop renames
- Basic value transformations
- Import statement updates
#### Manual Migration
For complex cases requiring human judgment:
1. **Dynamic prop values**: Review logic and update calculations
2. **Event handlers**: Update to match new event signature
3. **Conditional usage**: Verify new API handles all conditions
4. **Tests**: Update test assertions and mocks
#### Gradual Migration
For large codebases, consider a phased approach:
1. **Phase 1**: Update critical components first
2. **Phase 2**: Update high-traffic components
3. **Phase 3**: Update remaining components
4. **Phase 4**: Remove any compatibility shims
### Common Issues
#### TypeScript Errors
```typescript
// Error: Property 'oldProp' does not exist
// Solution: Use new prop name
```
#### Runtime Errors
```javascript
// Error: onOldEvent is not a function
props.onOldEvent?.(data);
// Solution: Use new event name
props.onNewEvent?.(data);
```
### Validation
After migration, verify:
- [ ] No TypeScript compilation errors
- [ ] Component renders as expected
- [ ] Event handlers work correctly
- [ ] Tests pass
- [ ] No console warnings
```
## Changelog Entry Template
```markdown
### BREAKING CHANGES
#### ComponentName v2.0.0
- **BREAKING**: Removed `oldProp` in favor of `newProp` for better API consistency
- **BREAKING**: Changed `existingProp` format from object to string for improved performance
- **Migration**: Use `npx @vibe/codemod componentname-api-update` for automated migration
- **Affects**: All ComponentName usage, ButtonGroup, Dialog components
##### Migration Example
```jsx
// Before
// After
```
See [Migration Guide](./VIBE4_MIGRATION_GUIDE.md#componentname-api-changes) for detailed instructions.
```
## Code Review Checklist Template
```markdown
## Breaking Change Review Checklist
### API Design
- [ ] Breaking change is necessary and well-justified
- [ ] New API is more intuitive than old API
- [ ] API follows Vibe design system conventions
- [ ] TypeScript types are accurate and helpful
### Implementation
- [ ] Target component updated correctly
- [ ] All dependent components updated
- [ ] No lingering references to old API
- [ ] Error handling updated appropriately
### Testing
- [ ] Component tests updated and comprehensive
- [ ] Integration tests cover dependent components
- [ ] Edge cases tested
- [ ] Performance impact assessed
### Documentation
- [ ] Migration guide entry is clear and complete
- [ ] Code examples work correctly
- [ ] Codemod documented and tested
- [ ] Breaking change clearly flagged
### Developer Experience
- [ ] TypeScript errors are helpful
- [ ] Runtime errors (if any) are clear
- [ ] Migration path is straightforward
- [ ] Documentation is discoverable
### Build System
- [ ] All packages build successfully
- [ ] No circular dependencies introduced
- [ ] Import/export structure clean
- [ ] Version bumps appropriate
```
================================================
FILE: .claude/skills/vibe-breaking-change/references/testing-validation.md
================================================
# Testing and Validation for Breaking Changes
## Testing Strategy
### 1. Unit Tests
Test individual component behavior with new API
```typescript
// Before: Test with old API
it('should handle oldProp correctly', () => {
render();
expect(screen.getByRole('button')).toHaveAttribute('data-old', 'value');
});
// After: Test with new API
it('should handle newProp correctly', () => {
render();
expect(screen.getByRole('button')).toHaveAttribute('data-new', 'value');
});
// Migration test: Ensure old API no longer works
it('should not accept oldProp', () => {
// TypeScript should prevent this at compile time
// @ts-expect-error - oldProp no longer exists
render();
});
```
### 2. Integration Tests
Test dependent components work with changes
```typescript
// Test component that uses the changed component
it('should work with updated ComponentName API', () => {
render(
);
// Verify integration works
expect(screen.getByTestId('parent-wrapper')).toBeInTheDocument();
expect(screen.getByRole('button')).toHaveAttribute('data-new', 'value');
});
```
### 3. Snapshot Testing
Update snapshots for visual regression testing
```bash
# Update component snapshots
yarn workspace @vibe/core test -- ComponentName --updateSnapshot
# Update all affected snapshots
yarn workspace @vibe/core test -- --updateSnapshot
# Check snapshot diffs are intentional
git diff packages/core/src/components/ComponentName/__tests__/__snapshots__/
```
## Validation Checklist
### Pre-Implementation Validation
- [ ] Impact analysis complete
- [ ] Dependent components identified
- [ ] Migration strategy defined
- [ ] Breaking change justified and approved
### Implementation Validation
- [ ] Target component updated correctly
- [ ] TypeScript compilation passes
- [ ] Component tests updated and passing
- [ ] No console errors in development
### Dependency Validation
- [ ] All dependent components updated
- [ ] Integration tests passing
- [ ] No broken imports or exports
- [ ] Cross-package dependencies resolved
### Build Validation
```bash
# Full monorepo build
lerna run build
# Individual package builds
yarn workspace @vibe/core build
yarn workspace @vibe/components build
# TypeScript validation
lerna run type-check
# Lint validation
lerna run lint
```
### Runtime Validation
```bash
# Storybook validation
yarn storybook
# Manually verify affected stories render correctly
# Test app validation (if available)
yarn start
# Test breaking changes in demo application
```
### Documentation Validation
- [ ] Migration guide updated with clear examples
- [ ] Breaking change documented with before/after
- [ ] Codemod usage documented (if applicable)
- [ ] API documentation updated
## Test Update Patterns
### Props Interface Changes
```typescript
// Old interface
interface OldComponentProps {
oldProp?: string;
size?: 'small' | 'medium' | 'large';
}
// New interface
interface NewComponentProps {
newProp?: string;
size?: ComponentSize; // enum to string union
}
// Test updates needed:
// 1. Update prop names in test cases
// 2. Update enum values to string literals
// 3. Add tests for new API behavior
// 4. Remove tests for deprecated functionality
```
### Behavior Changes
```typescript
// Test behavioral changes
describe('ComponentName behavior changes', () => {
it('should trigger callback on new event', () => {
const mockCallback = jest.fn();
render();
// Trigger new behavior
fireEvent.click(screen.getByRole('button'));
expect(mockCallback).toHaveBeenCalledWith(/* new signature */);
});
it('should not trigger old callback', () => {
// Ensure old behavior is removed
const mockOldCallback = jest.fn();
// @ts-expect-error - old callback should not exist
render();
});
});
```
## Common Testing Pitfalls
### 1. Incomplete Test Updates
- **Problem:** Tests still use old API but pass due to lenient typing
- **Solution:** Enable strict TypeScript checking in tests
- **Validation:** `yarn workspace @vibe/core test --typecheck`
### 2. Missing Integration Tests
- **Problem:** Individual components work but integration breaks
- **Solution:** Test component composition scenarios
- **Validation:** Create test cases for typical usage patterns
### 3. Snapshot Pollution
- **Problem:** Snapshots updated but changes not reviewed
- **Solution:** Review snapshot diffs before committing
- **Validation:** `git diff --no-index old-snapshot new-snapshot`
### 4. Performance Regressions
- **Problem:** New API performs worse than old API
- **Solution:** Add performance benchmarks for critical components
- **Validation:** Use React DevTools Profiler
## Automated Validation Scripts
### Pre-commit Validation
```bash
#!/bin/bash
# scripts/validate-breaking-change.sh
echo "🔍 Validating breaking change implementation..."
# 1. Check TypeScript compilation
echo "Checking TypeScript..."
lerna run type-check || exit 1
# 2. Run tests
echo "Running tests..."
lerna run test || exit 1
# 3. Check for old API usage
echo "Checking for old API usage..."
if grep -r "oldProp" packages/ --include="*.ts" --include="*.tsx"; then
echo "❌ Found old API usage"
exit 1
fi
# 4. Build validation
echo "Validating build..."
lerna run build || exit 1
echo "✅ Breaking change validation passed"
```
================================================
FILE: .claude/skills/vibe-breaking-change/references/workflow-checklist.md
================================================
# Complete Breaking Change Workflow Checklist
## Pre-Implementation Phase
### Planning and Analysis
- [ ] Breaking change request approved by team
- [ ] Impact analysis completed
- [ ] Alternative solutions considered and ruled out
- [ ] Migration strategy defined
- [ ] Timeline established
### Dependency Mapping
- [ ] Direct dependencies identified
- [ ] Indirect dependencies mapped
- [ ] Cross-package impacts assessed
- [ ] Test dependencies catalogued
- [ ] Documentation dependencies noted
## Implementation Phase
### Code Changes
- [ ] Target component updated with breaking change
- [ ] TypeScript interfaces updated
- [ ] Component logic implements new API
- [ ] Old API removed or deprecated appropriately
- [ ] Component constants updated
### Dependent Components
- [ ] Direct dependents updated
- [ ] Prop passing updated
- [ ] Event handling updated
- [ ] Composition patterns maintained
- [ ] Integration points verified
### Type System
- [ ] Component props interfaces updated
- [ ] Export types updated
- [ ] Enum to string union conversions (if applicable)
- [ ] Generic constraints updated
- [ ] Utility type helpers updated
## Testing Phase
### Unit Testing
- [ ] Component tests updated for new API
- [ ] Old API tests removed
- [ ] Edge cases covered
- [ ] Error conditions tested
- [ ] Accessibility tests updated
### Integration Testing
- [ ] Dependent component tests updated
- [ ] Cross-package integration tested
- [ ] Composition scenarios tested
- [ ] Event flow tested
- [ ] State management tested
### Build and Compilation
- [ ] TypeScript compilation passes
- [ ] All packages build successfully
- [ ] No circular dependencies
- [ ] Import/export consistency verified
- [ ] Bundle size impact assessed
### Quality Assurance
- [ ] Linting passes
- [ ] Style linting passes
- [ ] Prettier formatting applied
- [ ] No console errors/warnings
- [ ] Performance benchmarks within acceptable range
## Documentation Phase
### Migration Guide
- [ ] Clear before/after examples
- [ ] Migration strategies documented
- [ ] Common issues addressed
- [ ] Validation steps provided
- [ ] Timeline for migration suggested
### API Documentation
- [ ] Component API docs updated
- [ ] Props documentation current
- [ ] Examples reflect new API
- [ ] TypeScript signatures accurate
- [ ] Deprecation notices removed
### Changelog
- [ ] Breaking change clearly flagged
- [ ] Version bump appropriate
- [ ] Impact description clear
- [ ] Migration instructions included
- [ ] Related issues referenced
## Codemod Phase (If Applicable)
### Codemod Development
- [ ] Transform logic implements correctly
- [ ] Edge cases handled
- [ ] TypeScript compatibility maintained
- [ ] JSX patterns covered
- [ ] Test cases comprehensive
### Codemod Testing
- [ ] Dry run on real examples
- [ ] Before/after comparison reviewed
- [ ] Complex scenarios tested
- [ ] Error handling validated
- [ ] Performance acceptable
### Codemod Integration
- [ ] Added to codemod CLI
- [ ] Documentation written
- [ ] Usage examples provided
- [ ] Integration tests added
- [ ] Release notes updated
## Validation Phase
### Comprehensive Testing
- [ ] Full test suite passes
- [ ] Storybook stories render correctly
- [ ] Example applications work
- [ ] Performance tests pass
- [ ] Accessibility tests pass
### Build Verification
- [ ] All packages build
- [ ] Linting checks pass
- [ ] Type checking passes
- [ ] Bundle analysis clean
- [ ] No build warnings
### Manual Testing
- [ ] Component behavior verified in browser
- [ ] Responsive behavior tested
- [ ] Keyboard navigation tested
- [ ] Screen reader compatibility verified
- [ ] Cross-browser testing completed
## Release Phase
### Branch and Commit
- [ ] Feature branch created from vibe4
- [ ] Meaningful commit messages
- [ ] Breaking change flagged in commit
- [ ] Co-authored appropriately
- [ ] Commit history clean
### Pull Request
- [ ] PR title follows convention
- [ ] Description comprehensive and clear
- [ ] Breaking changes highlighted
- [ ] Migration instructions included
- [ ] Task/issue linked
- [ ] Reviewers assigned
### Code Review
- [ ] API design reviewed
- [ ] Implementation reviewed
- [ ] Test coverage reviewed
- [ ] Documentation reviewed
- [ ] Migration path validated
### CI/CD Pipeline
- [ ] All automated tests pass
- [ ] Build artifacts generated
- [ ] Quality gates passed
- [ ] Security scans clean
- [ ] Performance benchmarks acceptable
## Post-Implementation Phase
### Merge and Deploy
- [ ] PR approved and merged
- [ ] Branch cleaned up
- [ ] Version tagged appropriately
- [ ] Release notes published
- [ ] Team notified of changes
### Monitoring
- [ ] Error rates monitored
- [ ] Performance impact tracked
- [ ] User feedback collected
- [ ] Migration adoption tracked
- [ ] Support requests handled
### Follow-up Actions
- [ ] Migration deadline set and communicated
- [ ] Deprecated API removal scheduled
- [ ] Backward compatibility plan finalized
- [ ] Success metrics defined
- [ ] Lessons learned documented
================================================
FILE: .cursor/rules/accessibility-guidelines.mdc
================================================
---
description: Provides general web accessibility guidelines for developing components in the `@vibe/core` library (under `packages/core/`). This rule covers adherence to WCAG, use of semantic HTML, correct ARIA attribute application, ensuring keyboard navigability, proper focus management, and context-appropriate labeling for form inputs within components.
globs:
alwaysApply: false
---
# Web Accessibility Guidelines
This document outlines general web accessibility guidelines to ensure our components are usable by everyone, including people with disabilities. Adherence to these guidelines is crucial for creating inclusive digital experiences. This document is only relevant for the "@vibe/core" library, which is at "packages/core" directory.
## 1. WCAG Compliance
- **Follow WCAG:** Always strive to meet the Web Content Accessibility Guidelines (WCAG) at the AA level. Refer to the latest official WCAG documentation known to you for specific success criteria and techniques.
## 2. Semantic HTML
- **Use HTML Semantically:** Utilize HTML elements according to their intended purpose. For example, use `
),
description: "Use banners for system messages, background processes, and general updates."
},
negative: {
component: (
),
description: (
<>
Don’t use banners for notifying a user of an action they have taken. Instead, provide visual feedback with a{" "}
Toast.
>
)
}
},
{
positive: {
component: (
Try again
),
description: "If two actions are needed, use two different call to acitons."
},
negative: {
component: (
Learn moreTry again
),
description: "Don't include more than one action in an alert banner with the same type."
}
},
{
positive: {
component: (
),
description: "Use only the 4 color types: primary, negative, positive, and inverted."
},
negative: {
component: (
),
description: "Don’t choose other colors for alert banners. Keep it consistent."
}
}
]}
/>
## Use cases and examples
### Alert banner as an announcement
Use when you'd like to notify about an event or cross-company announcment.
### Alert banner as an opportunity to upgrade
Use to show a trial user the number of remaining free days to use the platform.
### Overflow text
In case that there's not enough space for the content, use an ellipses (...).
## Related components
================================================
FILE: packages/docs/src/pages/components/AlertBanner/AlertBanner.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipWhenToUse = () => (
Alert banners should be reserved only for high-signal, system-level alert messages. For in-app notifications use a{" "}
Toast.
);
================================================
FILE: packages/docs/src/pages/components/AlertBanner/AlertBanner.stories.tsx
================================================
import React from "react";
import {
AlertBanner,
type AlertBannerProps,
AlertBannerText,
AlertBannerLink,
AlertBannerButton,
Flex
} from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { type Meta, type StoryObj } from "@storybook/react";
const metaSettings = createStoryMetaSettingsDecorator({
component: AlertBanner,
actionPropsArray: ["onClose"]
});
const alertBannerTemplate = (args: AlertBannerProps) => {
return (
);
};
export default {
title: "Components/AlertBanner",
component: AlertBanner,
subcomponents: {
AlertBannerText,
AlertBannerLink,
AlertBannerButton
},
argTypes: {
...metaSettings.argTypes
},
decorators: [
...metaSettings.decorators,
(Story: React.ComponentType) => (
)
]
} as Meta;
export const Overview = {
render: alertBannerTemplate.bind({}),
args: {
id: "overview-alert-banner",
"aria-label": "Overview alert banner"
},
name: "Overview"
};
type Story = StoryObj;
export const Types: Story = {
render: () => (
),
name: "Types"
};
export const AlertBannerWithButton: Story = {
render: () => (
Lorem Ipsum
),
name: "Alert Banner with button"
};
export const AlertBannerWithLink: Story = {
render: () => (
),
name: "Alert Banner with link"
};
export const AlertBannerAsAnAnnouncement: Story = {
render: () => (
),
name: "Alert banner as an announcement"
};
export const AlertBannerAsAnOpportunityToUpgrade: Story = {
render: () => (
{}}
closeButtonAriaLabel="Close upgrade banner"
>
),
name: "Alert banner as an opportunity to upgrade"
};
export const OverflowText: Story = {
render: () => (
),
name: "Overflow text",
decorators: [
(Story: React.ComponentType) => (
)
]
};
================================================
FILE: packages/docs/src/pages/components/AttentionBox/AttentionBox.mdx
================================================
import { Meta } from "@storybook/blocks";
import { ComponentRules, UsageGuidelines } from "vibe-storybook-components";
import * as AttentionBoxStories from "./AttentionBox.stories";
import { AttentionBox, Box, Heading, Text, Link } from "@vibe/core";
# AttentionBox
Attention box lets users know important information within content areas, as close as possible to the content it’s about. An optional smooth entrance animation can be used to enhance visibility.
## Overview
## Import path
```tsx
import { AttentionBox } from "@vibe/core";
```
## Props
## Usage
## Variants
### Types
There are five types of attention boxes: primary, neutral, positive, warning and negative.
{" "}
### Default
The default Attention Box presents content in multiple lines, with or without title. It can include a link, a button, or both.
### Compact
The compact Attention Box presents content in a single line. It can include a link, a button, or both. If the text exceeds the available space it display an ellipsis and tooltip, when necessary.
### Link and Button
Both compact and default Attention Box can present CTA. They can include a link, a button, both, or be displayed without any CTA if none is needed.
### Dismissible
The Attention Box may be configured to be dismissible or not, depending on the use case.
### Icon
Attention Box can include an icon to reinforce its purpose. Each type has a default icon, which you may replace or remove as needed.
## Do's and Don'ts
{}}
/>
),
description: "Provide a brief title, and explanation related to the title."
},
negative: {
component: (
),
description: "Don't use only a title to explain something."
}
},
{
positive: {
component: (
Inbox
Catch up on updates from all your boards. {} }}
/>
),
description:
"Use when you are speaking directly to a piece of content and the notification can be positioned close to the content."
},
negative: {
component: (
Inbox
Catch up on updates from all your boards.
),
description: (
<>
Don’t use when not to a specific piece of content. If the message applies at a system level, use{" "}
Alert Banner instead.
>
)
}
},
{
positive: {
component: (
),
description: "If link is needed, keep it aligned to the right."
},
negative: {
component: (
Need guidance?{" "}
),
description: "Don’t use the link as an inline action."
}
}
]}
/>
## Use cases and examples
### Attention box within layouts
The width of the Attention Box adapts to its content and layout, expanding to fill the container or area it belongs to.
### Attention box inside a dialog/combobox
Provides contextual and related information.
### Entry animation
The Attention box component consist of enter animation prop to increase user attention. It is highly recommended to use a delay before the attention box entry motion, once the page is fully loaded.
## Related components
================================================
FILE: packages/docs/src/pages/components/AttentionBox/AttentionBox.stories.tsx
================================================
import React, { useState, useCallback, useEffect } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Info, Invite } from "@vibe/icons";
import {
AttentionBox,
Button,
Flex,
Heading,
Icon,
Text,
DialogContentContainer,
Search,
Avatar,
Box,
Skeleton,
type AttentionBoxProps
} from "@vibe/core";
import person from "./assets/person.png";
import contentImage from "./assets/content-image.png";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: AttentionBox,
actionPropsArray: ["onClose"]
});
export default {
title: "Components/AttentionBox",
component: AttentionBox,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
} satisfies Meta;
export const Overview: Story = {
render: (args: AttentionBoxProps) => (
),
args: {
title: "Attention box title",
text: "This action will cause your team to lose access to the account until you use the correct SSO source.",
action: {
text: "Button",
onClick: () => {}
},
link: {
href: "#",
text: "Read more"
},
onClose: () => {}
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Types: Story = {
render: () => (
) : (
setVisible(true)}>Show AttentionBox
);
}
};
export const IconStory: Story = {
render: () => (
{} }}
link={{ href: "#", text: "Read more" }}
onClose={() => {}}
/>
{} }}
link={{ href: "#", text: "Read more" }}
onClose={() => {}}
/>
),
name: "Icon"
};
export const AttentionBoxWithLayouts: Story = {
render: () => (
Cross-Account Copier
Copy boards and dashboards to another account {}}
/>
),
parameters: {
docs: {
liveEdit: {
scope: { Info }
}
}
}
};
export const AttentionBoxInsideADialogCombobox: Story = {
render: () => {
return (
Suggested peopleJulia Martinez
(UX/UI Product Designer)
Invite new board member by email {}} />
);
},
parameters: {
docs: {
liveEdit: {
scope: { person, Invite }
}
}
}
};
export const EntryAnimation: Story = {
render: () => {
type Stage = "button" | "skeleton" | "content" | "attention";
const [stage, setStage] = useState("button");
const onClick = useCallback(() => {
setStage("skeleton");
setTimeout(() => {
setStage("content");
}, 2000);
}, []);
useEffect(() => {
if (stage === "content") {
setTimeout(() => {
setStage("attention");
}, 750);
}
}, [stage]);
const reset = useCallback(() => {
setStage("button");
}, []);
return (
{/* Button Stage */}
{stage === "button" && (
Entry animation
)}
{/* Skeleton Stage */}
{stage === "skeleton" && (
)}
{/* Content Stage */}
{(stage === "content" || stage === "attention") && (
{stage === "attention" && (
)}
{/* Main Content */}
Entry animation
{"Here's"} a sneak peek at how it works. The entry animation is an integral part of the experience we
provide. {"It's"} up to you to ensure that the surrounding layout shifts downward smoothly when the
Attention Box enters the view.
)}
);
},
parameters: {
docs: {
liveEdit: {
scope: { Info, contentImage }
}
}
}
};
================================================
FILE: packages/docs/src/pages/components/Avatar/Avatar.mdx
================================================
import { Avatar, AvatarGroup, Flex, Counter } from "@vibe/core";
import { Meta } from "@storybook/blocks";
import { ComponentRules, createComponentTemplate } from "vibe-storybook-components";
import person1 from "./assets/person1.png";
import person2 from "./assets/person2.png";
import person3 from "./assets/person3.png";
import maleIcon from "./assets/maleIcon.png";
import femaleIcon from "./assets/femaleIcon.png";
import { TipMultipleAvatarsTogether } from "./Avatar.stories.helpers";
import * as AvatarStories from "./Avatar.stories";
export const avatarTemplate = createComponentTemplate(Avatar);
# Avatar
Avatar is a graphical representation of a person through a profile picture, image, icon, or set of initials.
### Import
```js
import { Avatar } from "@vibe/core";
```
## Props
## Usage
## Accessibility
Always provide an ariaLabel prop to describe the person or entity represented by the avatar (e.g.,
"John Smith", "Design Team", "Guest User"). This label is automatically used as tooltip content.
>,
<>
Use the role prop when the avatar represents something other than a person (e.g.,{" "}
role="img" for team avatars, role="button" for clickable workspace avatars).
>,
<>
For clickable avatars, ensure the ariaLabel describes the action that will occur when clicked (e.g.,
"Open John Smith's profile", "View Design Team details").
>,
<>
Use ariaHidden prop appropriately when the avatar is purely decorative and provides no meaningful
information to screen readers.
>,
<>
For avatars with badges (status indicators), ensure the ariaLabel includes relevant status
information (e.g., "John Smith (Away)", "Design Team (3 members online)").
>
]}
/>
## Variants
### Size
Avatars appear in 4 sizes: XS, Small, Medium, and Large.
### Disabled
Use when a user is inactive in the system.
### Avatar with text
Use when a user’s image is not available, use their initials.
### Square avatar with icon or text
Use for non-person avatars, such as a workspace or team.
## Do’s and Don’ts
),
description: "Use consistent avatar sizes for common use cases to convey their purpose."
},
negative: {
component: (
),
description: "Avoid using a mix of avatar sizes that display together and create design imbalance."
}
},
{
positive: {
component: ,
description: "Use branded generic avatars when a user has not set their avatar image."
},
negative: {
component: ,
description: "Don’t make assumptions and use gendered placeholder avatars."
}
}
]}
/>
## Use cases and examples
### Avatar with right badge
Use to indicate the user’s permissions such as: Guest, owner.
### Avatar with left badge
Use to indicate the status of a user such as: Working from home, out of office etc.
### Avatar with tooltip
Use to display tooltip on onHover Avatar event.
### Clickable avatar
Use to fire actions on avatar click event.
### Multiple avatars
To group multiple Avatars together, use the AvatarGroup component.
## Related components
================================================
FILE: packages/docs/src/pages/components/Avatar/Avatar.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipMultipleAvatarsTogether = () => (
If you want to stack multiple avatars together, check out{" "}
AvatarGroup
{" "}
component
);
================================================
FILE: packages/docs/src/pages/components/Avatar/Avatar.stories.scss
================================================
.monday-storybook-avatar {
&_multiple {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 20px 10px 20px;
.multiple-avatar {
margin: -8px;
}
}
&_multiple-reverse {
display: flex;
align-items: flex-start;
.multiple-reverse-avatar {
margin: -5px;
&:first-child {
z-index: 2;
}
&:nth-child(2) {
z-index: 1;
}
}
}
}
================================================
FILE: packages/docs/src/pages/components/Avatar/Avatar.stories.tsx
================================================
import React from "react";
import { Avatar, AvatarGroup, Flex, Counter } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { createComponentTemplate, StoryDescription } from "vibe-storybook-components";
import guest from "./assets/guest.svg";
import home from "./assets/home.svg";
import minus from "./assets/minus.svg";
import owner from "./assets/owner.svg";
import person1 from "./assets/person1.png";
import person2 from "./assets/person2.png";
import person3 from "./assets/person3.png";
import { WhatsNew } from "@vibe/icons";
import { useCallback, useState } from "react";
import "./Avatar.stories.scss";
const metaSettings = createStoryMetaSettingsDecorator({
component: Avatar,
iconPropNamesArray: ["icon"],
actionPropsArray: ["onClick"]
});
const avatarTemplate = createComponentTemplate(Avatar);
const AvatarBadgePropsType = {
summary: "AvatarBadgeProps",
detail: `{
src?: string;
icon?: SubIcon;
tabIndex?: number;
className?: string;
size?: "xs" | "small" | "medium" | "large";
}`
};
export default {
title: "Components/Avatar",
component: Avatar,
argTypes: {
...metaSettings.argTypes,
bottomRightBadgeProps: {
control: "object",
table: {
type: AvatarBadgePropsType
}
},
bottomLeftBadgeProps: {
control: "object",
table: {
type: AvatarBadgePropsType
}
},
topLeftBadgeProps: {
control: "object",
table: {
type: AvatarBadgePropsType
}
},
topRightBadgeProps: {
control: "object",
table: {
type: AvatarBadgePropsType
}
}
},
parameters: {
docs: {
liveEdit: {
scope: { person1 }
}
}
}
};
export const Overview = {
render: avatarTemplate.bind({}),
name: "Overview",
args: {
id: "overview-avatar",
size: "large",
src: person1,
type: "img",
"aria-label": "Julia Martinez"
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Size = {
render: () => (
)
};
export const Disable = {
render: () => (
)
};
export const AvatarWithText = {
render: () => (
)
};
export const SquareAvatar = {
render: () => (
)
};
export const AvatarWithRightBadge = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { guest, owner }
}
}
}
};
export const AvatarWithLeftBadge = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { home, minus }
}
}
}
};
export const AvatarWithTooltip = {
render: () => (
Julia Martinez,
position: "bottom"
}}
aria-label={"Julia Martinez"}
/>
),
parameters: {
docs: {
liveEdit: {
scope: { StoryDescription }
}
}
}
};
export const ClickableAvatar = {
render: () => {
const [count, setCount] = useState(0);
const incrementCount = useCallback(() => {
setCount(prevState => prevState + 1);
}, []);
return (
);
}
};
export const MultipleAvatars = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { person2, person3 }
}
}
}
};
================================================
FILE: packages/docs/src/pages/components/AvatarGroup/AvatarGroup.mdx
================================================
import { Meta } from "@storybook/blocks";
import { UsageGuidelines } from "vibe-storybook-components";
import * as AvatarGroupStories from "./AvatarGroup.stories";
# AvatarGroup
Use this component if you need to stack avatars as a group.
### Import
```js
import { AvatarGroup } from "@vibe/core";
```
## Props
## Usage
Use tooltip component while hovering on the counter when
you need only to display the content
>,
<>
If clickable and navigable list is required on counter, use{" "}
Menu component
>
]}
/>
## Accessibility
Use the counterAriaLabel prop to provide a descriptive accessible name for the counter (e.g., "3
additional team members", "5 more participants", "2 hidden collaborators").
>,
<>
Ensure each Avatar in the group has a meaningful ariaLabel prop that describes the
person or entity (e.g., "John Smith", "Sarah Johnson", "Design Team").
>
]}
/>
## Variants
### Size
Avatar Group appears in 4 sizes: XS, Small, Medium, and Large.
### Color variants
You can use Light or Dark counter color to maintain visual hierarchy.
### Clickable vs. Hover
If avatars are clickable, they will be displayed via Menu and user will be able to navigate each additional item.
Otherwise, avatars will be displayed in a Tooltip with no item's navigation.
### Disabled
Use when the avatar group is inactive in the specific context.
### Max avatars shown
Choose the ammount of avatars you want to show
### Custom counter
You can pass `counterProps` to specify counter params.
### Grid tooltip
When tooltip text for additional avatars is not passed, extra avatars will be displayed in a grid mode.
### Virtualized list
Should be used only to display large amount of avatars in default counter tooltip
### Counter custom tooltip content
Counter tooltip props can be specified in order to render tooltip with custom content.
## Use cases and examples
### Last seen users
### Displaying teams
## Related components
================================================
FILE: packages/docs/src/pages/components/AvatarGroup/AvatarGroup.stories.tsx
================================================
import React from "react";
import { useCallback, useState } from "react";
import { StoryDescription } from "vibe-storybook-components";
import { type Meta, type StoryObj } from "@storybook/react";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import person1 from "../Avatar/assets/person1.png";
import person2 from "../Avatar/assets/person2.png";
import person3 from "../Avatar/assets/person3.png";
import person4 from "../Avatar/assets/person4.png";
import {
AvatarGroup,
type AvatarGroupProps,
Avatar,
Flex,
Slider,
Table,
TableHeader,
TableHeaderCell,
TableBody,
TableRow,
TableCell
} from "@vibe/core";
const metaSettings = createStoryMetaSettingsDecorator({
component: AvatarGroup
});
export default {
title: "Components/AvatarGroup",
component: AvatarGroup,
argTypes: {
...metaSettings.argTypes,
counterProps: {
control: "object",
table: {
type: {
summary: "AvatarGroupCounterVisualProps",
detail: `{
color?: "light" | "dark";
count?: number;
prefix?: string;
maxDigits?: number;
ariaLabelItemsName?: string;
noAnimation?: boolean;
dialogContainerSelector?: string;
}`
}
}
}
},
decorators: metaSettings.decorators,
parameters: {
docs: {
liveEdit: {
scope: { StoryDescription, person1, person2, person3, person4 }
}
}
}
} satisfies Meta;
type Story = StoryObj;
interface AvatarGroupTemplateProps extends AvatarGroupProps {
persons: Record<`person${1 | 2 | 3 | 4}`, string>;
}
const avatarGroupTemplate = ({ persons, ...args }: AvatarGroupTemplateProps) => {
return (
);
};
export const Overview: StoryObj = {
render: avatarGroupTemplate.bind({}),
args: {
persons: {
person1: person1,
person2: person2,
person3: person3,
person4: person4
}
},
argTypes: {
persons: { table: { disable: true } }
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Size: Story = {
render: () => (
)
};
export const ColorVariants: Story = {
render: () => (
)
};
export const MaxAvatarsToDisplay: Story = {
render: () => {
const [max, setMax] = useState(4);
return (
setMax(value as number)}
valueText={`${max}`}
/>
);
}
};
export const HoverVsClickable: Story = {
render: () => {
const getDummyAvatarsProps = useCallback((numItems: number) => {
const avatarsProps = [
{
type: "img",
src: person1,
"aria-label": "Julia Martinez"
},
{
type: "img",
src: person2,
"aria-label": "Sophia Johnson"
},
{
type: "img",
src: person3,
"aria-label": "Marco DiAngelo"
},
{
type: "img",
src: person4,
"aria-label": "Liam Caldwell"
}
];
const result = [];
for (let i = 0; i < numItems; i++) {
result.push(avatarsProps[i % avatarsProps.length]);
}
return result;
}, []);
return (
{getDummyAvatarsProps(14).map(avatarProps => (
))}
{getDummyAvatarsProps(14).map((avatarProps, index) => (
{}} id={String(index)} />
))}
);
}
};
export const Disabled: Story = {
render: () => {
return (
);
}
};
export const LastSeenUsers: Story = {
render: () => (
}
emptyState={}
>
Julia Martinez
julia@martinez.comDeveloper
)
};
================================================
FILE: packages/docs/src/pages/components/Badge/Badge.mdx
================================================
import { Badge, Link, Button, Avatar } from "@vibe/core";
import { Meta } from "@storybook/blocks";
import person from "../Avatar/assets/person1.png";
import { WhatsNew } from "@vibe/icons";
import * as BadgeStories from "./Badge.stories";
# Badge
Badge component is responsible for layout an indicator/counter on a child component
### Import
```js
import { Badge } from "@vibe/core";
```
## Props
## Variants
### States
Badge can be of type Indicator or type Counter
### Button
When using Badge on a Button element, use alignment of RECTANGULAR in order to attach it to the element
### Avatar
When using Badge on an Avatar element, use alignment of CIRCULAR in order to attach it to the element
### Inline elements
When using Badge on an inline element, use alignment of OUTSIDE in order to attach it to the element
## Do’s and Don’ts
),
description: "When using the badge on an inline component, apply OUTSIDE alignment to it"
},
negative: {
component: (
),
description: "Do not leave the indicator inside the element boundaries"
}
},
{
positive: {
component: (
What's new
),
description: "Choose a color that does not blend with the one of the child component"
},
negative: {
component: (
What's new
),
description: "Do not use a color that blends with the child component"
}
},
{
positive: {
component: (
),
description: "When using Indicator badge, anchor it at the top-right edge"
},
negative: {
component: (
),
description: "Do not place it on any other edge"
}
}
]}
/>
## Related components
================================================
FILE: packages/docs/src/pages/components/Badge/Badge.stories.tsx
================================================
import React from "react";
import { Badge, Flex, Button, Avatar, Link } from "@vibe/core";
import person from "../Avatar/assets/person1.png";
import { ExternalPage, WhatsNew } from "@vibe/icons";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { createComponentTemplate } from "vibe-storybook-components";
const metaSettings = createStoryMetaSettingsDecorator({
component: Badge,
ignoreControlsPropNamesArray: ["children"],
actionPropsArray: ["onMouseDown"]
});
const badgeTemplate = createComponentTemplate(Badge);
export default {
title: "Components/Badge",
component: Badge,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
export const Overview = {
render: badgeTemplate.bind({}),
name: "Overview",
args: {
id: "overview-badge",
children: (
{"What's new"}
)
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const States = {
render: () => (
Indicator
{"What's new"}
Counter
{"What's new"}
),
parameters: {
docs: {
liveEdit: {
scope: { WhatsNew }
}
}
},
name: "States"
};
export const ButtonStory = {
render: () => (
Button
),
parameters: {
docs: {
liveEdit: {
scope: { ExternalPage }
}
}
},
name: "Button"
};
export const AvatarStory = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { person }
}
}
},
name: "Avatar"
};
export const InlineElements = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { WhatsNew, ExternalPage }
}
}
},
name: "Inline elements"
};
================================================
FILE: packages/docs/src/pages/components/BaseInput/BaseInput.stories.tsx
================================================
import { createComponentTemplate } from "vibe-storybook-components";
import { BaseInput } from "@vibe/base";
import { type Meta, type StoryObj } from "@storybook/react";
type Story = StoryObj;
export default {
title: "Internal/BaseInput",
component: BaseInput,
tags: ["internal"]
} satisfies Meta;
const baseInputTemplate = createComponentTemplate(BaseInput);
export const Overview: Story = {
render: baseInputTemplate.bind({})
};
================================================
FILE: packages/docs/src/pages/components/BaseList/BaseList.stories.tsx
================================================
import React, { useState } from "react";
import { type Meta, type StoryObj } from "@storybook/react";
import { BaseList } from "../../../../../core/src/components/BaseList";
import BaseItem from "../../../../../core/src/components/BaseItem/BaseItem";
import { Email, Favorite, Settings, Person, Search } from "@vibe/icons";
type Story = StoryObj;
export default {
title: "Internal/BaseList",
component: BaseList,
tags: ["internal"]
} satisfies Meta;
export const Overview: Story = {
render: args => (
)
};
export const WithSelectedItem: Story = {
render: () => (
)
};
export const WithDisabledItems: Story = {
render: () => (
)
};
export const ScrollableList: Story = {
render: () => (
{Array.from({ length: 10 }, (_, i) => (
))}
)
};
export const WithFocusCallback: Story = {
render: function WithFocusCallbackExample() {
const [focusedIndex, setFocusedIndex] = useState(0);
return (
This list controls the details panel below (using aria-controls).
Details panel controlled by the list above
);
}
};
================================================
FILE: packages/docs/src/pages/components/Box/Box.mdx
================================================
import { Meta } from "@storybook/blocks";
import { UsageGuidelines } from "vibe-storybook-components";
import { TipBenefits } from "./Box.stories.helpers";
import * as BoxStories from "./Box.stories";
# Box
Box component is used as a wrapper component.
Its purpose is to help scaffold compositions while using Vibe's prop keys without writing new CSS. It can be used as a container for
atom based compositions, it can accept all Vibe style related props and have a semantic html tag.
### Import
```js
import { Box } from "@vibe/core";
```
## Props
## Usage
## Utility props
### BACKGROUNDS COLORS
### TEXT COLORS
### BORDER
### BORDER COLOR
### ROUNDED CORNERS
#### Size props
### SHADOW
### MARGIN
#### Size props
#### Property variations per each size:
```
margin
marginX (x axis: left, right)
marginY (y axis: top, bottom)
marginTop
marginEnd
marginBottom
marginStart
```
#### Aligning Auto and none
each property variation can hold additional values for:
```
AUTO
NONE
```
### PADDING
#### Size props
#### Property variations per each size:
```
padding
paddingX (x axis: left, right)
paddingY (y axis: top, bottom)
paddingTop
paddingEnd
paddingBottom
paddingStart
```
### Component Disabled
## Related components
================================================
FILE: packages/docs/src/pages/components/Box/Box.stories.helpers.tsx
================================================
import React from "react";
import { Tip } from "vibe-storybook-components";
export const TipBenefits = () => (
Easily add Vibe styles without adding css
);
================================================
FILE: packages/docs/src/pages/components/Box/Box.stories.tsx
================================================
import React from "react";
import { Box, type BoxProps, Divider } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { type StoryFn } from "@storybook/react";
const metaSettings = createStoryMetaSettingsDecorator({
component: Box
});
//TODO remove when remove the flex class from the stories
metaSettings.decorators = [
...(metaSettings.decorators || []),
(Story: StoryFn) => (
),
name: "With icon"
};
================================================
FILE: packages/docs/src/pages/components/BreadcrumbsBar/BreadcrumbsBar.mdx
================================================
import { Meta } from "@storybook/blocks";
import { BreadcrumbsBar, BreadcrumbItem, Avatar, Text, DialogContentContainer } from "@vibe/core";
import { Board, Folder, Group, Workspace } from "@vibe/icons";
import person3 from "./assets/person3.png";
import do1 from "./assets/do1.png";
import dont1 from "./assets/dont1.png";
import { TipCheckYourself } from "./BreadcrumbsBar.stories.helpers";
import * as BreadcrumbsBarStories from "./BreadcrumbsBar.stories";
import styles from "./BreadcrumbsBar.stories.module.scss";
# BreadcrumbsBar
Breadcrumbs allow users to keep track and maintain awareness of their location as they navigate through pages, folders, files, etc.
### Import
```js
import { BreadcrumbsBar } from "@vibe/core";
```
## Props
## Usage
## Accessibility
Use the type="navigation" prop when breadcrumbs are clickable for navigation. This automatically
enables keyboard navigation and proper button/link semantics.
>,
<>
For clickable breadcrumbs, ensure all items have descriptive text props that clearly indicate what
section or page they represent.
>,
<>
When using icons in breadcrumbs, ensure they are purely decorative and don't replace the descriptive text, as
screen readers will announce the text content.
>,
<>
Use the isCurrent prop on BreadcrumbItem to mark the current page. This ensures screen readers
understand the user's current location in the navigation hierarchy.
>
]}
/>
## Variants
### Text only
### With icons
### With Breadcrumb Menu
## Do’s and Don’ts
,
description:
"Show the current step as the last item when using overflow behavior, and use only if content is overflowing and not as an aesthetic action."
},
negative: {
component: ,
description:
"overflow on the last item causes disorientation. Moreover using overflow on individual items prevent the user of essential information unnecessarily.."
}
},
{
positive: {
component: (
),
description: "If there’s a need to insert an icon, use for all items."
},
negative: {
component: (
),
description: "Don’t use icons if not applied to all steps."
}
},
{
positive: {
component: (
Rotem Dekel
),
description: "Use breadcrumbs when there is more than two levels of hierarchy."
},
negative: {
component: (
Rotem Dekel
),
description: "Don’t use breadcrumbs when there is only one level of navigation or hierachy."
}
}
]}
/>
## Use cases and examples
### Navigatable breadcrumbs
Use when breadcrumbs are clickable and are used for information and navigation
## Related components
================================================
FILE: packages/docs/src/pages/components/BreadcrumbsBar/BreadcrumbsBar.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipCheckYourself = () => (
If you are taking users through a multistep process, use the{" "}
MultiStepIndicator
{" "}
component instead.
);
================================================
FILE: packages/docs/src/pages/components/BreadcrumbsBar/BreadcrumbsBar.stories.module.scss
================================================
.list ol {
padding-inline-start: 0;
padding: 0;
margin: 0;
}
================================================
FILE: packages/docs/src/pages/components/BreadcrumbsBar/BreadcrumbsBar.stories.tsx
================================================
import React from "react";
import {
BreadcrumbsBar,
type BreadcrumbBarProps,
Avatar,
Flex,
Text,
BreadcrumbMenu,
BreadcrumbMenuItem,
BreadcrumbItem,
type BreadcrumbItemProps
} from "@vibe/core";
import { Board, Folder, Group, Workspace, Item } from "@vibe/icons";
import person3 from "./assets/person3.png";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import styles from "./BreadcrumbsBar.stories.module.scss";
const metaSettings = createStoryMetaSettingsDecorator({
component: BreadcrumbsBar
});
export default {
title: "Components/BreadcrumbsBar/BreadcrumbsBar",
component: BreadcrumbsBar,
argTypes: {
...metaSettings.argTypes,
children: {
description: "Breadcrumb item, each containing text and an optional icon, or a BreadcrumbMenu",
control: "object",
table: {
type: {
summary: "(BreadcrumbItemProps | BreadcrumbMenuProps)[]",
detail: `{
text: string;
icon?: React.ReactNode;
children?: BreadcrumbMenuItemProps[];
}[]`
},
defaultValue: {
summary: `[ { text: "Workspace", icon: } ]`
}
}
}
},
decorators: metaSettings.decorators,
parameters: {
docs: {
transformSource: (src: string) => {
return src.replace(/icon:\s*function[^{]+\{[^}]+\}/g, "icon: ");
},
liveEdit: {
scope: { Board, Group, Item }
}
}
}
};
export const Overview = {
render: (args: BreadcrumbBarProps) => (
{args.children &&
(args.children as BreadcrumbItemProps[]).map(item => (
))}
),
args: {
children: [
{
text: "Workspace",
icon: Workspace
},
{
text: "Folder",
icon: Folder
},
{
text: "Board",
icon: Board
},
{
text: "Group",
icon: Group
}
]
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const TextOnly = {
render: () => (
),
name: "Text only"
};
export const WithIcons = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { Workspace, Folder }
}
}
},
name: "With icons"
};
export const NavigatableBreadcrumbs = {
render: () => (
Rotem Dekel
),
parameters: {
docs: {
liveEdit: {
scope: { styles, person3 }
}
}
},
name: "Navigatable breadcrumbs"
};
export const WithBreadcrumbMenu = {
render: () => (
alert("Item 1 clicked")} />
)
};
================================================
FILE: packages/docs/src/pages/components/Button/Button.mdx
================================================
import { Button } from "@vibe/core";
import { Meta } from "@storybook/blocks";
import { TipIconButton } from "./Button.stories.helpers";
import * as ButtonStories from "./Button.stories";
# Button
Buttons allow users to trigger an action or event with a single click.
For example, you can use a button for allowing the functionality of submitting a form, opening a dialog, canceling an action,
or performing a delete operation.
### Import
```js
import { Button } from "@vibe/core";
```
## Props
## Usage
## Accessibility
Using an id is recommended for all instances to ensure proper label association.
>,
<>
Always provide descriptive text content for buttons, or use ariaLabel prop when buttons contain only
icons.
>,
<>
Use ariaLabel prop when you need to provide a custom accessible name that differs from the visible
button text or for icon-only buttons.
>,
<>
Use ariaLabeledBy prop when an external element provides the accessible name for the button.
>,
<>
Use ariaHasPopup prop when the button triggers a popup menu or dialog.
>,
<>
Use ariaExpanded prop to indicate when a popup triggered by the button is open or closed.
>,
<>
Use ariaControls prop to link the button to the element it controls.
>,
<>
Use aria-describedby prop when additional descriptive text is needed for the button.
>,
<>
Use aria-pressed prop for toggle buttons to indicate their current pressed state.
>
]}
/>
## Variants
### Button kinds
There can be more than one button in a screen, but to create the hierarchy of actions we need to use button kinds.
### Sizes
### Disabled
Set disable button for something that isn’t usable. Use a tooltip on hover in order to indicate the reason of the disabled action.
### States
### Positive and Negative
### Icons
## Do’s and Don’ts
Get started,
description: "Use 1 or 2 words, no longer than 4 words, with fewer than 20 characters including spaces."
},
negative: {
component: Get started and enjoy discount!,
description: "Don’t use punctuation marks such as periods or exclamation points."
}
},
{
positive: {
component: Get started,
description: "Use sentence-case capitalization."
},
negative: {
component: [Get Started, GET STARTED],
description: "Don’t use title case captalization or all caps."
}
},
{
positive: {
component: [Cancel, Get started],
description: "Use primary button as the main action , put the tertiary as the second option."
},
negative: {
component: [Get started, Cancel],
description: "Use primary button next to secondary."
}
},
{
positive: {
component: [Cancel, Get started],
description: "Use active verbs or phrases that clearly indicate action."
},
negative: {
component: [Yes, No],
description: "Use vague and generic labels that make the user read the dialog before taking action."
}
}
]}
/>
## Use cases and examples
### Loading state
### Success state
### On color state
### Adjacent buttons
## Related components
================================================
FILE: packages/docs/src/pages/components/Button/Button.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipIconButton = () => (
If you need to use icon as button with no text, check out{" "}
IconButton
{" "}
component
);
================================================
FILE: packages/docs/src/pages/components/Button/Button.stories.tsx
================================================
import React from "react";
import { useCallback, useState } from "react";
import { createComponentTemplate } from "vibe-storybook-components";
import { Add, Calendar, Check, Remove } from "@vibe/icons";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { Button, Text } from "@vibe/core";
import { type Meta, type StoryObj } from "@storybook/react";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: Button,
iconPropNamesArray: ["leftIcon", "rightIcon", "successIcon"],
actionPropsArray: ["onClick"]
});
export default {
title: "Components/Button",
component: Button,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
} satisfies Meta;
const buttonTemplate = createComponentTemplate(Button);
export const Overview: Story = {
render: buttonTemplate.bind({}),
args: {
id: "overview-button",
"aria-label": "Button",
children: "Button"
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const ButtonKinds: Story = {
render: () => (
<>
Primary
Secondary
Tertiary
>
),
name: "Button kinds"
};
export const Sizes: Story = {
render: () => (
<>
Large
Medium
Small
>
)
};
export const Disabled: Story = {
render: () => (
<>
Primary
Secondary
Tertiary
>
)
};
export const States: Story = {
render: () => (
<>
Regular
Active
>
)
};
export const PositiveAndNegative: Story = {
render: () => (
<>
Positive
Negative
>
),
name: "Positive and Negative"
};
export const Icons: Story = {
render: () => (
<>
Right icon
Left icon
>
),
parameters: {
docs: {
liveEdit: {
scope: { Calendar }
}
}
}
};
export const LoadingState: Story = {
render: () => {
const [loading, setLoading] = useState(false);
const onClick = useCallback(() => {
setLoading(true);
}, [setLoading]);
return (
Click here for loading
);
},
name: "Loading state"
};
export const SuccessState: Story = {
render: () => {
const [success, setSuccess] = useState(false);
const onClick = useCallback(() => {
setSuccess(true);
}, [setSuccess]);
return (
Click here for success
);
},
parameters: {
docs: {
liveEdit: {
scope: { Check }
}
}
},
name: "Success state"
};
export const OnColorStates: Story = {
render: () => (
<>
Regular
Primary on color
Secondary on color
Tertiary on color
Disabled
Primary on color
Secondary on color
Tertiary on color
>
),
name: "On color states"
};
export const AdjacentButtons: Story = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { Remove, Add }
}
}
},
name: "Adjacent buttons"
};
================================================
FILE: packages/docs/src/pages/components/ButtonGroup/ButtonGroup.mdx
================================================
import { Meta } from "@storybook/blocks";
import { ButtonGroup, Flex } from "@vibe/core";
import { Mobile } from "@vibe/icons";
import Desktop from "./assets/Desktop";
import { TipCheckYourself } from "./ButtonGroup.stories.helpers";
import * as ButtonGroupStories from "./ButtonGroup.stories";
# ButtonGroup
ButtonGroup can be used to group related options. To emphasize groups of related toggle buttons, a group should share a common container.
### Import
```js
import { ButtonGroup } from "@vibe/core";
```
## Props
## Usage
Keep the content short and actionable. For longer actions, use the{" "}
Dropdown component.
>,
<>
Button group is used to display the same content in different view, if you need to present different content, use{" "}
Tabs.
>
]}
/>
## Accessibility
Always provide a groupAriaLabel prop to describe the purpose of the button group. This provides
context for screen readers about what the group represents (e.g., "View options", "Sort options", "Display
settings").
>,
<>
For individual buttons, ensure each option has clear, descriptive text content, or provide
ariaLabel in the options array for icon-only buttons.
>,
<>
Use ariaLabel in the options array when you need a different accessible name than the visible button
text, or for icon-only buttons (e.g., ariaLabel: "Grid view").
>
]}
/>
## Variants
### Default
### Tertiary
### Disabled - all buttons
### Disabled - single button
### Size
## Do’s and Don’ts
),
description: "Use when all the actions in the group take place immediately in one click."
},
negative: {
component: (
),
description: (
<>
Don’t use a button group when switching between content or object pages. Use{" "}
Tabs instead.
>
)
}
},
{
positive: {
component: (
),
description: "Always use buttons with a consistent style: Icons, text or icon with text."
},
negative: {
component: (
),
description: "Avoid combining text buttons with icon buttons within the same button group."
}
},
{
positive: {
component: (
),
description: "Keep button copy width as simillar as possible."
},
negative: {
component: (
),
description: "Avoid mixing long button copy and short copy."
}
}
]}
/>
## Use cases and examples
### Button group in settings
For example: on the views settings you can choose only one option.
### Button group as toggle
Use button group as toggle for change the view between two states.
For on and off actions, use the Toggle component.
### Full width button group
Use this option in order for the ButtonGroup to fill the entire width of its container
This feature is intended to be used within small containers like a side panel, menu or similar small floating elements
## Related components
================================================
FILE: packages/docs/src/pages/components/ButtonGroup/ButtonGroup.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipCheckYourself = () => (
Button group will always have one button selected. If you need to display adjacent buttons without selected mode,
use the{" "}
Button
{` component with "Flat" props.`}
);
================================================
FILE: packages/docs/src/pages/components/ButtonGroup/ButtonGroup.stories.tsx
================================================
import React from "react";
import { ButtonGroup, Flex, Text } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { createComponentTemplate } from "vibe-storybook-components";
import { type Meta, type StoryObj } from "@storybook/react";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: ButtonGroup,
actionPropsArray: ["onSelect"]
});
const buttonGroupTemplate = createComponentTemplate(ButtonGroup);
export default {
title: "Components/ButtonGroup",
component: ButtonGroup,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
} satisfies Meta;
export const Overview: Story = {
render: buttonGroupTemplate.bind({}),
args: {
id: "overview-button-group",
groupAriaLabel: "Overview button group",
options: [
{
value: 1,
text: "Alpha"
},
{
value: 2,
text: "Beta"
},
{
value: 3,
text: "Gamma"
},
{
value: 4,
text: "Delta"
}
],
value: 1
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Default: Story = {
render: () => (
)
};
export const Tertiary: Story = {
render: () => (
)
};
export const Disabled: Story = {
render: () => (
)
};
export const DisabledSingeButton: Story = {
render: () => (
),
name: "Disabled - Singe Button"
};
export const Size: Story = {
render: () => (
MediumSmall
)
};
export const ButtonGroupInSettings: Story = {
render: () => (
Function
),
name: "Button group in settings"
};
export const ButtonGroupAsToggle: Story = {
render: () => (
),
name: "Button group as toggle"
};
export const FullWidthButtonGroup: Story = {
render: () => (
),
name: "Full width button group"
};
================================================
FILE: packages/docs/src/pages/components/ButtonGroup/assets/Desktop.tsx
================================================
/* eslint-disable */
/* tslint:disable */
import PropTypes from "prop-types";
import React from "react";
const Desktop = ({ size = "20", ...props }: { size: string }) => (
);
Desktop.displayName = "Desktop";
Desktop.propTypes = {
size: PropTypes.string
};
export default Desktop;
/* tslint:enable */
/* eslint-enable */
================================================
FILE: packages/docs/src/pages/components/Checkbox/Checkbox.mdx
================================================
import { Meta } from "@storybook/blocks";
import { Checkbox, DialogContentContainer } from "@vibe/core";
import { createComponentTemplate, Link } from "vibe-storybook-components";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { TipAmIUsingTheRightComponent } from "./Checkbox.stories.helpers";
import "./Checkbox.stories.scss";
import * as CheckboxStories from "./Checkbox.stories";
export const metaSettings = createStoryMetaSettingsDecorator({ component: Checkbox });
export const checkboxTemplate = createComponentTemplate(Checkbox);
# Checkbox
Checkboxes allow users to select one or more items from a set of options.
### Import
```js
import { Checkbox } from "@vibe/core";
```
## Props
## Usage
Use checkboxes to:1. Select one or more options from a list2. Turn an item on or off in a desktop environment
,
"Use checkboxes independently from each other: selecting one checkbox shouldn’t change the selection status of another checkbox in the list. The exception is when a checkbox is used to make a bulk selection.",
"Ensure both label and input are clickable to select the checkbox field. ",
"Keep a positive tone of voice. For example: “Turn on notifications” instead of “Turn off notifications”.",
"Checkboxes should be listed according to a logical order.",
"Place checkboxes vertically, using 16px spacing.",
"Checkbox will always appear with a label."
]}
/>
## Accessibility
Using an id is highly recommended for all instances to ensure proper label association.
>,
<>
Always provide a visible label prop to ensure the checkbox's purpose is clear to all users.
>,
<>
It is recommended to use separateLabel mode for better screen reader support as it provides clearer,
more explicit label-input associations. When using this mode, the id prop is required for proper
label association.
>,
<>
Use ariaLabel prop when you need to provide a custom accessible name.
>,
<>
Use ariaLabelledBy prop when the checkbox is described by external elements.
>,
<>
Use indeterminate prop for mixed selection states (e.g., when some but not all sub-items are
selected).
>,
<>
Use tabIndex prop for custom keyboard navigation order.
>,
<>
Use autoFocus prop when the checkbox should receive initial focus for keyboard navigation.
>
]}
/>
## Variants
### States
Has 4 states: regular, hover, selected, and disabled.
## Do’s and Don’ts
),
description: "Use checkboxes when one or more items can be selected."
},
negative: {
component: (
),
description: (
<>
Don't use checkboxes when only one item can be selected from a list. Use{" "}
radio buttons instead.
>
)
}
},
{
positive: {
component: (
),
description: "Use the checkbox label’s prop to describe the option purpse."
},
negative: {
component: [
,
Option
],
description: "Don’t use a separte div which is not related to the Checkbox component."
}
},
{
positive: {
component: (
),
description: "Place the checkbox on the left side of the label."
},
negative: {
component: (
Item 1
Item 2
Item 3
),
description: "Don’t change the position of the checkbox. Keep it to the left side of the label."
}
},
{
positive: {
component: (
),
description: "Always keep a positive tone of voice."
},
negative: {
component: (
),
description: "Avoid negative language that makes the user check the box in order for something not to happen."
}
}
]}
/>
## Use cases and examples
### Single checkbox
Allows the user to choose a single option. For example: accept terms of use.
## Related components
================================================
FILE: packages/docs/src/pages/components/Checkbox/Checkbox.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipAmIUsingTheRightComponent = () => (
For settings that are immediately applied, consider using a{" "}
toggle switch
{" "}
instead.
);
================================================
FILE: packages/docs/src/pages/components/Checkbox/Checkbox.stories.scss
================================================
.monday-storybook-checkbox {
&_border {
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed var(--sb-ui-border-color);
padding: 0 8px;
height: 38px;
margin-right: 3px;
&:nth-child(2) {
margin-left: 11px;
font-size: 14px;
line-height: 22px;
}
}
&_wrapper {
padding: 14px 16px;
width: 240px;
display: flex;
flex-direction: column;
row-gap: 16px;
}
&_inline-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
&_minus-margin {
margin-right: -8px;
}
&_list {
display: flex;
flex-direction: column;
}
&_list-item {
display: inline-block;
width: 100%;
}
&_link {
display: initial;
}
}
================================================
FILE: packages/docs/src/pages/components/Checkbox/Checkbox.stories.tsx
================================================
import React from "react";
import { createComponentTemplate } from "vibe-storybook-components";
import { Link, Checkbox } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import "./Checkbox.stories.scss";
import { type Meta, type StoryObj } from "@storybook/react";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: Checkbox,
actionPropsArray: ["onChange"]
});
const checkboxTemplate = createComponentTemplate(Checkbox);
export default {
title: "Components/Checkbox",
component: Checkbox,
decorators: metaSettings.decorators,
argTypes: metaSettings.argTypes
} satisfies Meta;
export const Overview: Story = {
render: checkboxTemplate.bind({}),
args: {
label: "Option",
defaultChecked: true,
id: "checkbox-1",
"aria-label": "Checkbox option"
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const States: Story = {
render: () => (
<>
>
)
};
export const SingleCheckbox: Story = {
render: () => (
I agree to the and{" "}
.
>
}
/>
),
name: "Single checkbox"
};
================================================
FILE: packages/docs/src/pages/components/Chips/Chips.mdx
================================================
import { Meta } from "@storybook/blocks";
import { Chips } from "@vibe/core";
import { TipLabel } from "./Chips.stories.helpers";
import * as ChipsStories from "./Chips.stories";
# Chip
Chips are compact elements that represent an input, attribute, or action. They may display text, icons, or both.
### Import
```js
import { Chips } from "@vibe/core";
```
## Props
## Usage
## Accessibility
Provide an id for the Chips to enable proper accessibility associations and unique identification.
>,
<>
For removable chips, use the closeButtonAriaLabel prop to provide a descriptive accessible name for
the remove button (defaults to "Remove", but should be more specific like "Remove John Smith" or "Delete tag").
>,
<>
Use the ariaLabel prop when the visible label text alone doesn't provide enough context
for screen readers to understand the chip's purpose or when chips contain complex content.
>,
<>
For clickable chips, ensure the ariaLabel describes the action that will occur when clicked.
>,
<>
When using icons or avatars in chips, ensure they are purely decorative and don't replace the descriptive text.
Screen readers will announce the text content, and icons should be hidden from assistive technologies.
>
]}
/>
## Variants
### With read only state
### With icons
### With avatars
### Themes
### Clickable chips
### Color coded chips
Use chips with colors to indicate different categories.
e.g.
```jsx
```
### Chips on colored backgrounds
When a chip appears on a background color identical to its color, use `showBorder` prop in order to add a distinctive white border.
## Do’s and Don’ts
,
description: "When using a chip, the width will automatically size itself to fit the content."
},
negative: {
component: ,
description: "Don’t change the chip width."
}
}
]}
/>
## Use cases and examples
### Colorful chips for different content
Sometimes when needed, chips can change fill color.
### Chips in a person picker combo box
Chips can be removable, and can be used in a multiple options selection use cases.
## Related components
================================================
FILE: packages/docs/src/pages/components/Chips/Chips.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipLabel = () => (
Chips will always show up in context of a field. If you just need to add information, use{" "}
Label.
);
================================================
FILE: packages/docs/src/pages/components/Chips/Chips.stories.tsx
================================================
import React from "react";
import { chunk as _chunk } from "es-toolkit";
import { Chips, Text, Avatar, DialogContentContainer, Flex, Search, ColorUtils } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { createComponentTemplate } from "vibe-storybook-components";
import { Email } from "@vibe/icons";
import person1 from "../Avatar/assets/person1.png";
import person3 from "../Avatar/assets/person3.png";
const metaSettings = createStoryMetaSettingsDecorator({
component: Chips,
iconPropNamesArray: ["rightIcon", "leftIcon"],
actionPropsArray: ["onDelete", "onMouseDown", "onClick"]
});
const chipsTemplate = createComponentTemplate(Chips);
export default {
title: "Components/Chips",
component: Chips,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
export const Overview = {
render: chipsTemplate.bind({}),
name: "Overview",
args: {
id: "overview-chip",
"aria-label": "Overview chip",
label: "This is a chip",
onMouseDown: () => {},
onClick: () => {}
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const ChipsWithReadOnlyState = {
render: () => ,
name: "Chips with read only state"
};
export const ChipsWithIcons = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { Email }
}
}
},
name: "Chips with icons"
};
export const ChipsWithAvatars = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { person1 }
}
}
},
name: "Chips with avatars"
};
export const Themes = {
render: () => (
<>
>
),
name: "Themes"
};
export const Clickable = {
render: () => {
return (
{}}
/>
{}}
/>
);
},
name: "Clickable"
};
export const ChipsPalette = {
render: () => {
const excludedColors = ["dark_indigo", "blackish"];
const stateColors = ["positive", "negative", "primary", "warning", "neutral"];
const allColors = [
...ColorUtils.contentColors.filter((c: string) => !excludedColors.includes(c)),
...stateColors
];
const allowedColorsChunks = _chunk(allColors, 7);
return (
{allowedColorsChunks.map((colorChunk: any) => {
return (
{colorChunk.map((colorName: any) => (
))}
);
})}
);
},
parameters: {
docs: {
liveEdit: {
scope: { _chunk, ColorUtils }
}
}
},
name: "Chips palette"
};
export const OnColor = {
render: () => (
),
name: "On color"
};
export const ColorfulChipsForDifferentContent = {
render: () => (
),
name: "Colorful chips for different content"
};
export const ChipsInAPersonPickerComboBox = {
render: () => (
Suggested people
May Kishon (UX/UI Product Designer)Liron Cohen(Customer Experience)Amanda Lawrence(Customer Experience Designer)Dor Yehuda(Customer Experience Designer)
),
parameters: {
docs: {
liveEdit: {
scope: { person1, person3 }
}
}
},
name: "Chips in a person picker combo box"
};
================================================
FILE: packages/docs/src/pages/components/Clickable/Clickable.interactions.ts
================================================
import { expect } from "@storybook/jest";
import {
getByLabelText,
pressNavigationKey,
interactionSuite,
NavigationCommand,
resetFocus
} from "@vibe/core/interactionsTests";
async function states_onClickTabFocusElementTest(canvas) {
const CLICKABLE_LABEL = "clickable button";
const CLICKABLE_DISABLED_LABEL = "disabled clickable button";
const clickableElement = await getByLabelText(canvas, CLICKABLE_LABEL);
const disabledClickableElement = await getByLabelText(canvas, CLICKABLE_DISABLED_LABEL);
await pressNavigationKey(NavigationCommand.TAB);
expect(document.activeElement).toEqual(clickableElement);
await pressNavigationKey(NavigationCommand.TAB);
expect(document.activeElement).not.toEqual(disabledClickableElement);
expect(canvas).not.toContain(document.activeElement);
}
export const statesPlaySuite = interactionSuite({
tests: [states_onClickTabFocusElementTest],
afterEach: async () => {
await resetFocus();
}
});
================================================
FILE: packages/docs/src/pages/components/Clickable/Clickable.mdx
================================================
import { Meta } from "@storybook/blocks";
import * as ClickableStories from "./Clickable.stories";
import { TipHookSolution } from "./Clickable.stories.helpers";
# Clickable
An accessibility helper component, this component simulates a button on non button elements
### Import
```js
import { Clickable } from "@vibe/core";
```
## Props
## Variants
### States
Clickable component supports two different states: regular state and disabled state.
The state only affects the component functionality (a user cannot interact with a disabled clickable component) and not the component appearance.
You can use the component className and style props to change the component appearance.
## Usage
## Related components
================================================
FILE: packages/docs/src/pages/components/Clickable/Clickable.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipHookSolution = () => (
{`If you'd like to set clickable functionality on a specific element inside your React component instead of using a
wrapper, please, take a look on our `}
useClickableProps
{" "}
hook.
);
================================================
FILE: packages/docs/src/pages/components/Clickable/Clickable.stories.tsx
================================================
import React from "react";
import { Clickable, type ClickableProps, Flex, Box } from "@vibe/core";
import { statesPlaySuite } from "./Clickable.interactions";
export default {
title: "Accessibility/Clickable",
component: Clickable
};
const clickableTemplate = (args: ClickableProps) => {
return (
alert("clicked")} {...args}>
I act like a button
);
};
export const Overview = {
render: clickableTemplate.bind({}),
name: "Overview",
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const States = {
render: () => (
alert("clicked")} aria-label="clickable button">
Regular clickable element
alert("clicked")} disabled aria-label="disabled clickable button">
Disabled clickable element
),
name: "States",
play: statesPlaySuite
};
================================================
FILE: packages/docs/src/pages/components/ColorPicker/ColorPicker.interactions.ts
================================================
import { userEvent, within } from "@storybook/test";
import { typeMultipleTimes, interactionSuite, resetFocus } from "@vibe/core/interactionsTests";
import { expect } from "@storybook/jest";
import { ComponentDefaultTestId, getTestId } from "@vibe/shared";
// Color names used in tests
const ContentColorByName = {
BRIGHT_GREEN: "bright-green",
STEEL: "steel",
DARK_PURPLE: "dark_purple",
INDIGO: "indigo"
};
async function selectAndResetWithKeyboard(canvas) {
await clickOnColor(canvas, ContentColorByName.BRIGHT_GREEN);
await expectColorToBeSelected(canvas, ContentColorByName.BRIGHT_GREEN);
//move to a color in the last row
await typeMultipleTimes("{arrowDown}", 7);
await expectColorToBeActive(canvas, ContentColorByName.STEEL);
//move to a color in the last row
await userEvent.keyboard(" ");
await expectColorToBeActive(canvas, ContentColorByName.STEEL);
await expectColorToBeSelected(canvas, ContentColorByName.STEEL);
await expectColorToBeNotSelected(canvas, ContentColorByName.BRIGHT_GREEN);
//move to the "Clear color" button
await userEvent.keyboard("{arrowDown}");
await expectColorToBeNotActive(canvas, ContentColorByName.STEEL);
await userEvent.keyboard("{Enter}");
await expectColorToBeNotSelected(canvas, ContentColorByName.STEEL);
}
export const noColorInteractionSuite = interactionSuite({
tests: [selectAndResetWithKeyboard],
afterEach: async () => {
await resetFocus();
}
});
async function selectMultiColorsWithKeyboardAndMouse(canvas) {
await clickOnColor(canvas, ContentColorByName.DARK_PURPLE);
await expectColorToBeSelected(canvas, ContentColorByName.DARK_PURPLE);
//move with keyboard to a different color
await typeMultipleTimes("{arrowRight}", 3);
await expectColorToBeActive(canvas, ContentColorByName.INDIGO);
//select this color as well
await userEvent.keyboard("{Enter}");
await expectColorToBeSelected(canvas, ContentColorByName.DARK_PURPLE);
await expectColorToBeSelected(canvas, ContentColorByName.INDIGO);
await expectColorToBeActive(canvas, ContentColorByName.INDIGO);
//cancel the selection of the first color
await clickOnColor(canvas, ContentColorByName.DARK_PURPLE);
await expectColorToBeNotSelected(canvas, ContentColorByName.DARK_PURPLE);
await expectColorToBeSelected(canvas, ContentColorByName.INDIGO);
//since we used the mouse, the "active" indicator should be removed
await expectColorToBeNotActive(canvas, ContentColorByName.DARK_PURPLE);
await expectColorToBeNotActive(canvas, ContentColorByName.INDIGO);
}
export const multiSelectionInteractionSuite = interactionSuite({
tests: [selectMultiColorsWithKeyboardAndMouse],
afterEach: async () => {
await resetFocus();
}
});
function formatColorName(color: string) {
return color.replace(/-|_/g, " ").replace(/(?:^|\s)\S/g, a => a.toUpperCase());
}
async function clickOnColor(canvas, color) {
const element = await findColorItem(canvas, color);
const toClick = await within(element).findByLabelText(formatColorName(color));
await userEvent.click(toClick);
}
async function expectColorToBeSelected(canvas, color) {
const element = await findColorItem(canvas, color);
expect(element.getAttribute("class")).toContain("selectedColor");
}
async function expectColorToBeNotSelected(canvas, color) {
const element = await findColorItem(canvas, color);
expect(element.getAttribute("class")).not.toContain("selectedColor");
}
async function expectColorToBeActive(canvas, color) {
const element = await findColorItem(canvas, color);
expect(element.getAttribute("class")).toContain("active");
}
async function expectColorToBeNotActive(canvas, color) {
const element = await findColorItem(canvas, color);
expect(element.getAttribute("class")).not.toContain("active");
}
async function findColorItem(canvas, color) {
return await canvas.findByTestId(getTestId(ComponentDefaultTestId.COLOR_PICKER_ITEM, color));
}
================================================
FILE: packages/docs/src/pages/components/ColorPicker/ColorPicker.mdx
================================================
import { Canvas, Meta } from "@storybook/blocks";
import * as ColorPickerStories from "./ColorPicker.stories";
# ColorPicker
ColorPicker is our component for selecting our content colors
### Import
```js
import { ColorPicker } from "@vibe/core";
```
## Props
## Variants
### With Indicator
### Text Indication
### Selected
### No color
Ability to revert to initial color
### Selected icon
Selected colors indication, when using multi-selection
### Shapes
The `ColorPicker` supports multiple shapes
## Related components
================================================
FILE: packages/docs/src/pages/components/ColorPicker/ColorPicker.stories.tsx
================================================
import { ColorPicker } from "@vibe/core";
import { TextColorIndicator, Check } from "@vibe/icons";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { multiSelectionInteractionSuite, noColorInteractionSuite } from "./ColorPicker.interactions";
import { createComponentTemplate } from "vibe-storybook-components";
const metaSettings = createStoryMetaSettingsDecorator({
component: ColorPicker,
iconPropNamesArray: ["ColorIndicatorIcon", "SelectedIndicatorIcon", "NoColorIcon"],
actionPropsArray: [{ name: "onSave", linkedToPropValue: "value" }]
});
export default {
title: "Components/ColorPicker",
component: ColorPicker,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
const colorPickerTemplate = createComponentTemplate(ColorPicker);
export const Overview = {
render: colorPickerTemplate.bind({}),
args: {
id: "overview-color-picker"
},
name: "Overview"
};
export const WithIndicator = {
render: colorPickerTemplate.bind({}),
args: {
id: "indicator-color-picker",
ColorIndicatorIcon: TextColorIndicator
},
name: "With Indicator"
};
export const TextIndication = {
render: colorPickerTemplate.bind({}),
args: {
id: "text-indication-color-picker",
ColorIndicatorIcon: TextColorIndicator,
value: "peach",
shouldRenderIndicatorWithoutBackground: true
},
name: "Text Indication"
};
export const Selected = {
render: colorPickerTemplate.bind({}),
args: {
id: "selected-color-picker",
ColorIndicatorIcon: TextColorIndicator,
colorStyle: "selected"
},
name: "Selected"
};
export const NoColor = {
render: colorPickerTemplate.bind({}),
args: {
id: "no-color-picker",
noColorText: "Clear color"
},
name: "No color",
play: noColorInteractionSuite
};
export const SelectedIcon = {
render: colorPickerTemplate.bind({}),
args: {
id: "multiselect-color-picker",
isMultiselect: true,
SelectedIndicatorIcon: Check,
value: "peach"
},
name: "Selected icon",
play: multiSelectionInteractionSuite
};
export const Shapes = {
render: colorPickerTemplate.bind({}),
args: {
id: "shapes-color-picker",
colorShape: "circle"
},
name: "Shapes"
};
================================================
FILE: packages/docs/src/pages/components/Combobox/Combobox.interactions.ts
================================================
import { expect } from "@storybook/jest";
import { queryByText, userEvent } from "@storybook/test";
import {
getByLabelText,
getByTestId,
getByText,
clickElement,
typeText,
interactionSuite,
pressNavigationKey,
NavigationCommand,
resetFocus
} from "@vibe/core/interactionsTests";
import { ComponentDefaultTestId, getTestId } from "@vibe/shared";
async function getComponentElements(canvas) {
const comboboxElement = getByTestId(canvas, ComponentDefaultTestId.COMBOBOX);
const searchElement = getByLabelText(comboboxElement, "Search for content");
return { comboboxElement, searchElement };
}
async function onTypeFilterComboboxOptionsTest(canvas) {
const { comboboxElement, searchElement } = await getComponentElements(canvas);
await typeText(searchElement, "jjj", 400);
expect(queryByText(comboboxElement, "Option 1")).toBeNull();
}
async function onSelectExistFilterClearsFilterTest(canvas) {
const { comboboxElement, searchElement } = await getComponentElements(canvas);
await typeText(searchElement, "jjj", 400);
const cleanSearchButton = getByTestId(
comboboxElement,
getTestId(ComponentDefaultTestId.CLEAN_SEARCH_BUTTON, "combobox-search")
);
await clickElement(cleanSearchButton);
expect(searchElement).toHaveValue("");
const option1 = getByText(comboboxElement, "Option 1");
expect(option1).toBeInTheDocument();
}
async function onNavigateBetweenOptionsByArrowsAriaUpdates(canvas) {
const { comboboxElement, searchElement } = await getComponentElements(canvas);
// Press on list for initial focus
await clickElement(searchElement);
// Navigate to first option with down arrow
await pressNavigationKey(NavigationCommand.DOWN_ARROW);
const option1 = getByText(comboboxElement, "Option 1").parentElement;
// Check active option by aria is the one we navigate to it by keyboard
let ariaActiveDescendant = searchElement.getAttribute("aria-activedescendant");
expect(ariaActiveDescendant).toEqual(option1.id);
// Navigate to second option with down arrow
await pressNavigationKey(NavigationCommand.DOWN_ARROW);
const option2 = getByText(comboboxElement, "Option 2").parentElement;
// Check active option by aria is the one we navigate to it by keyboard
ariaActiveDescendant = searchElement.getAttribute("aria-activedescendant");
expect(ariaActiveDescendant).toEqual(option2.id);
// Navigate to first option with up arrow
await pressNavigationKey(NavigationCommand.UP_ARROW);
// Check active option by aria is the one we navigate to it by keyboard
ariaActiveDescendant = searchElement.getAttribute("aria-activedescendant");
expect(ariaActiveDescendant).toEqual(option1.id);
}
export const defaultPlaySuite = interactionSuite({
tests: [
onNavigateBetweenOptionsByArrowsAriaUpdates,
onTypeFilterComboboxOptionsTest,
onSelectExistFilterClearsFilterTest
],
afterEach: async () => {
await resetFocus();
}
});
================================================
FILE: packages/docs/src/pages/components/Combobox/Combobox.mdx
================================================
import { Meta } from "@storybook/blocks";
import { Combobox, DialogContentContainer } from "@vibe/core";
import { TipOtherComponents } from "./Combobox.stories.helpers";
import * as ComboboxStories from "./Combobox.stories";
# Combobox
{" "}
See the
Combobox migration guide
for detailed migration instructions.
>
}
/>
Combobox allowing users to filter longer lists to only the selections matching a query.
### Import
```js
import { Combobox } from "@vibe/core";
```
## Props
## Usage
## Accessibility
Using an id is highly recommended for all instances to ensure proper label association.
>,
<>
Use searchInputAriaLabel prop when you need to provide a custom accessible name for the search input.
>,
<>
Use disabled prop appropriately to indicate when the combobox is not available for interaction.
>,
<>
Use autoFocus prop when the combobox should receive initial focus for keyboard navigation.
>
]}
/>
## Variants
### Default
Default Combobox can be used without dialog or as part of the layout.
### Combobox inside a dialog
Use this for Combobox that triggered by button.
### Sizes
We have three pre-defined sizes for Combobox width size: Small 200px, Medium 240px, Large 260px.
### With categories
When having a lot of options, you may use headings to categorize them.
### With icons
### With optionRenderer
### With Button
If Combobox requires action, use button component at the end of the list.
### With creation when no items are available
### With virtualization optimization
When you display a large number of options, you may want to render only the options shown at a given moment to allow better performance and a
better user experience.
### Loading state
If importing the Combobox options may take time, you reflect this to the user by using our Combobox loading mode.
## Do’s and Don’ts
),
description: "Use Combobox to make large lists easier to search."
},
negative: {
component: (
),
description: (
<>
Don’t use Combobox if you have less than 5 list items. If it's not complex enough for a Combobox, use a{" "}
Radio button or{" "}
Dropdown.
>
)
}
},
{
className: "combobox-stories-styles_big-figure",
positive: {
component: (
),
description: "Use the Combobox input to filter results from the list."
},
negative: {
component: (
),
description: "Don’t use the Combobox as a search input to search results that are not within the list."
}
}
]}
/>
## Use cases and examples
### Combobox as person picker
We are using Combobox component for our board person picker.
## Related components
================================================
FILE: packages/docs/src/pages/components/Combobox/Combobox.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipOtherComponents = () => (
When there are fewer than five items, consider using{" "}
Radio buttons
{" "}
(if only one item can be selected) or{" "}
Checkboxes
{" "}
(if multiple items can be selected).
);
================================================
FILE: packages/docs/src/pages/components/Combobox/Combobox.stories.tsx
================================================
import React, { useMemo, useState } from "react";
import { StoryDescription } from "vibe-storybook-components";
import person1 from "../Avatar/assets/person1.png";
import person2 from "../Avatar/assets/person2.png";
import person3 from "../Avatar/assets/person3.png";
import { defaultPlaySuite } from "./Combobox.interactions";
import { Edit, Person, ThumbsUp, Time, Update, Upgrade, Wand } from "@vibe/icons";
import {
Avatar,
Flex,
Button,
Dialog,
Icon,
Text,
DialogContentContainer,
type ComboboxProps,
Combobox
} from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
const metaSettings = createStoryMetaSettingsDecorator({
component: Combobox,
iconPropNamesArray: ["searchIcon"],
actionPropsArray: ["onOptionHover", "onOptionLeave", "onFilterChanged"]
});
export default {
title: "Components/Combobox [Deprecated]",
component: Combobox,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
const comboboxTemplate = (args: ComboboxProps) => {
return (
);
};
export const Overview = {
render: comboboxTemplate.bind({}),
args: {
options: [
{
id: "1",
label: "Option 1"
},
{
id: "2",
label: "Option 2"
},
{
id: "3",
label: "Option 3"
}
],
onClick: () => alert("clicked"),
placeholder: "Placeholder text here",
clearFilterOnSelection: true
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Default = {
render: () => {
const options = useMemo(
() => [
{
id: "1",
label: "Option 1"
},
{
id: "2",
label: "Option 2"
},
{
id: "3",
label: "Option 3"
}
],
[]
);
return ;
},
play: defaultPlaySuite
};
export const ComboboxInsideADialog = {
render: () => {
const options = useMemo(
() => [
{
id: "1",
label: "Option 1"
},
{
id: "2",
label: "Option 2"
},
{
id: "3",
label: "Option 3"
},
{
id: "4",
label: "Option 4"
},
{
id: "5",
label: "Option 5"
}
],
[]
);
return (
);
},
name: "Combobox inside a dialog"
};
export const Sizes = {
render: () => {
const options = useMemo(
() => [
{
id: "1",
label: "Option 1"
},
{
id: "2",
label: "Option 2"
},
{
id: "3",
label: "Option 3"
},
{
id: "4",
label: "Option 4"
},
{
id: "5",
label: "Option 5"
}
],
[]
);
return (
);
}
};
export const WithCategories = {
render: () => {
const options = useMemo(
() => [
{
id: "1",
label: "Favorites",
categoryId: "favorites"
},
{
id: "2",
label: "Main workspace",
categoryId: "workspace"
},
{
id: "3",
label: "Client Foundations",
categoryId: "workspace"
},
{
id: "4",
label: "Design",
categoryId: "workspace"
},
{
id: "5",
label: "Marketing Cluster",
categoryId: "workspace"
},
{
id: "6",
label: "Mobile",
categoryId: "workspace"
}
],
[]
);
const categories = useMemo(
() => ({
favorites: {
id: "favorites",
label: "Favorites"
},
workspace: {
id: "Workspaces",
label: "Workspaces"
}
}),
[]
);
return (
Regular
Sticky mode
With divider
);
},
name: "With categories"
};
export const WithIcons = {
render: () => {
const options = useMemo(
() => [
{
id: "1",
label: "Option 1",
leftIcon: Wand
},
{
id: "2",
label: "Option 2",
leftIcon: ThumbsUp
},
{
id: "3",
label: "Option 3",
leftIcon: Time
},
{
id: "4",
label: "Option 4",
leftIcon: Update
},
{
id: "5",
label: "Option 5",
leftIcon: Upgrade
}
],
[]
);
return (
);
},
parameters: {
docs: {
liveEdit: {
scope: { Wand, ThumbsUp, Time, Update, Upgrade }
}
}
},
name: "With icons"
};
export const WithOptionRenderer = {
render: () => {
const options = useMemo(
() => [
{ id: "1", label: "Option 1" },
{ id: "2", label: "Option 2" },
{ id: "3", label: "Option 3" },
{ id: "4", label: "Option 4" },
{ id: "5", label: "Option 5" },
{ id: "6", label: "Option 6" },
{ id: "7", label: "Option 7" },
{ id: "8", label: "Option 8" },
{ id: "9", label: "Option 9" }
],
[]
);
const optionRenderer = (option: any) => (
- [Overview](#overview)
- [Why Migrate? ✨](#why-migrate-)
- [Migration Steps 🚀](#migration-steps-)
- [Breaking Changes 🚨](#breaking-changes-)
- [Complete Migration Examples](#complete-migration-examples)
- [FAQ ❓](#faq-)
- [Help 🙏](#help-)
## Overview
The Combobox component is deprecated in favor of the new Dropdown's box mode. The box mode provides the same always-visible list functionality with significant improvements in accessibility, performance, and developer experience.
## Why Migrate? ✨
### Enhanced Accessibility
- **Proper ARIA attributes** and keyboard navigation
- **Screen reader optimized** with clear announcements
- **Improved focus management** and visual indicators
- **Better form integration** with proper label associations
### Better Performance
- **Smaller bundle size** through optimized implementation
- **Improved rendering** for large datasets
- **Automatic virtualization** for performance optimization
- **Better memory usage** with optimized re-renders
### Enhanced TypeScript Support
- **Full generic type support** with `` type parameter
- **Better type safety** for options and callbacks
- **Comprehensive prop type definitions**
- **IntelliSense improvements** for better developer experience
### Improved Form Integration
- **Built-in label, helper text, and error state support**
- **Better validation** and required field handling
- **Proper form field structure** and semantics
- **Native form integration** without additional wrappers
### Enhanced Features
- **Sticky group headers** with `stickyGroupTitle`
- **Custom filtering** with `filterOption` prop
- **Richer option structures** with start/end elements
- **Better tooltip integration** with `tooltipProps`
## Migration Steps 🚀
### Quick Migration Example
console.log(option)}
- />
* console.log(option)}
* ariaLabel="Select an option"
* />`}
/>
### 1. Update Import Path
### 2. Enable Box Mode and Search
Box mode requires `searchable={true}` and `boxMode={true}`:
+ `}
/>
### 3. Update Option Data Structure
### 4. Update Categories to Groups
### 5. Update Callbacks
handleSelect(option)}
- onFilterChanged={(value) => handleSearch(value)}
- />
+ handleSelect(option)}
+ onInputChange={(value) => handleSearch(value)}
+ />`}
/>
## Breaking Changes 🚨
### Removed Props
These props are no longer available in the new Dropdown box mode:
- **`onOptionHover`** - Not needed in new implementation
- **`onOptionLeave`** - Not needed in new implementation
- **`shouldScrollToSelectedItem`** - Handled automatically
- **`renderOnlyVisibleOptions`** - Automatic virtualization built-in
- **`searchIcon`** - Handled automatically
- **`onAddNew`** - Use `menuRenderer` for custom "add new" functionality
- **`addNewLabel`** - Use `menuRenderer` for custom "add new" functionality
- **`noResultsRenderer`** - Use `noOptionsMessage` prop instead (accepts ReactNode)
- **`searchWrapperClassName`** - Style via main `className` prop
- **`optionClassName`** - Use `optionRenderer` for custom option styling
- **`stickyCategoryClassName`** - Style via CSS
- **`disableFilter`** - Simply don't use the `searchable` prop
- **`optionLineHeight`** - Handled automatically by the component
### Changed Props
These props have been renamed or changed behavior:
- **`id` → `id`** - Update structure from `{id, label}` to `{value, label}` for options
- **`onClick`** → **`onChange`** - Renamed for consistency with form inputs
- **`onFilterChanged`** → **`onInputChange`** - Renamed for consistency
- **`categories`** → **`options` (grouped)** - Categories become nested group structure
- **`filter`** → **`filterOption`** - Custom filtering with different signature
- **`filterValue`** → **`inputValue`** - For controlled search input
- **`defaultFilter`** → **`defaultInputValue`** - For uncontrolled default search value
- **`clearFilterOnSelection`** → **`clearInputOnChange`** - Renamed for consistency
- **`noResultsMessage`** → **`noOptionsMessage`** - Renamed and accepts ReactNode
- **`optionsListHeight`** → **`menuHeight`** - Renamed for consistency
- **`stickyCategories`** → **`stickyGroupTitle`** - Renamed to match new terminology
- **`withCategoriesDivider`** → **`withGroupDivider`** - Renamed to match new terminology
- **`searchInputAriaLabel`** → **`inputAriaLabel`** - Renamed for consistency
- **`maxOptionsWithoutScroll`** → **`maxMenuHeight`** - Different approach to limiting height
### Required New Props
You must add these props to enable box mode:
- **`searchable`** - **Required**: Set to `true` to enable search functionality
- **`boxMode`** - **Required**: Set to `true` to display as always-visible box
### New Props (Optional)
These new props provide enhanced functionality:
- **`label`** - Built-in label support for form integration
- **`helperText`** - Built-in helper text support
- **`error`** - Built-in error state support
- **`required`** - Built-in required field indicator
- **`ariaLabel`** - Proper accessibility label for the dropdown
- **`tooltipProps`** - Enhanced tooltip integration
- **`dir`** - Text direction support (ltr/rtl)
### Option Data Structure Changes
The most important breaking change is the option data structure:
### Categories to Groups
Category structure has changed from a flat map to nested groups:
## Complete Migration Examples
### Basic Combobox to Box Mode
setSelected(option)}
- searchInputAriaLabel="Search for people"
- />
+ setSelected(option)}
+ ariaLabel="Search for people"
+ />`}
/>
### Combobox with Categories to Grouped Dropdown
setSelected(option)}
- />
+ setSelected(option)}
+ ariaLabel="Select person"
+ />`}
/>
### Combobox with Icons to Dropdown with Elements
- },
- {
- id: "2",
- label: "Send",
- leftRenderer: () =>
- }
- ]}
- onClick={(option) => setSelected(option)}
- />
+ setSelected(option)}
+ ariaLabel="Select option"
+ />`}
/>
### Combobox with Custom Filter
{
- return options.filter(opt =>
- opt.label.toLowerCase().includes(filterValue.toLowerCase())
- );
- }}
- onClick={(option) => setSelected(option)}
- />
+ {
+ return option.label.toLowerCase().includes(inputValue.toLowerCase());
+ }}
+ onChange={(option) => setSelected(option)}
+ ariaLabel="Select option"
+ />`}
/>
### Combobox with "Add New" Functionality
{
- const newOption = { id: Date.now(), label: value };
- setOptions([...options, newOption]);
- }}
- addNewLabel={(value) => \`Add "\${value}"\`}
- onClick={(option) => setSelected(option)}
- />
+ setSelected(option)}
+ menuRenderer={({ children, filteredOptions }) => (
+ <>
+ {children}
+ {filteredOptions.length === 0 && inputValue && (
+ {
+ const newOption = { value: Date.now(), label: inputValue };
+ setOptions([...options, newOption]);
+ }}>
+ Add "{inputValue}"
+
+ )}
+ >
+ )}
+ ariaLabel="Select or create option"
+ />`}
/>
### Form Integration Example
One of the biggest improvements is built-in form field support:
-
- setSelected(option)}
- />
- Choose a person to assign
- {error && {error}}
-
+ setSelected(option)}
+ ariaLabel="Select person"
+ />`}
/>
## FAQ ❓
**Q: When should I migrate to Dropdown box mode?**
A: We recommend migrating when updating your codebase or when you need the enhanced accessibility and performance features. The new implementation is production-ready.
**Q: Will Combobox be removed?**
A: Yes, Combobox will be removed in Vibe 4. We'll provide ample notice and migration support.
**Q: Can I use both Combobox and Dropdown box mode during migration?**
A: Yes, you can use both during the migration period. Migrate components incrementally at your own pace.
**Q: What if I have a large number of Combobox instances?**
A: Start with new features and gradually migrate existing ones. The migration is straightforward and can be done incrementally.
**Q: Are there any features missing from Dropdown box mode?**
A: Dropdown box mode covers all major use cases. If you're using advanced Combobox features, check the prop migration reference or reach out for help.
## Help 🙏
If you have any questions, feedback, or need help with migration, please don't hesitate to reach out:
- **GitHub Issues**: [Report issues](https://github.com/mondaycom/vibe/issues/new/choose)
- **GitHub Discussions**: [Ask questions](https://github.com/mondaycom/vibe/discussions)
================================================
FILE: packages/docs/src/pages/components/Counter/Counter.mdx
================================================
import { Meta } from "@storybook/blocks";
import { Link } from "vibe-storybook-components";
import { Counter } from "@vibe/core";
import { TipCheckYourself, Usage } from "./Counter.stories.helpers";
import * as CounterStories from "./Counter.stories";
# Counter
Counters show the count of some adjacent data.
### Import
```js
import { Counter } from "@vibe/core";
```
## Props
## Usage
## Variants
### Sizes
There are 3 sizes of counters
### Colors
### Outline
### Limits
## Do’s and Don’ts
,
description: "Use counter to notify the number of items, such as notifications, updates, or inbox alerts."
},
negative: {
component: ,
description: (
<>
Don’t include any text. If you need a text label, use a{" "}
Label.
>
)
}
},
{
positive: {
component: ,
description: "Only use a maximum of 3 digits in a counter."
},
negative: {
component: ,
description: "Don’t use more than 3 digits in a counter."
}
}
]}
/>
## Use cases and examples
### Notification counter
Used on the notification icon to indicate the number of new notifications.
### Counter on inbox filters
The counter represents the number of items on each topic.
### Count the number of updates
The counter represents the number of items on each topic.
## Related components
================================================
FILE: packages/docs/src/pages/components/Counter/Counter.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip, UsageGuidelines } from "vibe-storybook-components";
export const TipCheckYourself = () => (
Need to indicate information that is not numeric? Use the{" "}
Label
{" "}
component instead.
);
export const Usage = () => (
The element the counter refers to should include{" "}
Tooltip
{" , where necessary, to enhance user understanding. For example, a badge is used in conjunction with an icon."}
);
},
name: "Dialog with tooltip",
parameters: {
docs: {
liveEdit: {
scope: { Info, shift, preventMainAxisShift }
}
}
}
};
export const DisableScrollWhenDialogOpen = {
render: () => {
// For maintain active state of each button according to the dialog open state (this hooks is available for your usage)
const { isChecked: checkedTop, onChange: onChangeTop } = useSwitch({
defaultChecked: false
});
return (
);
},
name: "Disable scroll when dialog open"
};
================================================
FILE: packages/docs/src/pages/components/Dialog/DialogContentContainer.mdx
================================================
import { Meta } from "@storybook/blocks";
import * as DialogContentContainerStories from "./DialogContentContainer.stories";
# DialogContentContainer
This component is a style component, it provide the look and feel of elevation.
### Import
```js
import { DialogContentContainer } from "@vibe/core";
```
## Props
## Usage
## Variants
### Popover
### Modal
================================================
FILE: packages/docs/src/pages/components/Dialog/DialogContentContainer.stories.tsx
================================================
import React from "react";
import { DialogContentContainer, Box } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { createComponentTemplate } from "vibe-storybook-components";
const metaSettings = createStoryMetaSettingsDecorator({
component: DialogContentContainer,
ignoreControlsPropNamesArray: ["children"]
});
const dialogContentContainerTemplate = createComponentTemplate(DialogContentContainer);
export default {
title: "Components/DialogContentContainer",
component: DialogContentContainer,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
export const Overview = {
render: dialogContentContainerTemplate.bind({}),
name: "Overview",
args: {
children:
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Popover = {
render: () => (
)
};
export const Modal = {
render: () => (
)
};
================================================
FILE: packages/docs/src/pages/components/Divider/Divider.mdx
================================================
import { Meta } from "@storybook/blocks";
import * as DividerStories from "./Divider.stories";
# Divider
Divider create separation between two UI elements
### Import
```js
import { Divider } from "@vibe/core";
```
## Props
## Variants
### Directions
## Related components
================================================
FILE: packages/docs/src/pages/components/Divider/Divider.stories.tsx
================================================
import React from "react";
import { Divider, type DividerProps } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
const metaSettings = createStoryMetaSettingsDecorator({
component: Divider
});
const dividerTemplate = (args: DividerProps) => (
),
name: "Directions"
};
================================================
FILE: packages/docs/src/pages/components/Dropdown/Dropdown.mdx
================================================
import { Canvas, Meta } from "@storybook/blocks";
import { Link, Tip, StorybookLink } from "vibe-storybook-components";
import { Overview as BasicDropdownPreview } from "./DropdownBasicDropdown.stories";
import { Overview as BoxModePreview } from "./DropdownBoxMode.stories";
import do1 from "./assets/do1.png";
import dont1 from "./assets/dont1.png";
# Dropdown
Select component present a list of options from which a user can select one or several.
### Import
```js
import { Dropdown } from "@vibe/core";
```
### Basic dropdown
The Basic dropdown present a popup list of options from which a user can select one or several.
### Dropdown box mode
The Dropdown box mode is an always open Dropdown, with the same properties as the basic Dropdown.
## Usage
## Do's and Don'ts
,
description: "Use the select when theres a need to choose one or multiple options from a predefined list."
},
negative: {
component: ,
description: (
<>
Use select when the purpose of the list is to provide actions or navigation options. Use a{" "}
menu instead.
>
)
}
}
]}
/>
## Related components
================================================
FILE: packages/docs/src/pages/components/Dropdown/DropdownBasicDropdown.mdx
================================================
import { Meta } from "@storybook/blocks";
import * as DropdownBasicDropdownStories from "./DropdownBasicDropdown.stories";
import basicSelectDo1 from "./assets/basicSelectDo1.png";
import basicSelectDont1 from "./assets/basicSelectDont1.png";
import basicSelectDo2 from "./assets/basicSelectDo2.png";
import basicSelectDont2 from "./assets/basicSelectDont2.png";
import dont1 from "./assets/dont1.png";
import { StorybookLink } from "vibe-storybook-components";
# Basic Dropdown
The basic dropdown is intended for quick value selection when space is limited and the list of options doesn't need to remain visible. It typically is used when you have 5-8 items to choose from where an action is initiated based on the selection.
### Import
```js
import { Dropdown } from "@vibe/core";
```
## Props
## Usage
## Accessibility
Using an id is highly recommended for all instances to ensure proper label association.
>,
<>
Always provide a label prop to ensure the dropdown's purpose is clear to all users.
>,
<>
Use ariaLabel prop when you need to provide a custom accessible name for the dropdown.
>,
<>
Use clearAriaLabel prop when dropdown is clearable.
>,
<>
Use inputAriaLabel prop when you need to provide a specific accessible name for the input field in
searchable dropdowns.
>,
<>
Use menuAriaLabel prop when you need to provide a custom accessible name for the dropdown menu.
>,
<>
Use autoFocus prop when the dropdown should receive initial focus for keyboard navigation.
>
]}
/>
## Variants
### Sizes
There are three sizes available: Small, Medium, and Large
### States
### Multi select
The Dropdown component supports multi select option that display as chips. The selected items can be shown in either a single line (with additional option for hidden list), or multiple line. This mode also supports all standard dropdown states.
### Dropdown with icon or avatar
## Do's and Don'ts
,
description: "Use the searchable option when having a long list of options."
},
negative: {
component: ,
description: <>Use a searchable select when the list is short. Use the select as is.>
}
},
{
positive: {
component: ,
description:
"Use the select as a closed component. Users should normally be allowed only to click on the items; search is not recommended, though possible."
},
negative: {
component: ,
description: (
<>
Keep the select component in open mode as permanent state. If this is a design requirement consider use the
box mode instead box mode instead.
>
)
}
}
]}
/>
## Use cases and examples
### Searchable dropdown
The dropdown can also function as a search for a specific item within the list.
### Dropdown with groups
Dropdown can be organized into groups, either with titled sections or dividers. Group titles can be configured to remain sticky or non-sticky.
### Dropdown with elements
The dropdown item can contain start element or end element. This are the options:
### Hide selected items
You can choose to hide selected items within the dropdown list, so users can see only the available options.
### Dropdown with tooltips
### Dropdown with virtualization
For performance optimization with large datasets, the Dropdown supports virtualization through the menuRenderer prop. You can integrate any virtualization library of your choice - this example demonstrates implementation using react-window.
## Related components
================================================
FILE: packages/docs/src/pages/components/Dropdown/DropdownBasicDropdown.stories.tsx
================================================
import React, { useCallback, useMemo } from "react";
import { type Meta, type StoryObj } from "@storybook/react";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import person1 from "../Avatar/assets/person1.png";
import person3 from "../Avatar/assets/person3.png";
import person2 from "../Avatar/assets/person2.png";
import { Attach, Email } from "@vibe/icons";
import { Dropdown, type BaseDropdownProps, type DropdownOption, Flex, Text } from "@vibe/core";
import { FixedSizeList as List } from "react-window";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: Dropdown,
actionPropsArray: [
"onMenuOpen",
"onMenuClose",
"onFocus",
"onBlur",
"onChange",
"openMenuOnFocus",
"onOptionRemove",
"onOptionSelect",
"onClear",
"onInputChange",
"onKeyDown"
]
});
const meta: Meta = {
title: "Components/Dropdown/Basic dropdown",
component: Dropdown,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
export default meta;
const dropdownTemplate = (props: BaseDropdownProps) => {
const options = useMemo(
() => [
{ value: 1, label: "Option 1" },
{ value: 2, label: "Option 2" },
{ value: 3, label: "Option 3" }
],
[]
);
return (
);
},
name: "Virtualized Dropdown"
};
================================================
FILE: packages/docs/src/pages/components/Dropdown/DropdownBoxMode.mdx
================================================
import { Meta } from "@storybook/blocks";
import * as DropdownBoxModeStories from "./DropdownBoxMode.stories";
import boxModeDo from "./assets/boxModeDo.png";
import boxModeDont from "./assets/boxModeDont.png";
# Dropdown box mode
The dropdown box mode is intended for cases where users need to view and compare options at all times, without opening or closing a dropdown. It allows the user to make a selection from a predefined list of options and is typically used when there are a large amount of options to choose from.
Migrating from the Combobox? Check out our comprehensive{" "}
Combobox Migration Guide for
step-by-step instructions, breaking changes, and new features.
### Import
```js
import { Dropdown } from "@vibe/core";
```
## Props
## Usage
## Variants
### Default state
Dropdown box mode can be used without dialog or as part of the layout by default.
### Inside a dialog
Use this for Dropdown box mode that triggered by button.
### Multi select
The box mode supports multi select option that display as chips. The selected items can be shown in either a single line (with additional option for hidden list), or multiple line. This mode also supports all standard dropdown states.
### With icons
## Do's and don'ts
,
description: "Use when there’s a need for a select that displays the list in a persistent, always-open panel."
},
negative: {
component: ,
description: (
<>
Use when there's need for a searchable menu, for navigation. Use a{" "}
menu instead
>
)
}
}
]}
/>
## Use cases and examples
### People picker
Can be used when there is a need for selecting people to assign a column
## Related components
================================================
FILE: packages/docs/src/pages/components/Dropdown/DropdownBoxMode.stories.tsx
================================================
import React, { useMemo } from "react";
import { type Meta, type StoryObj } from "@storybook/react";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import person1 from "../Avatar/assets/person1.png";
import person2 from "../Avatar/assets/person2.png";
import person3 from "../Avatar/assets/person3.png";
import person4 from "../Avatar/assets/person4.png";
import { Email, Send, Mobile } from "@vibe/icons";
import { Dropdown, Flex, Text, DialogContentContainer, Button } from "@vibe/core";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: Dropdown,
actionPropsArray: [
"onMenuOpen",
"onMenuClose",
"onFocus",
"onBlur",
"onChange",
"openMenuOnFocus",
"onOptionRemove",
"onOptionSelect",
"onClear",
"onInputChange",
"onKeyDown"
]
});
const meta: Meta = {
title: "Components/Dropdown/Dropdown box mode",
component: Dropdown,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
export default meta;
const dropdownTemplate = props => {
const options = useMemo(
() => [
{ value: 1, label: "Label" },
{ value: 2, label: "Label" },
{ value: 3, label: "Label" },
{ value: 4, label: "Label" },
{ value: 5, label: "Label" }
],
[]
);
return (
);
},
parameters: {
docs: {
liveEdit: {
scope: { person1, person2, person3, person4 }
}
}
}
};
================================================
FILE: packages/docs/src/pages/components/Dropdown/dropdown-migration-guide.mdx
================================================
import { Meta } from "@storybook/blocks";
import { Link, Tip } from "vibe-storybook-components";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { DiffCodeBlock } from "../../migration-guide/DiffCodeBlock";
# Dropdown Migration Guide
- [Overview](#overview)
- [Why Migrate? ✨](#why-migrate-)
- [Migration Steps 🚀](#migration-steps-)
- [Breaking Changes 🚨](#breaking-changes-)
- [Enhanced Option Data Structure 🎯](#enhanced-option-data-structure-)
- [Type Safety Improvements 🛡️](#type-safety-improvements-)
- [FAQ ❓](#faq-)
- [Help 🙏](#help-)
## Overview
The Dropdown component has been completely rewritten from scratch to provide better accessibility, performance, and developer experience. The new implementation is built on top of the **Downshift library** instead of **react-select**, offering significant improvements across all areas.
## Why Migrate? ✨
### Enhanced Accessibility
- **Proper ARIA attributes** and keyboard navigation
- **Screen reader optimized** with clear announcements
- **Single combobox role** to avoid confusion
- **Improved focus management** and visual indicators
### Better Performance
- **Smaller bundle size** (no react-select dependency)
- **Native implementation** with optimized rendering
- **Improved performance** for large datasets
- **Better memory usage** with optimized re-renders
### Enhanced TypeScript Support
- **Full generic type support** with `` type parameter
- **Better type safety** for options and callbacks
- **Comprehensive prop type definitions**
- **IntelliSense improvements** for better developer experience
### Improved Form Integration
- **Built-in label, helper text, and error state support**
- **Better validation** and required field handling
- **Proper form field structure** and semantics
- **Native form integration** without additional wrappers
### Enhanced Features
- **Sticky group headers** with `stickyGroupTitle`
- **Custom filtering** with `filterOption` prop
- **Control selected options visibility** with `showSelectedOptions`
- **Better tooltip integration** with `tooltipProps`
## Migration Steps 🚀
### 1. Update Import Path
### 2. Update Option Data Structure
The new Dropdown expects options with explicit `label` and `value` properties:
### 3. Update Form Integration
-
-
- Helper text here
-
+ `}
/>
## Breaking Changes 🚨
### Removed Props
These props are no longer available in the new implementation:
- **`extraStyles`** - Use CSS classes instead
- **`menuPortalTarget`** - Handled automatically with better positioning
- **`isVirtualized`** - Built-in performance optimization
- **`asyncOptions`** - Use external data fetching with `options` prop
- **`cacheOptions`** - Handle caching externally
- **`loadingMessage`** - Use `helperText` with loading indicator
- **`onMenuScrollToBottom`** - Use `onScroll` instead
- **`captureMenuScroll`** - Handled automatically
- **`insideOverflowContainer`** - Handled automatically
- **`insideOverflowWithTransformContainer`** - Handled automatically
- **`insideLayerContext`** - Handled automatically
- **`popupsContainerSelector`** - Handled automatically
- **`tooltipContent`** - Use `tooltipProps` instead for enhanced tooltip support
### Changed Props
These props have different default values or behavior:
- **`searchable`** - **BREAKING CHANGE**: Now defaults to `false` (was `true`).
- **`showSelectedOptions`** - **BREAKING CHANGE**: Defaults to `true`. In the old Dropdown, selected options were always hidden in multi select mode.
- **`multi`** - Better type safety and native chip display
### New Props
These props are new and provide enhanced functionality:
- **`label`** - Built-in label support
- **`helperText`** - Built-in helper text support
- **`error`** - Built-in error state support
- **`required`** - Built-in required field support
- **`stickyGroupTitle`** - Sticky group headers
- **`showSelectedOptions`** - Control selected options visibility (defaults to `true`)
- **`filterOption`** - Custom filtering logic
- **`tooltipProps`** - Enhanced tooltip integration (replaces `tooltipContent`)
- **`inputAriaLabel`** - ARIA label for input
- **`menuAriaLabel`** - ARIA label for menu
- **`dir`** - Text direction support instead of `rtl` prop
### Tooltip Integration Changes
The tooltip integration has been improved in the new Dropdown:
+ // New Dropdown: Full tooltip props support
+
+ // Option-level tooltips
+ const optionsWithTooltips = [
+ {
+ value: "option1",
+ label: "Option 1",
+ tooltipProps: {
+ content: "Description for this option"
+ }
+ }
+ ];`}
/>
### Selected Options Visibility Behavior
**BREAKING CHANGE**: The new Dropdown changes how selected options are displayed in the dropdown list:
- // Selected option still appears in the list when opened
+ // New Dropdown: Selected options hidden by default
+
+ // Selected option is hidden from the list when opened
+
+ // To restore old behavior:
+ `}
/>
### Search Functionality Changes
**BREAKING CHANGE**: The new Dropdown changes the default search behavior:
- // Search input was enabled by default
+ // New Dropdown: Not searchable by default
+
+ // No search input, simple select behavior
+
+ // To restore old search behavior:
+ `}
/>
#### When to Enable Search
Enable search for dropdowns with many options or when users need to filter:
```tsx
// Enable search for large option lists
```
## Enhanced Option Data Structure 🎯
### Start and End Elements
The new Dropdown supports rich option structures with start and end elements, allowing you to add icons, avatars, suffixes, and custom content to your options:
#### Available Element Types
**Start Elements:**
- **`avatar`** - Display user avatars: `{ type: "avatar", value: "image.jpg", square?: boolean }`
- **`icon`** - Display icons: `{ type: "icon", value: "iconName" }`
- **`indent`** - Add indentation: `{ type: "indent" }`
- **`custom`** - Custom content: `{ type: "custom", render: () => }`
**End Elements:**
- **`icon`** - Display icons: `{ type: "icon", value: "iconName" }`
- **`suffix`** - Display text suffix: `{ type: "suffix", value: "Hint text" }`
- **`custom`** - Custom content: `{ type: "custom", render: () => }`
#### Multi-Select Chip Integration
Elements are automatically integrated with chips in multi-select mode:
```tsx
const optionsWithElements = [
{
value: "user1",
label: "John Doe",
startElement: { type: "avatar", value: "john.jpg" },
endElement: { type: "suffix", value: "Admin" },
chipColor: "primary" // Controls chip color in multi-select
}
];
;
```
## Type Safety Improvements 🛡️
The new Dropdown provides full TypeScript support with generics:
+ options={users}
+ onChange={(user) => {
+ // user is fully typed as User
+ console.log(user.email);
+ }}
+ />`}
/>
### Enhanced Type Safety
```tsx
// Multi-select with proper typing
interface Task {
value: string;
label: string;
priority: "high" | "medium" | "low";
}
multi
options={tasks}
onChange={selectedTasks => {
// selectedTasks is properly typed as Task[]
selectedTasks.forEach(task => {
console.log(task.priority); // TypeScript knows this exists
});
}}
/>;
```
## FAQ ❓
**Q: What happened to the old Dropdown?**
A: The old react-select based Dropdown has been removed in Vibe 4. The new Dropdown (previously available via `@vibe/core/next`) is now the default export from `@vibe/core`.
**Q: I was importing Dropdown from `@vibe/core/next`. What should I change?**
A: Update your import to use `@vibe/core` instead of `@vibe/core/next`.
**Q: Are there any features missing from the new Dropdown?**
A: The new Dropdown covers all major use cases. If you're using `asyncOptions`, implement data fetching externally and pass the results to the `options` prop.
**Q: How do I handle async data loading?**
A: Implement your own data fetching logic and pass the results to the `options` prop. You can use the `helperText` prop to show loading states.
**Q: Is the new Dropdown accessible?**
A: Yes! The new Dropdown is built with accessibility as a core principle.
**Q: How do I migrate tooltip functionality?**
A: Replace `tooltipContent` with `tooltipProps` for dropdown-level tooltips. The new implementation also supports option-level tooltips via the `tooltipProps` property on individual options.
**Q: Why is my dropdown no longer searchable?**
A: The new Dropdown defaults to `searchable={false}` since most dropdowns don't need search functionality. Add the `searchable` prop to enable search: ``
**Q: How do I add icons or avatars to dropdown options?**
A: Use the `startElement` and `endElement` properties on option objects. The new Dropdown supports icons, avatars, indentation, suffixes, and custom elements.
**Q: I'm migrating from Combobox, where should I look?**
A: We have a dedicated Combobox Migration Guide that covers migrating from Combobox to Dropdown box mode.
## Help 🙏
If you have any questions, feedback, or need help with migration, please don't hesitate to reach out:
- **GitHub Issues**: [Report issues](https://github.com/mondaycom/vibe/issues/new/choose)
- **GitHub Discussions**: [Ask questions](https://github.com/mondaycom/vibe/discussions)
The new Dropdown represents a significant improvement in accessibility, performance, and developer experience. We're excited to see what you build with it! 🚀
================================================
FILE: packages/docs/src/pages/components/EditableHeading/EditableHeading.interactions.ts
================================================
import { expect } from "@storybook/jest";
import {
type Canvas,
clearText,
clickElement,
getByTestId,
getByRole,
interactionSuite,
typeText,
delay,
resetFocus
} from "@vibe/core/interactionsTests";
import { ComponentDefaultTestId } from "@vibe/shared";
const CHANGES_DELAY = 200;
const text = "This heading is an editable heading";
function getComponent(canvas: Canvas) {
return getByTestId(canvas, ComponentDefaultTestId.EDITABLE_HEADING);
}
function getInput(canvas: Canvas) {
return getByRole(canvas, "input");
}
async function changeModes(canvas: Canvas) {
await delay(CHANGES_DELAY); // needed the tests would run correctly on page refresh
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
expect(input).toHaveAttribute("value", text);
await resetFocus();
const heading = getComponent(canvas);
expect(heading).toHaveTextContent(text);
}
async function editAndChangeToValidText(canvas: Canvas) {
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
await clearText(input);
await typeText(input, text);
expect(input).toHaveAttribute("value", text);
await resetFocus();
const heading = getComponent(canvas);
expect(heading).toHaveTextContent(text);
}
async function clearInput(canvas: Canvas) {
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
await clearText(input);
await resetFocus();
const heading = getComponent(canvas);
expect(heading).toHaveTextContent(text);
}
async function cancelEditing(canvas: Canvas) {
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
await clearText(input);
await delay(CHANGES_DELAY);
const textToChange = "test";
await typeText(input, textToChange);
expect(input).toHaveAttribute("value", textToChange);
await typeText(input, "{Escape}");
const heading = getComponent(canvas);
expect(heading).toHaveTextContent(text);
}
export const overviewPlaySuite: ReturnType = interactionSuite({
tests: [changeModes, editAndChangeToValidText, clearInput, cancelEditing]
});
================================================
FILE: packages/docs/src/pages/components/EditableHeading/EditableHeading.mdx
================================================
import { EditableHeading } from "@vibe/core";
import { Meta } from "@storybook/blocks";
import { createComponentTemplate } from "vibe-storybook-components";
import { TipEditableText } from "./EditableHeading.stories.helpers";
import * as EditableHeadingStories from "./EditableHeading.stories";
export const editableHeadingTemplate = createComponentTemplate(EditableHeading);
# EditableHeading
Editable Heading allows users to seamlessly and dynamically edit in-line content. Its default state is a read-view, based on the Heading component, and it becomes editable after clicking on it.
### Import
```js
import { EditableHeading } from "@vibe/core";
```
## Props
## Usage
## Variants
### Heading types
Editable heading can be used with any of the Heading component sizes and weights.
### With placeholder
## Related components
================================================
FILE: packages/docs/src/pages/components/EditableHeading/EditableHeading.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipEditableText = () => (
This component is used for editing text size 18px and up. For editing smaller text sizes, consider using{" "}
EditableText
);
================================================
FILE: packages/docs/src/pages/components/EditableHeading/EditableHeading.stories.tsx
================================================
import React from "react";
import { EditableHeading, Flex } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { createComponentTemplate } from "vibe-storybook-components";
import { overviewPlaySuite } from "./EditableHeading.interactions";
const metaSettings = createStoryMetaSettingsDecorator({
component: EditableHeading,
actionPropsArray: ["onChange"]
});
export default {
title: "Components/EditableHeading",
component: EditableHeading,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
const editableHeadingTemplate = createComponentTemplate(EditableHeading);
export const Overview = {
render: editableHeadingTemplate.bind({}),
args: {
value: "This heading is an editable heading"
},
play: overviewPlaySuite,
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Types = {
render: () => (
)
};
export const WithPlaceholder = {
render: () => (
)
};
================================================
FILE: packages/docs/src/pages/components/EditableText/EditableText.interactions.ts
================================================
import { expect } from "@storybook/jest";
import {
type Canvas,
clearText,
clickElement,
getByTestId,
getByRole,
interactionSuite,
typeText,
delay,
resetFocus
} from "@vibe/core/interactionsTests";
import { ComponentDefaultTestId } from "@vibe/shared";
const CHANGES_DELAY = 200;
const text = "This text is an editable text";
function getComponent(canvas: Canvas) {
return getByTestId(canvas, ComponentDefaultTestId.EDITABLE_TEXT);
}
function getInput(canvas: Canvas) {
return getByRole(canvas, "input");
}
async function changeModes(canvas: Canvas) {
await delay(CHANGES_DELAY);
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
expect(input).toHaveAttribute("value", text);
await resetFocus();
const textElement = getComponent(canvas);
expect(textElement).toHaveTextContent(text);
}
async function editAndChangeToValidText(canvas: Canvas) {
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
await clearText(input);
await typeText(input, text);
expect(input).toHaveAttribute("value", text);
await resetFocus();
const textElement = getComponent(canvas);
expect(textElement).toHaveTextContent(text);
}
async function clearInput(canvas: Canvas) {
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
await clearText(input);
await resetFocus();
const textElement = getComponent(canvas);
expect(textElement).toHaveTextContent(text);
}
async function cancelEditing(canvas: Canvas) {
const compponent = getComponent(canvas);
clickElement(compponent);
await delay(CHANGES_DELAY);
const input = getInput(canvas);
await clearText(input);
const textToChange = "test";
await typeText(input, textToChange);
expect(input).toHaveAttribute("value", textToChange);
await typeText(input, "{Escape}");
const textElement = getComponent(canvas);
expect(textElement).toHaveTextContent(text);
}
export const overviewPlaySuite: ReturnType = interactionSuite({
tests: [changeModes, editAndChangeToValidText, clearInput, cancelEditing]
});
================================================
FILE: packages/docs/src/pages/components/EditableText/EditableText.mdx
================================================
import { Meta } from "@storybook/blocks";
import { TipTextField } from "./EditableText.stories.helpers";
import * as EditableTextStories from "./EditableText.stories";
# EditableText
Editable text allows users to seamlessly and dynamically edit in-line content. Its default state is a read-view, based on the Text component, and it becomes editable after clicking on it.
### Import
```js
import { EditableText } from "@vibe/core";
```
## Props
## Usage
## Variants
### Text types
Editable text can be used with any of the Text component sizes and weights.
### Multiline
Editable text can be used to allow multiline input.
### With placeholder
## Related components
================================================
FILE: packages/docs/src/pages/components/EditableText/EditableText.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipTextField = () => (
This component is used for editing existing text. To allow users to fill empty text fields, for example in a form,
consider using{" "}
TextField
);
================================================
FILE: packages/docs/src/pages/components/EditableText/EditableText.stories.tsx
================================================
import React from "react";
import { createComponentTemplate } from "vibe-storybook-components";
import { EditableText, Flex } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { overviewPlaySuite } from "./EditableText.interactions";
const metaSettings = createStoryMetaSettingsDecorator({
component: EditableText,
actionPropsArray: ["onChange"]
});
export default {
title: "Components/EditableText",
component: EditableText,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
const EditableTextTemplate = createComponentTemplate(EditableText);
export const Overview = {
render: EditableTextTemplate.bind({}),
args: {
"aria-label": "Editable text",
value: "This text is an editable text"
},
play: overviewPlaySuite,
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Types = {
render: () => (
)
};
export const Multiline = {
render: () => (
)
};
export const WithPlaceholder = {
render: () => (
)
};
================================================
FILE: packages/docs/src/pages/components/EmptyState/EmptyState.mdx
================================================
import { Meta, Controls } from "@storybook/blocks";
import { EmptyState } from "@vibe/core";
import * as EmptyStateStories from "./EmptyState.stories";
import { EmptyStateTip } from "./EmptyState.stories.helpers";
import mainActionDo from "./assets/main-action-do.png";
import mainActionDont from "./assets/main-action-dont.png";
import supportingDo from "./assets/supporting-do.png";
import supportingDont from "./assets/supporting-dont.png";
import supportingLinkDo from "./assets/supporting-link-do.png";
import supportingLinkDont from "./assets/supporting-link-dont.png";
import { StorybookLink } from "vibe-storybook-components";
import styles from "./EmptyState.stories.module.scss";
# EmptyState
An empty state component is a user interface element that communicates to users that a particular section or feature contains no data or content at the moment. It often provides visual cues, prompts, or suggestions on what actions can be taken to fill the space.
## Props
## Usage
## Actions
### Main Action
Designers should be able to add custom action in specific situations. Like a primary and secondary with consideration to location and layout.
### Link
Links should guide users to troubleshoot the issue or learn more about how to populate the section.
If there’s no action to the section the link can stand alone.
### Two buttons
Instead of link you can use a supporting action as a button. The supporting action cannot be the only button. You should include a main action with it.
## Typography
Empty state can be with or without a title.
## Layout
### Default
The Default layout is meant for most layouts and locations.
### Compact
Instead of link you can use a supporting action as a button. The supporting action cannot be the only button. You should include a main action with it.
## Do's and Don'ts
),
description: "Provide clear guidance and an action to help users resolve the empty state."
},
negative: {
component: ,
description: "Don't use vague messaging or unhelpful actions that don't guide users."
}
},
{
componentContainerClassName: styles.largeComponentRule,
positive: {
component: (
),
description: "Offer clear next steps with supporting information when needed."
},
negative: {
component: ,
description: "Don't provide minimal or unclear information about how to proceed."
}
},
{
componentContainerClassName: styles.largeComponentRule,
positive: {
component: (
),
description: "Offer clear next steps with supporting information when needed."
},
negative: {
component: (
),
description: "Don't provide minimal or unclear information about how to proceed."
}
}
]}
/>
## Related components
================================================
FILE: packages/docs/src/pages/components/EmptyState/EmptyState.stories.helpers.tsx
================================================
import React from "react";
import { Tip } from "vibe-storybook-components";
export const EmptyStateTip = () => {
return (
Actions help guide users towards the next steps they can take, ensuring that they remain engaged and know how to
proceed. Depending on the context and user needs, you can include a primary action, a link, both, or no actions
at all.
)
};
================================================
FILE: packages/docs/src/pages/components/Flex/Flex.mdx
================================================
import { Meta } from "@storybook/blocks";
import * as FlexStories from "./Flex.stories";
# Flex
Use Flex component to position group of sub-elements in one dimension, horizontal or vertical, without being dependent on a custom CSS file for positioning the sub-elements.
### Import
```js
import { Flex } from "@vibe/core";
```
## Props
## Usage
## Variants
### Directions
### Horizontal spacing between items
### Vertical spacing between items
### Horizontal positions
### Horizontal layout using flex
### Vertical positions
### Vertical layout using flex
### Support multi lines layout
You can display a layout that includes multiple lines using the flex component wrap mode.
This mode allows the layout to break into multiple lines if all the component children cannot fit into one only.
## Use cases and examples
### Flex as toolbar container
You can use flex component for create responsive toolbars
## Related components
================================================
FILE: packages/docs/src/pages/components/Flex/Flex.stories.tsx
================================================
import React from "react";
import { Add, Filter, Person, Search, Sort } from "@vibe/icons";
import { Button, Flex, Text, Box, Chips, type FlexProps } from "@vibe/core";
import { StoryDescription } from "vibe-storybook-components";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
const metaSettings = createStoryMetaSettingsDecorator({
component: Flex,
actionPropsArray: ["onClick"]
});
const flexTemplate = (args: FlexProps) => {
return (
);
};
export default {
title: "Layout/Flex",
component: Flex,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators,
parameters: {
docs: {
liveEdit: {
scope: { StoryDescription }
}
}
}
};
export const Overview = {
render: flexTemplate.bind({}),
name: "Overview",
args: {},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Directions = {
render: () => (
No spacing between items
Extra small spacing between items
Small spacing between items
Medium spacing between items
Large spacing between items
Custom spacing between items
);
}
};
================================================
FILE: packages/docs/src/pages/components/IconButton/IconButton.mdx
================================================
import { Meta } from "@storybook/blocks";
import { IconButton, DialogContentContainer } from "@vibe/core";
import { Add, Bolt, Broom, HighlightColorBucket, Pin, Show } from "@vibe/icons";
import { TipCheckYourself, TipInfo, TipMenuButton } from "./IconButton.stories.helpers";
import * as IconButtonStories from "./IconButton.stories";
# IconButton
Icon button is a square button contains only icon with no visible text labels used mostly in control bars.
### Import
```js
import { IconButton } from "@vibe/core";
```
## Props
## Usage
Icon button will always appear with a tooltip while
hovering, defining the icon’s meaning.
>,
"Use an icon button when a user can perform an inline action on this page and there's no room for a default button.",
"Use icon button when you want to display an active state of a button.",
"Use a Primary icon button when it's the most important action to take.",
"Use icon button only when the icons is clear and understandable.",
"Icon buttons are often used when there are 2 or 3 adjacent icons buttons that perform actions on a single item presented in a row."
]}
/>
## Accessibility
Provide an id for the IconButton to enable proper accessibility associations and unique
identification.
>,
<>
Always provide an accessible name using either ariaLabel prop or tooltipContent prop.
The component automatically uses tooltipContent as the accessible name if no ariaLabel{" "}
is provided.
>,
<>
Use ariaLabel prop when you need a different accessible name than the tooltip text, or when the icon
button doesn't have a tooltip.
>,
<>
Use ariaLabeledBy prop when an external element provides the accessible name for the icon button.
Pass the id of that external element.
>,
<>
Use ariaHasPopup prop when the icon button opens a menu, dialog, or popup.
>,
<>
Use ariaExpanded prop to indicate when a popup or menu opened by the icon button is currently open (
true) or closed (false).
>,
<>
Use ariaControls prop to link the icon button to the element it controls. Pass the id of
the controlled element.
>,
<>
Use aria-describedby prop to link the icon button to additional descriptive text. Pass the{" "}
id of the descriptive element.
>,
<>
Use aria-pressed prop for toggle icon buttons to indicate their current pressed state (
true, false, or mixed).
>,
<>
Use aria-hidden prop appropriately to hide the icon button from screen readers when necessary, but
use sparingly as it removes the button from the accessibility tree entirely.
>,
<>
Use tabIndex prop to control the icon button's position in the keyboard navigation order. The default
value allows normal tab navigation.
>,
<>
Provide disabledReason prop when disabling an icon button to give users context about why the button
is unavailable. This will be displayed in the tooltip.
>
]}
/>
## Variants
### Kinds
There can be more than one button in a screen, but to create the hierarchy of actions we need to use button kinds.
### Sizes
### Active
### Disabled
Set disable button for something that isn’t usable. Use a tooltip on hover in order to indicate the reason of the disabled action.
## Do’s and Don’ts
),
description:
"Use Icon button when action is lower priority than a regular action or there’s no space available to place a button."
},
negative: {
component: ,
description: (
<>
Don’t use Icon button as the main action on the page. Instead, use the{" "}
Button component with an icon.
>
)
}
},
{
positive: {
component: ,
description: "Always provide ariaLabel or tooltipContent"
},
negative: {
component: ,
description: "Don’t use icon button without adding a tooltip while hovering."
}
}
]}
/>
## Use cases and examples
### Icon button as toolbar button
### Icon button as close button
## Related components
================================================
FILE: packages/docs/src/pages/components/IconButton/IconButton.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipCheckYourself = () => (
To display icons that do not have actions associated with them, use the{" "}
Icon component
.
);
export const TipInfo = () => (
If you need to use an icon as a button that opens info dialog with additional information, check out{" "}
Info
{" "}
component.
);
export const TipMenuButton = () => (
If you need to use an icon as a button that opens menu next to it, check out{" "}
Menu button
{" "}
component.
);
================================================
FILE: packages/docs/src/pages/components/IconButton/IconButton.stories.tsx
================================================
import React from "react";
import { createComponentTemplate, Link } from "vibe-storybook-components";
import { IconButton, Text, Flex, Button, Box, Divider, Avatar, Icon } from "@vibe/core";
import person1 from "../Avatar/assets/person1.png";
import { Add, Bolt, CloseSmall, Doc, Drag, Filter, Item, Robot, Time } from "@vibe/icons";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { type Meta, type StoryObj } from "@storybook/react";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: IconButton,
iconPropNamesArray: ["icon"],
actionPropsArray: ["onClick"]
});
const iconButtonTemplate = createComponentTemplate(IconButton);
export default {
title: "Components/IconButton",
component: IconButton,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
} satisfies Meta;
export const Overview: Story = {
render: iconButtonTemplate.bind({}),
args: {
id: "overview-icon-button",
"aria-label": "Add",
icon: Add
},
parameters: {
docs: {
liveEdit: {
isEnabled: false
}
}
}
};
export const Kinds: Story = {
render: () => (
),
parameters: {
docs: {
liveEdit: {
scope: { Doc }
}
}
}
};
export const IconButtonAsToolbarButton: Story = {
render: () => (
Widget name
),
parameters: {
docs: {
liveEdit: {
scope: { Drag, Filter }
}
}
},
name: "Icon button as toolbar button"
};
export const IconButtonAsCloseButton: Story = {
render: () => (
Item
Hadas Farhi
deleted the item
Hello World
from the board
Tasks
13m(Available for restore in the next 1M)
Restore
),
parameters: {
docs: {
liveEdit: {
scope: { person1, Item, Time, CloseSmall }
}
}
},
name: "Icon button as close button"
};
================================================
FILE: packages/docs/src/pages/components/Info/Info.mdx
================================================
import { Meta } from "@storybook/blocks";
import { Frame, ComponentRules, UsageGuidelines } from "vibe-storybook-components";
import * as InfoStories from "./Info.stories";
import do1 from "./assets/do1.png";
import do2 from "./assets/do2.png";
import dont1 from "./assets/dont1.png";
import dont2 from "./assets/dont2.png";
import usecaseboard from "./assets/usecaseboard.png";
import usecasecolumn from "./assets/usecasecolumn.png";
import styles from "./Info.stories.module.scss";
# Info
An info component is a contextual container that provides supplemental information to help users understand related content
## Import path
```tsx
import { Info } from "@vibe/core";
```
## Overview
## Props
## Usage
## Accessibility
Provide an id for the Info component to enable proper accessibility associations and unique
identification.
>,
<>
You should provide an accessible name using the aria-label prop to describe the purpose of the info
button (e.g., "More information about this feature").
>,
<>
Use aria-labelledby prop when an external element provides the accessible name for the info button.
Pass the id of that external element (instead of using aria-label).
>,
<>
The component automatically manages aria-controls, aria-haspopup="dialog", and{" "}
aria-expanded attributes to indicate the relationship with the dialog and its current state.
>,
<>
Focus management is handled automatically - when the dialog opens, focus moves to the content, and when closed
with Escape, focus returns to the info button.
>
]}
/>
## Variants
### Directions
Info component dialog can appear from top, bottom, left or right.
## Do's and Don'ts
,
description: "Place Info component in location with context"
},
negative: {
component: ,
description: "Place it in an location without context"
}
},
{
className: styles.container,
componentContainerClassName: styles.largeComponentRule,
positive: {
component: ,
description: "Use when the supporting text is longer than a few words and requires more context"
},
negative: {
component: (
),
description:
"Use when the supporting text is brief and can be explained in just a few words. Use a tooltip instead."
}
}
]}
/>
## Use cases and examples
### In board header
Used for header description
### In board column
Used for column description
## Related Components
================================================
FILE: packages/docs/src/pages/components/Info/Info.stories.module.scss
================================================
.container {
display: flex;
flex-direction: column;
}
.largeComponentRule {
height: fit-content !important;
width: 100%;
padding: var(--sb-spacing-large);
&:has(.small) {
flex: 1;
}
img {
height: 250px;
width: 100%;
object-fit: contain;
object-position: center;
&.small {
height: 150px;
}
}
}
.useCase {
display: flex;
img {
max-width: 70%;
margin: auto;
}
}
================================================
FILE: packages/docs/src/pages/components/Info/Info.stories.tsx
================================================
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { Info, Flex, Text, Box } from "@vibe/core";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: Info,
enumPropNamesArray: ["position"],
actionPropsArray: ["onDialogShow", "onDialogHide"]
});
export default {
title: "Components/Info",
component: Info,
argTypes: {
...metaSettings.argTypes
},
decorators: metaSettings.decorators
} satisfies Meta;
export const Overview: Story = {
render: args => ,
args: {
id: "overview-info",
body: "Message will appear here, to give more context related information. This is meant for detailed supplemental information, where tooltips do not suffice. This is not the place for critical information for task-completion but rather paragraph-like text serving as supplemental info.",
link: { text: "Learn more about content", href: "#" },
title: "Placement: Bottom start",
"aria-label": "Overview information"
},
parameters: {
docs: {
liveEdit: { isEnabled: false }
}
}
};
export const Directions: Story = {
render: () => (
Bottom
Left
Right
Top
)
};
export const WithCustomLink: Story = {
render: () => (
)
};
export const Disabled: Story = {
render: () => (
)
};
================================================
FILE: packages/docs/src/pages/components/Label/Label.mdx
================================================
import { Meta } from "@storybook/blocks";
import { Link } from "vibe-storybook-components";
import { Label } from "@vibe/core";
import * as LabelStories from "./Label.stories";
import { TipCheckYourself } from "./Label.stories.helpers";
# Label
A label indicates the status of an item.
### Import
```js
import { Label } from "@vibe/core";
```
## Props
## Usage
## Variants
### Kinds
### Size
Label can appear in 2 sizes: small and medium.
### Colors
### Clickable
## Do’s and Don’ts
,
description: "Use label to indicate the status of an item, for example: “New”."
},
negative: {
component: ,
description: (
<>
Don’t use the label component in order to indicate numbers, instead use the{" "}
Counter.
>
)
}
},
{
positive: {
component: (
Categories
),
description: "Use label only once per item."
},
negative: {
component: [
Categories
],
description: (
<>
Don’t use multiple labels for one item. Instead, use{" "}
Chips.
>
)
}
},
{
positive: {
component: [, ],
description: "Use only the UI colors above."
},
negative: {
component: [
,
],
description: "Don’t use any content colors for labels. If the page is full of CTAs, use the outline state."
}
}
]}
/>
## Use cases and examples
### Secondary label
In case of visual overload, use the secondary label in order to create hirarchy.
### Celebration
To celebrate new feature, outline label can be highlighted by adding celebrate animation.
## Related components
================================================
FILE: packages/docs/src/pages/components/Label/Label.stories.helpers.tsx
================================================
import React from "react";
import { StorybookLink, Tip } from "vibe-storybook-components";
export const TipCheckYourself = () => (
Need to count or indicate numbers? Use the{" "}
Counter
{" "}
component instead.
);
================================================
FILE: packages/docs/src/pages/components/Label/Label.stories.tsx
================================================
import React from "react";
import { Label, Button, Flex, Box, Heading, Text } from "@vibe/core";
import { createStoryMetaSettingsDecorator } from "../../../utils/createStoryMetaSettingsDecorator";
import { createComponentTemplate } from "vibe-storybook-components";
import { useEffect, useState } from "react";
import { type Decorator, type StoryObj } from "@storybook/react";
type Story = StoryObj;
const metaSettings = createStoryMetaSettingsDecorator({
component: Label
});
export default {
title: "Components/Label",
component: Label,
argTypes: metaSettings.argTypes,
decorators: metaSettings.decorators
};
const withGrid: Decorator = Story => (