Componentization
The components can be:
Simple
or Complex
and Visual
or Logical
The components can live on:
Application
or Fuselage Library
The components rules matrix
Text | Fuselage Level | Application Level |
---|---|---|
Simple & Visual | ✅ | ❌ |
Complex & Visual | ✅ | ✅ |
Simple & Logical | ❌ | ❌ |
Complex & Logical | ❌ | ✅ |
This is the lowest level of your component, a unique part of an interface. A good example of this is the
Button
ComponentInstead of using style based props names e.g. Blue or Gray, uses names that suggest the variation of your component e.g Primary or Secondary

✅ Correct example, passing the
small
and square
prop that will handle the size of the component<Button small square>
<Icon name='circle-arrow-down' size='x24' />
</Button>
❌ Wrong example, using magic numbers to define the size of the component
<Button height='50px' width='50px' square>
<Icon name='circle-arrow-down' size='x24' />
</Button>
It's not appropriate using CSS random values, do prefer to use customization using CSS-vars
$modal-margin: theme('modal-margin', auto);
.rcx-modal {
position: static;
display: flex;
width: 100%;
max-height: 100%;
margin: $modal-margin;
}
It's important to expose each variation to allow developers or designers to check the existing possibilities.
Also, it should have a description, if the variation, isn't self-explanatory

Guarantee that all the planned behaviors be covered by unit tests
describe('[Menu Component]', () => {
const menuOption = screen.queryByText('Make Admin');
it('should renders without crashing', () => {
render(<Simple {...Simple.args} />);
});
it('should open options when click', async () => {
const { getByTestId } = render(<Simple {...Simple.args} />);
const button = getByTestId('menu');
userEvent.click(button);
expect(await screen.findByText('Make Admin')).toBeInTheDocument();
});
it('should have no options when click twice', async () => {
const { getByTestId } = render(<Simple {...Simple.args} />);
const button = getByTestId('menu');
userEvent.click(button);
userEvent.click(button);
expect(menuOption).toBeNull();
});
it('should have no options when click on menu and then elsewhere', async () => {
const { getByTestId } = render(<Simple {...Simple.args} />);
const button = getByTestId('menu');
userEvent.click(button);
userEvent.click(document.body);
expect(menuOption).toBeNull();
});
});
The usage of
Box
is recommended for Simple or Complex Components mainly (save the cases when we need to quickly prototype a component) on the Application Level as it's a wildcard component, for simple components, we suggest avoiding it and building the component using HTML tagsA mix of simple components results in a complex component
Handle just the user interface and left it prepared to receive the logic

export const Default = () => {
<Modal>
<Modal.Header>
<Modal.HeaderText>
<Modal.Title>Modal Header</Modal.Title>
</Modal.HeaderText>
<Modal.Close />
</Modal.Header>
<Modal.Content>Modal Body</Modal.Content>
<Modal.Footer>
<Modal.FooterControllers>
<Button>Cancel</Button>
<Button primary onClick={action('click')}>
Submit
</Button>
</Modal.FooterControllers>
</Modal.Footer>
</Modal>
};
export const CallingDM: ComponentStory<typeof VideoConfMessage> = () => (
<VideoConfMessage>
<VideoConfMessageRow>
<VideoConfMessageIcon variant='incoming' />
<VideoConfMessageText>Calling...</VideoConfMessageText>
</VideoConfMessageRow>
<VideoConfMessageFooter>
<VideoConfMessageAction primary>Join</VideoConfMessageAction>
<VideoConfMessageFooterText>Waiting for answer</VideoConfMessageFooterText>
</VideoConfMessageFooter>
</VideoConfMessage>
);
export const CallEndedDM: ComponentStory<typeof VideoConfMessage> = () => (
<VideoConfMessage>
<VideoConfMessageRow>
<VideoConfMessageIcon />
<VideoConfMessageText>Call ended</VideoConfMessageText>
</VideoConfMessageRow>
<VideoConfMessageFooter>
<VideoConfMessageAction>Call Back</VideoConfMessageAction>
<VideoConfMessageFooterText>Call was not answered</VideoConfMessageFooterText>
</VideoConfMessageFooter>
</VideoConfMessage>
);
❌ Wrong example on composing components
export const MyComponent: ComponentStory<typeof VideoConfMessage> = () => (
<Box display='flex'>
<form>
<VideoConfMessageAction>Call ended</VideoConfMessageAction>
</form>
</Box>
);
✅ Correct example of composing components
export const MyComponent: ComponentStory<typeof VideoConfMessage> = () => (
<Box display='flex'>
<form>
<VideoConfMessage>
<VideoConfMessageFooter>
<VideoConfMessageAction>Call ended</VideoConfMessageAction>
</VideoConfMessageFooter>
</VideoConfMessage>
</form>
</Box>
);
❌ Wrong example on composing components
export const VideoConfMessage: ComponentStory<typeof VideoConfMessage> = () => (
<Box mbs='x4' maxWidth='345px' borderWidth={2} borderColor='neutral-200' borderRadius='x4'>
<Box p='x16' display='flex' alignItems='center'>
<Icon name='link' />
<div>My Text</div>
</Box>
</Box>
);
✅ Correct example of composing components
const VideoConfMessage = ({ ...props }): ReactElement => (
<Box mbs='x4' maxWidth='345' borderWidth={2} borderColor='neutral-200' borderRadius='x4' {...props} />
);
In the example below the
useVideoConfControllers
was provided to control the state of the popup's controllersexport const useVideoConfControllers = (
initialPreferences: controllersConfigProps = { mic: true, cam: false },
): { controllersConfig: controllersConfigProps; handleToggleMic: () => void; handleToggleCam: () => void } => {
const [controllersConfig, setControllersConfig] = useState(initialPreferences);
const handleToggleMic = useCallback((): void => {
setControllersConfig((prevState) => ({ ...prevState, mic: !prevState.mic }));
}, []);
const handleToggleCam = useCallback((): void => {
setControllersConfig((prevState) => ({ ...prevState, cam: !prevState.cam }));
}, []);
return {
controllersConfig,
handleToggleMic,
handleToggleCam,
};
};
const { controllersConfig } = useVideoConfControllers();
return (
<VideoConfPopup>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Calling')} counter />
<VideoConfPopupControllers>
<VideoConfController
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
disabled
/>
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
disabled
/>
</VideoConfPopupControllers>
</VideoConfPopupHeader>
</VideoConfPopup>
);
Usually, a new component is born based on the needing of the Product Design Team, and it's the responsibility of the front-end engineer to verify the real need of this new component. A new component generates a big effort, so it's highly recommended to validate with the product manager and the designers involved the possibility of using Complex Components as an MVP to validate the new idea and the user flow and only after, moving on to creating a new component on Fuselage Level.
How do I know my component should be part of the Fuselage library?
A good example of this process is the
VerticalBar
the component which started as a Complex Component on the Application Level and now we're moving it to the Fuselage Level because more than one application can benefit from the component. E.g Rocket.Chat Application and Cloud Portalconst OutgoingPopup = ({ room, onClose, id }: OutgoingPopupProps): ReactElement => {
const t = useTranslation();
const videoConfPreferences = useVideoConfPreferences();
const { controllersConfig } = useVideoConfControllers();
return (
<VideoConfPopup>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Calling')} counter />
<VideoConfPopupControllers>
<VideoConfController
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
disabled
/>
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
disabled
/>
</VideoConfPopupControllers>
</VideoConfPopupHeader>
</VideoConfPopup>
);
}
In development
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
disabled
/>
const VideoConfController = ({ icon, active, secondary, disabled, small = true, ...props }: VideoConfControllerProps): ReactElement => {
const id = useUniqueId();
return (
<IconButton
small={small}
icon={icon}
id={id}
info={active}
disabled={disabled}
secondary={secondary || active || disabled}
{...props}
/>
);
};
<VideoConfPopup>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Calling')} counter />
<VideoConfPopupControllers>
<Box display='flex' alignItems='center'>
<VideoConfController
width='50px'
height='50px'
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
disabled
/>
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
disabled
/>
</Box>
</VideoConfPopupControllers>
</VideoConfPopupHeader>
</VideoConfPopup>
const customClass = css`
border: 1px solid black;
padding: 1.5rem;
`;
return (
<VideoConfPopup>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Calling')} counter />
<VideoConfPopupControllers>
<VideoConfController
className={customClass}
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
disabled
/>
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
disabled
/>
</VideoConfPopupControllers>
</VideoConfPopupHeader>
</VideoConfPopup>
);
if (isReceiving) {
return <IncomingPopup room={room} id={id} position={position} onClose={onClose} onMute={handleMute} onConfirm={handleConfirm}
}
if (isCalling) {
return <OutgoingPopup room={room} id={id} onClose={onClose}
}
return <StartCallPopup loading={starting} room={room} id={id} onClose={dismissOutgoing} onConfirm={handleStartCall} />
Each state should render the proper Complex Component
const OutgoingPopup = ({ room, onClose, id }: OutgoingPopupProps): ReactElement => {
const t = useTranslation();
const videoConfPreferences = useVideoConfPreferences();
const { controllersConfig } = useVideoConfControllers();
return (
<VideoConfPopup>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Calling')} counter />
<VideoConfPopupControllers>
<VideoConfController
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
disabled
/>
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
disabled
/>
</VideoConfPopupControllers>
</VideoConfPopupHeader>
</VideoConfPopup>
);
}