summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/atoms/forms/forms.module.scss1
-rw-r--r--src/components/molecules/modals/modal.module.scss17
-rw-r--r--src/components/molecules/modals/modal.tsx25
-rw-r--r--src/components/organisms/modals/settings-modal.module.scss23
-rw-r--r--src/components/organisms/modals/settings-modal.test.tsx2
-rw-r--r--src/components/organisms/modals/settings-modal.tsx14
-rw-r--r--src/components/organisms/toolbar/settings.module.scss10
-rw-r--r--src/components/organisms/toolbar/settings.stories.tsx76
-rw-r--r--src/components/organisms/toolbar/settings.test.tsx18
-rw-r--r--src/components/organisms/toolbar/settings.tsx72
10 files changed, 241 insertions, 17 deletions
diff --git a/src/components/atoms/forms/forms.module.scss b/src/components/atoms/forms/forms.module.scss
index 279c185..19c7aee 100644
--- a/src/components/atoms/forms/forms.module.scss
+++ b/src/components/atoms/forms/forms.module.scss
@@ -3,6 +3,7 @@
.item {
margin: var(--spacing-xs) 0;
+ width: 100%;
max-width: 45ch;
}
diff --git a/src/components/molecules/modals/modal.module.scss b/src/components/molecules/modals/modal.module.scss
index 2fff562..8866834 100644
--- a/src/components/molecules/modals/modal.module.scss
+++ b/src/components/molecules/modals/modal.module.scss
@@ -1,4 +1,5 @@
@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
.wrapper {
padding: var(--spacing-md);
@@ -12,6 +13,22 @@
1;
box-shadow: fun.convert-px(2) fun.convert-px(-2) fun.convert-px(3)
fun.convert-px(-1) var(--color-shadow-dark);
+
+ @include mix.media("screen") {
+ @include mix.dimensions(null, "sm") {
+ padding: var(--spacing-xs);
+ border-left: none;
+ border-right: none;
+
+ .title {
+ margin-bottom: var(--spacing-2xs);
+ }
+ }
+
+ @include mix.dimensions("sm") {
+ max-width: 35ch;
+ }
+ }
}
.icon {
diff --git a/src/components/molecules/modals/modal.tsx b/src/components/molecules/modals/modal.tsx
index ce12e7a..52ada57 100644
--- a/src/components/molecules/modals/modal.tsx
+++ b/src/components/molecules/modals/modal.tsx
@@ -13,6 +13,10 @@ export type ModalProps = {
*/
className?: string;
/**
+ * Set additional classnames to the heading.
+ */
+ headingClassName?: string;
+ /**
* A icon to illustrate the modal.
*/
icon?: Icons;
@@ -22,9 +26,12 @@ export type ModalProps = {
title?: string;
};
-const CogIcon = dynamic<CogProps>(() => import('@components/atoms/icons/cog'));
+const CogIcon = dynamic<CogProps>(() => import('@components/atoms/icons/cog'), {
+ ssr: false,
+});
const SearchIcon = dynamic<MagnifyingGlassProps>(
- () => import('@components/atoms/icons/magnifying-glass')
+ () => import('@components/atoms/icons/magnifying-glass'),
+ { ssr: false }
);
/**
@@ -32,7 +39,13 @@ const SearchIcon = dynamic<MagnifyingGlassProps>(
*
* Render a modal component with an optional title and icon.
*/
-const Modal: FC<ModalProps> = ({ children, className = '', icon, title }) => {
+const Modal: FC<ModalProps> = ({
+ children,
+ className = '',
+ headingClassName = '',
+ icon,
+ title,
+}) => {
const getIcon = (id: Icons) => {
switch (id) {
case 'cogs':
@@ -47,7 +60,11 @@ const Modal: FC<ModalProps> = ({ children, className = '', icon, title }) => {
return (
<div className={`${styles.wrapper} ${className}`}>
{title && (
- <Heading isFake={true} level={3}>
+ <Heading
+ isFake={true}
+ level={3}
+ className={`${styles.title} ${headingClassName}`}
+ >
{icon && <span className={styles.icon}>{getIcon(icon)}</span>}
{title}
</Heading>
diff --git a/src/components/organisms/modals/settings-modal.module.scss b/src/components/organisms/modals/settings-modal.module.scss
index f17c9b3..ebae3da 100644
--- a/src/components/organisms/modals/settings-modal.module.scss
+++ b/src/components/organisms/modals/settings-modal.module.scss
@@ -1,14 +1,21 @@
-.wrapper {
- max-width: 30ch;
+@use "@styles/abstracts/mixins" as mix;
+.wrapper {
.label {
margin-right: auto;
}
-}
-.tooltip {
- width: 120%;
- top: calc(100% + var(--spacing-sm));
- right: -10%;
- transform-origin: top right;
+ @include mix.media("screen") {
+ @include mix.dimensions(null, "2xs", "height") {
+ font-size: var(--font-size-sm);
+
+ .heading {
+ font-size: var(--font-size-lg);
+ }
+
+ .label {
+ font-size: var(--font-size-sm);
+ }
+ }
+ }
}
diff --git a/src/components/organisms/modals/settings-modal.test.tsx b/src/components/organisms/modals/settings-modal.test.tsx
index 44695d7..6291e54 100644
--- a/src/components/organisms/modals/settings-modal.test.tsx
+++ b/src/components/organisms/modals/settings-modal.test.tsx
@@ -1,8 +1,6 @@
import { render, screen } from '@test-utils';
import SettingsModal from './settings-modal';
-jest.mock('next/dynamic', () => () => 'dynamic-import');
-
describe('SettingsModal', () => {
it('renders a theme toggle setting', () => {
render(<SettingsModal />);
diff --git a/src/components/organisms/modals/settings-modal.tsx b/src/components/organisms/modals/settings-modal.tsx
index 0fac332..25d6f6f 100644
--- a/src/components/organisms/modals/settings-modal.tsx
+++ b/src/components/organisms/modals/settings-modal.tsx
@@ -10,9 +10,13 @@ import styles from './settings-modal.module.scss';
export type SettingsModalProps = {
/**
- * Set additional classnames to modal wrapper.
+ * Set additional classnames to the modal wrapper.
*/
className?: string;
+ /**
+ * Set additional classnames to the tooltip wrapper.
+ */
+ tooltipClassName?: string;
};
/**
@@ -20,7 +24,10 @@ export type SettingsModalProps = {
*
* Render a modal with settings options.
*/
-const SettingsModal: VFC<SettingsModalProps> = ({ className }) => {
+const SettingsModal: VFC<SettingsModalProps> = ({
+ className = '',
+ tooltipClassName = '',
+}) => {
const intl = useIntl();
const title = intl.formatMessage({
defaultMessage: 'Settings',
@@ -33,6 +40,7 @@ const SettingsModal: VFC<SettingsModalProps> = ({ className }) => {
title={title}
icon="cogs"
className={`${styles.wrapper} ${className}`}
+ headingClassName={styles.heading}
>
<Form onSubmit={() => null}>
<ThemeToggle labelClassName={styles.label} value={false} />
@@ -41,7 +49,7 @@ const SettingsModal: VFC<SettingsModalProps> = ({ className }) => {
<AckeeSelect
initialValue="full"
labelClassName={styles.label}
- tooltipClassName={styles.tooltip}
+ tooltipClassName={tooltipClassName}
/>
</Form>
</Modal>
diff --git a/src/components/organisms/toolbar/settings.module.scss b/src/components/organisms/toolbar/settings.module.scss
new file mode 100644
index 0000000..08c8cd4
--- /dev/null
+++ b/src/components/organisms/toolbar/settings.module.scss
@@ -0,0 +1,10 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+
+.modal {
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ width: 120%;
+ }
+ }
+}
diff --git a/src/components/organisms/toolbar/settings.stories.tsx b/src/components/organisms/toolbar/settings.stories.tsx
new file mode 100644
index 0000000..f01e772
--- /dev/null
+++ b/src/components/organisms/toolbar/settings.stories.tsx
@@ -0,0 +1,76 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
+import { IntlProvider } from 'react-intl';
+import SettingsComponent from './settings';
+
+export default {
+ title: 'Organisms/Toolbar',
+ component: SettingsComponent,
+ argTypes: {
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classnames to the modal wrapper.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ isActive: {
+ control: {
+ type: null,
+ },
+ description: 'Define the modal state: either opened or closed.',
+ type: {
+ name: 'boolean',
+ required: true,
+ },
+ },
+ setIsActive: {
+ control: {
+ type: null,
+ },
+ description: 'A callback function to update modal state.',
+ type: {
+ name: 'function',
+ required: true,
+ },
+ },
+ tooltipClassName: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classnames to the tooltip wrapper.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta<typeof SettingsComponent>;
+
+const Template: ComponentStory<typeof SettingsComponent> = ({
+ isActive,
+ setIsActive: _setIsActive,
+ ...args
+}) => {
+ const [isOpen, setIsOpen] = useState<boolean>(isActive);
+
+ return (
+ <IntlProvider locale="en">
+ <SettingsComponent isActive={isOpen} setIsActive={setIsOpen} {...args} />
+ </IntlProvider>
+ );
+};
+
+export const Settings = Template.bind({});
+Settings.args = {
+ isActive: false,
+};
diff --git a/src/components/organisms/toolbar/settings.test.tsx b/src/components/organisms/toolbar/settings.test.tsx
new file mode 100644
index 0000000..96a32c9
--- /dev/null
+++ b/src/components/organisms/toolbar/settings.test.tsx
@@ -0,0 +1,18 @@
+import { render, screen } from '@test-utils';
+import Settings from './settings';
+
+describe('Settings', () => {
+ it('renders a button to open settings modal', () => {
+ render(<Settings isActive={false} setIsActive={() => null} />);
+ expect(
+ screen.getByRole('checkbox', { name: 'Open settings' })
+ ).toBeInTheDocument();
+ });
+
+ it('renders a button to close settings modal', () => {
+ render(<Settings isActive={true} setIsActive={() => null} />);
+ expect(
+ screen.getByRole('checkbox', { name: 'Close settings' })
+ ).toBeInTheDocument();
+ });
+});
diff --git a/src/components/organisms/toolbar/settings.tsx b/src/components/organisms/toolbar/settings.tsx
new file mode 100644
index 0000000..88539fb
--- /dev/null
+++ b/src/components/organisms/toolbar/settings.tsx
@@ -0,0 +1,72 @@
+import Checkbox, { CheckboxProps } from '@components/atoms/forms/checkbox';
+import Label from '@components/atoms/forms/label';
+import Cog from '@components/atoms/icons/cog';
+import { VFC } from 'react';
+import { useIntl } from 'react-intl';
+import SettingsModal from '../modals/settings-modal';
+import sharedStyles from './toolbar-items.module.scss';
+import settingsStyles from './settings.module.scss';
+
+export type SettingsProps = {
+ /**
+ * Set additional classnames to the modal wrapper.
+ */
+ className?: string;
+ /**
+ * The button state.
+ */
+ isActive: CheckboxProps['value'];
+ /**
+ * A callback function to handle button state.
+ */
+ setIsActive: CheckboxProps['setValue'];
+ /**
+ * Set additional classnames to the tooltip wrapper.
+ */
+ tooltipClassName?: string;
+};
+
+const Settings: VFC<SettingsProps> = ({
+ className = '',
+ isActive,
+ setIsActive,
+ tooltipClassName = '',
+}) => {
+ const intl = useIntl();
+ const label = isActive
+ ? intl.formatMessage({
+ defaultMessage: 'Close settings',
+ id: '+viX9b',
+ description: 'Settings: Close label',
+ })
+ : intl.formatMessage({
+ defaultMessage: 'Open settings',
+ id: 'QCW3cy',
+ description: 'Settings: Open label',
+ });
+
+ return (
+ <div className={`${sharedStyles.item} ${settingsStyles.item}`}>
+ <Checkbox
+ id="settings-button"
+ name="settings-button"
+ value={isActive}
+ setValue={setIsActive}
+ className={`${sharedStyles.checkbox} ${settingsStyles.checkbox}`}
+ />
+ <Label
+ htmlFor="settings-button"
+ aria-label={label}
+ className={`${sharedStyles.label} ${settingsStyles.label}`}
+ >
+ <Cog />
+ </Label>
+ <SettingsModal
+ className={`${sharedStyles.modal} ${settingsStyles.modal} ${className}`}
+ tooltipClassName={tooltipClassName}
+ />
+ </div>
+ );
+};
+
+export default Settings;