Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
91960d9
feat: add multi-select on envs and refactor layout
talissoncosta Nov 4, 2025
be6f826
refactor: extract logic and clean up
talissoncosta Nov 4, 2025
1414794
fix: prevent breaking input on multi-select
talissoncosta Nov 4, 2025
437e0da
refactor: split components into smaller files and keep the selected o…
talissoncosta Nov 5, 2025
4e2c074
fix: fix operation transitions values
talissoncosta Nov 5, 2025
47bacc4
fix: use button to handle tag removal
talissoncosta Nov 5, 2025
164e985
feat: update placeholder
talissoncosta Nov 5, 2025
f201a9e
feat: add inline prop on multi-select and handle it as text
talissoncosta Nov 6, 2025
be67301
fix: revert layout change on name and description fields
talissoncosta Nov 6, 2025
3142e7b
fix: prevents placeholder wrap
talissoncosta Nov 6, 2025
c34752a
fix: search and wrap
talissoncosta Nov 6, 2025
14bae64
fix: checkmark icon size on option with large text
talissoncosta Nov 6, 2025
b280454
feat: add native tooltip on value and center action buttons
talissoncosta Nov 6, 2025
cbb66d6
fix: prevent row to grow with multiple values selected
talissoncosta Nov 6, 2025
5149d8b
fix: arrow key navigation highlight
talissoncosta Nov 6, 2025
dae8bc0
fix: handle undefined values in segment rule operator changes
talissoncosta Nov 7, 2025
dfa4a69
refactor: avoid nested ternary on inline multi value format
talissoncosta Nov 10, 2025
9f55d66
fix: custom fields full width
talissoncosta Nov 10, 2025
42372e0
refactor: replace margin with gap
talissoncosta Nov 10, 2025
3320794
fix: handle null on multi-select onChange
talissoncosta Nov 10, 2025
8325e20
fix: revert IN operator to legacy CSV format for API compatibility
talissoncosta Nov 10, 2025
1fe68d7
feat: add initial storybook structure and multi select stories
talissoncosta Nov 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ module.exports = {
'es6': true,
'node': true,
},
'extends': [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'plugin:@dword-design/import-alias/recommended',
],
'extends': ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:@dword-design/import-alias/recommended', 'plugin:storybook/recommended'],
'globals': {
'$': true,
'$crisp': true,
Expand Down
3 changes: 3 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ common/project.js

# Sentry Config File
.env.sentry-build-plugin

*storybook.log
storybook-static
45 changes: 45 additions & 0 deletions frontend/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import path from 'path'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

/** @type { import('@storybook/react-webpack5').StorybookConfig } */
const config = {
"stories": [
"../stories/**/*.mdx",
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../documentation/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@storybook/addon-webpack5-compiler-swc",
"@storybook/addon-docs"
],
"framework": {
"name": "@storybook/react-webpack5",
"options": {}
},
webpackFinal: async (config) => {
// Add path aliases
config.resolve.alias = {
...config.resolve.alias,
'common': path.resolve(__dirname, '../common'),
'components': path.resolve(__dirname, '../web/components'),
'project': path.resolve(__dirname, '../web/project'),
}

// Add SCSS support
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
],
include: path.resolve(__dirname, '../'),
})

return config
},
};
export default config;
17 changes: 17 additions & 0 deletions frontend/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** @type { import('@storybook/react-webpack5').Preview } */

// Import global styles
import '../web/styles/styles.scss'

const preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};

export default preview;
2 changes: 2 additions & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type Operator = {
hideValue?: boolean
warning?: string
valuePlaceholder?: string
append?: string
type?: string
}
export type ChangeRequestSummary = {
id: number
Expand Down
36 changes: 36 additions & 0 deletions frontend/documentation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Component Documentation

This directory contains Storybook stories and documentation for our components.

## Structure

```
documentation/
└── components/ # Component stories organized by component
└── multi-select/
└── MultiSelect.stories.tsx
```

## Adding New Component Stories

To add stories for a new component:

1. Create a new directory under `components/` with the component name
2. Add a `.stories.tsx` file (e.g., `ComponentName.stories.tsx`)
3. The stories will automatically be picked up by Storybook

## Running Storybook

```bash
npm run storybook
```

This will start the Storybook dev server at `http://localhost:6006/`

## Building Storybook

```bash
npm run build-storybook
```

This will create a static build of the Storybook in the `storybook-static` directory.
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import type { Meta, StoryObj } from '@storybook/react'
import { MultiSelect } from '../../../web/components/base/select/multi-select/MultiSelect'
import React, { useState } from 'react'

const meta: Meta<typeof MultiSelect> = {
title: 'Components/MultiSelect',
component: MultiSelect,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
}

export default meta
type Story = StoryObj<typeof MultiSelect>

// Wrapper component to handle state
const MultiSelectWrapper = (args: any) => {
const [selectedValues, setSelectedValues] = useState<string[]>(
args.selectedValues || []
)

return (
<div style={{ width: '400px' }}>
<MultiSelect
{...args}
selectedValues={selectedValues}
onSelectionChange={setSelectedValues}
/>
</div>
)
}

// Basic example with simple options
export const Default: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
options: [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' },
{ label: 'Option 4', value: 'option4' },
{ label: 'Option 5', value: 'option5' },
],
placeholder: 'Select options...',
selectedValues: [],
hideSelectedOptions: true,
},
}

// With label
export const WithLabel: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
...Default.args,
label: 'Choose your options',
},
}

// With colors
export const WithColors: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
options: [
{ label: 'Red', value: 'red' },
{ label: 'Green', value: 'green' },
{ label: 'Blue', value: 'blue' },
{ label: 'Yellow', value: 'yellow' },
{ label: 'Purple', value: 'purple' },
],
colorMap: new Map([
['red', '#EF4444'],
['green', '#10B981'],
['blue', '#3B82F6'],
['yellow', '#F59E0B'],
['purple', '#8B5CF6'],
]),
placeholder: 'Select colors...',
selectedValues: [],
hideSelectedOptions: true,
},
}

// Pre-selected values
export const WithPreselectedValues: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
...WithColors.args,
selectedValues: ['red', 'blue'],
},
}

// Disabled state
export const Disabled: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
...Default.args,
disabled: true,
selectedValues: ['option1', 'option2'],
},
}

// Small size
export const SmallSize: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
...Default.args,
size: 'small',
label: 'Small size select',
},
}

// Large size
export const LargeSize: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
...Default.args,
size: 'large',
label: 'Large size select',
},
}

// Show selected options in dropdown
export const ShowSelectedOptions: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
...Default.args,
hideSelectedOptions: false,
selectedValues: ['option1', 'option2'],
},
}

// Inline display
export const InlineDisplay: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
...Default.args,
inline: true,
selectedValues: ['option1', 'option2', 'option3'],
},
}

// Many options
export const ManyOptions: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
options: Array.from({ length: 50 }, (_, i) => ({
label: `Option ${i + 1}`,
value: `option${i + 1}`,
})),
placeholder: 'Search and select...',
selectedValues: [],
label: 'Select from many options',
},
}

// Long option labels
export const LongLabels: Story = {
render: (args) => <MultiSelectWrapper {...args} />,
args: {
options: [
{
label: 'This is a very long option label that might overflow',
value: 'long1',
},
{
label: 'Another extremely long label to test text overflow behavior',
value: 'long2',
},
{
label: 'Short label',
value: 'short',
},
],
placeholder: 'Select options...',
selectedValues: [],
},
}
Loading
Loading