Skip to content
Merged
530 changes: 268 additions & 262 deletions src/App.css

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
.button {
background: none;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
fill: var(--text-color);
}

@media screen and (max-device-width: 1090px) {
.button {
padding: 6px;
font-size: var(--fs-s);
}
}

.tooltip {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: hsla(0, 0%, 20%, 0.7);
border-radius: 3px;
color: var(--text-color);
font-size: 14px;
line-height: 14px;
padding: 7px 14px;
position: absolute;
transform: translate(0%, 90px);
visibility: hidden;
z-index: 2;
}

.tooltipBottom {
transform: translate(0%, 90px);
}

.tooltipTop {
transform: translate(0%, -90px);
}

.tooltipTopLeft {
transform: translate(-50%, -90px);
}

@media (hover: hover) {
.button:hover .tooltip {
animation: fadeIn 1s linear;
animation-fill-mode: forwards;
}
}

@media screen and (max-device-width: 1090px) {
.tooltip {
display: none;
}
}

@keyframes fadeIn {
0% {
opacity: 0;
}
75% {
opacity: 0;
}
100% {
opacity: 1;
visibility: visible;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { ToolbarButton } from './toolbarButton.component';
import { vi } from 'vitest';
import { ShortcutOptions } from '../../shortcut/shortcut.model';
import { ActionButton } from './action-button.component';
import { ShortcutOptions } from '@/common/shortcut';

describe('ToolbarButton', () => {
describe('ActionButton', () => {
let onClick: () => void;
let shortcutOptions: ShortcutOptions;

Expand All @@ -25,7 +25,7 @@ describe('ToolbarButton', () => {

it('should render the button with the provided label and icon', () => {
render(
<ToolbarButton
<ActionButton
icon={<span>Icon</span>}
label="Label"
onClick={onClick}
Expand All @@ -39,7 +39,7 @@ describe('ToolbarButton', () => {

it('should call the onClick callback when the button is clicked', () => {
const { getByText } = render(
<ToolbarButton
<ActionButton
icon={<span>Icon</span>}
label="Label"
onClick={onClick}
Expand All @@ -54,7 +54,7 @@ describe('ToolbarButton', () => {

it('should render the tooltip with the correct shortcut key', () => {
const { getByRole } = render(
<ToolbarButton
<ActionButton
icon={<span>Icon</span>}
label="Label"
onClick={onClick}
Expand All @@ -69,7 +69,7 @@ describe('ToolbarButton', () => {

it('should disable the button if the disabled prop is true', () => {
const { getByText } = render(
<ToolbarButton
<ActionButton
icon={<span>Icon</span>}
label="Label"
onClick={onClick}
Expand All @@ -82,4 +82,47 @@ describe('ToolbarButton', () => {

expect(button).toHaveProperty('disabled', true);
});

it('should hide the label when showLabel is false', () => {
render(
<ActionButton
icon={<span>Icon</span>}
label="Label"
onClick={onClick}
showLabel={false}
shortcutOptions={shortcutOptions}
/>
);

expect(screen.queryByText('Label')).toBeNull();
});

it('should apply tooltipBottom by default when tooltipPosition is not provided', () => {
const { getByRole } = render(
<ActionButton
icon={<span>Icon</span>}
label="Label"
onClick={onClick}
shortcutOptions={shortcutOptions}
/>
);

const tooltip = getByRole('tooltip');
expect(tooltip.className).toContain('tooltipBottom');
});

it('should apply tooltipTop class when tooltipPosition is top', () => {
const { getByRole } = render(
<ActionButton
icon={<span>Icon</span>}
label="Label"
onClick={onClick}
tooltipPosition="top"
shortcutOptions={shortcutOptions}
/>
);

const tooltip = getByRole('tooltip');
expect(tooltip.className).toContain('tooltipTop');
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import useShortcut from '../../shortcut/shortcut.hook';
import { isMacOS } from '@/common/helpers/platform.helpers';
import { ShortcutOptions } from '../../shortcut/shortcut.model';
import classes from './action-button.component.module.css';
import { ShortcutOptions } from '@/common/shortcut';
import useShortcut from '@/common/shortcut/shortcut.hook';

interface Props {
icon?: React.ReactNode;
Expand All @@ -10,20 +11,33 @@ interface Props {
className?: string;
disabled?: boolean;
shortcutOptions?: ShortcutOptions;
showLabel?: boolean;
tooltipPosition?: 'top' | 'bottom';
}

export const ToolbarButton: React.FC<Props> = ({
export const ActionButton: React.FC<Props> = ({
disabled,
icon,
onClick = () => {},
className,
label,
shortcutOptions,
showLabel = true,
tooltipPosition = 'bottom',
}) => {
const shortcutCommand = isMacOS() ? 'Ctrl' : 'Alt';
const showTooltip = shortcutOptions && !disabled;
const tooltipText = `(${shortcutCommand} + ${shortcutOptions?.targetKeyLabel})`;

const tooltipPositionClass =
tooltipPosition === 'top' ? classes.tooltipTop : classes.tooltipBottom;

const tooltipClasses = `${classes.tooltip} ${tooltipPositionClass}`;

const buttonClasses = className
? `${classes.button} ${className}`.trim()
: classes.button;

useShortcut({
...shortcutOptions,
targetKey: shortcutOptions?.targetKey || [],
Expand All @@ -32,15 +46,19 @@ export const ToolbarButton: React.FC<Props> = ({

return (
<button
className={className}
className={buttonClasses}
onClick={onClick}
disabled={disabled === true}
aria-describedby={shortcutOptions?.id}
>
<span aria-hidden={true}>{icon}</span>
<span>{label}</span>
{showLabel && <span>{label}</span>}
{showTooltip && (
<span role="tooltip" id={shortcutOptions?.id}>
<span
className={tooltipClasses}
role="tooltip"
id={shortcutOptions?.id}
>
{tooltipText}
</span>
)}
Expand Down
1 change: 1 addition & 0 deletions src/common/components/action-button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './action-button.component';
3 changes: 3 additions & 0 deletions src/common/shortcut/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './shortcut.const';
export * from './shortcut.hook';
export * from './shortcut.model';
2 changes: 2 additions & 0 deletions src/pods/footer/components/zoom-button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './zoom-in-button.component';
export * from './zoom-out-button.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.button :global([role='tooltip']) {
transform: translate(-60%, -90px);
white-space: nowrap;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useCanvasViewSettingsContext } from '@/core/providers';
import { ZoomIn } from '@/common/components/icons';
import { ActionButton } from '@/common/components/action-button';
import { SHORTCUTS } from '@/common/shortcut/shortcut.const';
import classes from './zoom-in-button.component.module.css';

const MINIMUM_ZOOM_FACTOR_ALLOWED = 2.5;

export const ZoomInButton = () => {
const { zoomIn, canvasViewSettings } = useCanvasViewSettingsContext();

return (
<ActionButton
icon={<ZoomIn />}
label="Zoom In"
onClick={zoomIn}
className={`${classes.button} hide-mobile`}
disabled={canvasViewSettings.zoomFactor < MINIMUM_ZOOM_FACTOR_ALLOWED}
shortcutOptions={SHORTCUTS.zoomIn}
showLabel={false}
tooltipPosition="top"
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useCanvasViewSettingsContext } from '@/core/providers';
import { ZoomOut } from '@/common/components/icons/zoom-out-icon.component';
import { ActionButton } from '@/common/components/action-button';
import { SHORTCUTS } from '@/common/shortcut/shortcut.const';

const MAXIMUM_ZOOM_FACTOR_ALLOWED = 7.8;

export const ZoomOutButton = () => {
const { zoomOut, canvasViewSettings } = useCanvasViewSettingsContext();

return (
<ActionButton
icon={<ZoomOut />}
label="Zoom Out"
onClick={zoomOut}
className="hide-mobile"
disabled={canvasViewSettings.zoomFactor > MAXIMUM_ZOOM_FACTOR_ALLOWED}
shortcutOptions={SHORTCUTS.zoomOut}
showLabel={false}
tooltipPosition="top"
/>
);
};
5 changes: 5 additions & 0 deletions src/pods/footer/footer.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getFileNameCanvasIsPristine,
getFileNameCanvasDirty,
} from '@/pods/footer/footer.business';
import { ZoomInButton, ZoomOutButton } from './components/zoom-button';

const NEW_DOCUMENT_NAME = 'New Document';
const ASTERISK = '*';
Expand All @@ -31,6 +32,10 @@ export const FooterComponent: React.FC = () => {
return (
<footer className={classes.footerText}>
<span>{isDevice ? documentNameMobile() : documentName()}</span>
<div className={classes.zoomButtons}>
<ZoomOutButton />
<ZoomInButton />
</div>
<button onClick={toggleTheme} className="mobile-only">
{theme.themeMode === 'dark' ? <LightIcon /> : <DarkIcon />}
</button>
Expand Down
8 changes: 6 additions & 2 deletions src/pods/footer/footer.pod.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
background-color: var(--footer-background);
color: var(--footer-text-color);
text-align: left;
padding: var(--space-unit) var(--space-md);
padding-right: var(--space-xs);
padding: var(--space-xs) var(--space-md);
font-size: var(--fs-xs);
}

.zoomButtons {
display: flex;
gap: var(--space-xs);
}

@media (max-width: 1090px) {
.footer-text {
font-size: var(--fs-s);
Expand Down
6 changes: 2 additions & 4 deletions src/pods/toolbar/components/about-button/about-button.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useModalDialogContext } from '@/core/providers/modal-dialog-provider';
import { AboutIcon } from '@/common/components/icons';
import { ToolbarButton } from '@/pods/toolbar/components/toolbar-button';
import classes from '@/pods/toolbar/toolbar.pod.module.css';
import { ABOUT_TITLE } from '@/common/components';
import { AboutPod } from '@/pods/about';
import { ActionButton } from '@/common/components/action-button';

export const AboutButton = () => {
const { openModal } = useModalDialogContext();
Expand All @@ -13,11 +12,10 @@ export const AboutButton = () => {
};

return (
<ToolbarButton
<ActionButton
icon={<AboutIcon />}
label="About us"
onClick={handleRelationClick}
className={classes.button}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useModalDialogContext } from '@/core/providers/modal-dialog-provider';
import { EditTablePod } from '@/pods/edit-table';
import { ToolbarButton } from '../toolbar-button/toolbarButton.component';
import { TableIcon } from '@/common/components/icons';
import { useCanvasViewSettingsContext } from '@/core/providers';
import classes from '@/pods/toolbar/toolbar.pod.module.css';

import {
TableVm,
useCanvasSchemaContext,
} from '@/core/providers/canvas-schema';
import { ADD_COLLECTION_TITLE } from '@/common/components/modal-dialog';
import { SHORTCUTS } from '../../shortcut/shortcut.const';
import { ActionButton } from '@/common/components/action-button';
import { SHORTCUTS } from '@/common/shortcut';

const BORDER_MARGIN = 40;

Expand Down Expand Up @@ -44,11 +44,11 @@ export const AddCollection = () => {
closeModal();
};
return (
<ToolbarButton
<ActionButton
icon={<TableIcon />}
label="Add Collection"
onClick={handleEditTableClick}
className={`${classes.button} hide-mobile`}
className="hide-mobile"
shortcutOptions={SHORTCUTS.addCollection}
/>
);
Expand Down
Loading