Skip to content

Commit

Permalink
create FeedbackError component (#265)
Browse files Browse the repository at this point in the history
* refactor: add icon props to ladle

* refactor: fix button error on ladle

* refactor: remove blank div and add FeedbackError component on home screen

* chore: create snippet to import scss files

* feat: create error message store

* feat: finished FeedbackError component

* refactor: change breakingpoint style

* refactor: turn store name more generic

* refactor: remove @use snippet

* refactor: change breakingpoint position to bottom

* test: fix failing test

* refactor: add error icon on icon component

* refactor: fix icon not showing on screen error

* refactor: add Icon component on button storie

* refactor: add icon component to show icon error

* refactor: add breakingpoint

* refactor: create a data file for feedbackComponent and fix ladle stories
  • Loading branch information
hxsggsz authored Dec 11, 2023
1 parent ab6c213 commit 0b0316c
Show file tree
Hide file tree
Showing 23 changed files with 231 additions and 41 deletions.
15 changes: 8 additions & 7 deletions src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Story } from '@ladle/react';

import Icon from '~components/Icon/Icon';

import Button from './Button';
import LocationIcon from './icons/LocationIcon';

export const ButtonStories: Story = () => (
<div>
Expand All @@ -17,22 +18,22 @@ export const ButtonStories: Story = () => (
Secondary Outlined
</Button>
<br />
<Button circle icon={<LocationIcon />} />
<Button variant="container" circle icon={<LocationIcon />} />
<Button variant="outlined" circle icon={<LocationIcon />} />
<Button circle icon={<Icon icon="at" />} />
<Button variant="container" circle icon={<Icon icon="pin" />} />
<Button variant="outlined" circle icon={<Icon icon="hashtag" />} />
<br />
<Button color="secondary" circle icon={<LocationIcon />} />
<Button color="secondary" circle icon={<Icon icon="error" />} />
<Button
color="secondary"
variant="container"
circle
icon={<LocationIcon />}
icon={<Icon icon="emote" />}
/>
<Button
color="secondary"
variant="outlined"
circle
icon={<LocationIcon />}
icon={<Icon icon="canva" />}
/>
</div>
);
18 changes: 0 additions & 18 deletions src/components/Button/icons/LocationIcon.jsx

This file was deleted.

16 changes: 16 additions & 0 deletions src/components/FeedbackError/FeedbackError.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const animationVariants = {
hidden: {
y: -100,
opacity: 0,
transition: {
type: 'tween',
},
},
visible: {
y: 0,
opacity: 1,
transition: {
type: 'tween',
},
},
};
38 changes: 38 additions & 0 deletions src/components/FeedbackError/FeedbackError.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@use '~styles/global.scss' as *;

.wrapper {
width: 100%;

display: flex;
gap: 1rem;

align-items: center;

margin-top: 3.2rem;
padding-left: 1rem;
border-left: 8px solid $secondaryRed;

background: $secondaryRed;

.errorIcon {
color: $primaryWhite;
}

.errorMessage {
color: $primaryWhite;
}
}

@include from599 {
.wrapper {
background: inherit;

.errorIcon {
color: $secondaryRed;
}

.errorMessage {
color: $secondaryRed;
}
}
}
17 changes: 17 additions & 0 deletions src/components/FeedbackError/FeedbackError.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render, screen } from '@testing-library/react';

import FeedbackError from './FeedbackError';

describe('FeedbackEror', () => {
describe('when initialize', () => {
describe('there is no error', () => {
it('renders nothing on screen', () => {
render(<FeedbackError />);

expect(
screen.queryByTestId(/error-container/i)
).not.toBeInTheDocument();
});
});
});
});
24 changes: 24 additions & 0 deletions src/components/FeedbackError/FeedbackError.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect } from 'react';

import type { Story } from '@ladle/react';
import { useError } from 'stores/useError/useError';

import FeedbackError from './FeedbackError';

interface IFeedbackErrorProps {
errorMessage: string;
}

export const FeedbackErrorComponent: Story<IFeedbackErrorProps> = (props) => {
const setErrorMessage = useError((state) => state.setErrorMessage);

useEffect(() => {
setErrorMessage(props.errorMessage);
}, [props.errorMessage]);

return <FeedbackError />;
};

FeedbackErrorComponent.args = {
errorMessage: 'generic error message',
};
29 changes: 29 additions & 0 deletions src/components/FeedbackError/FeedbackError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AnimatePresence, motion } from 'framer-motion';
import { useError } from 'stores/useError/useError';

import Icon from '~components/Icon/Icon';

import scss from './FeedbackError.module.scss';

import { animationVariants } from './FeedbackError.data';

function FeedbackError() {
const errorMessage = useError((state) => state.errorMessage);

const renderError = () => (
<motion.div
initial="hidden"
animate="visible"
exit="hidden"
className={scss.wrapper}
variants={animationVariants}
data-testid="error-container"
>
<Icon icon="error" size="large" className={scss.errorIcon} />
<p className={scss.errorMessage}>{errorMessage}</p>
</motion.div>
);
return <AnimatePresence>{errorMessage && renderError()}</AnimatePresence>;
}

export default FeedbackError;
10 changes: 9 additions & 1 deletion src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ import classNames from 'classnames';

import { colors, icons, sizes } from './data';

import scss from './icon.module.scss';

import { IIconProps } from './icon.types';

const Icon: React.FC<IIconProps> = ({ icon, color, size, className }) => {
const iconIcons = icon ? icons[icon] : '';
const iconColors = color ? colors[color] : '';
const iconSizes = size ? sizes[size] : '';

const iconClasses = classNames(iconIcons, iconSizes, iconColors, className);
const iconClasses = classNames(
scss.icon,
iconIcons,
iconSizes,
iconColors,
className
);

return <span data-testid="icon-element" className={iconClasses} />;
};
Expand Down
1 change: 1 addition & 0 deletions src/components/Icon/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const icons: Record<IIconProps['icon'], string> = {
pin: scss.iPin,
gpt: scss.iGpt,
canva: scss.iCanva,
error: scss.iError,
};

export const sizes = {
Expand Down
Binary file modified src/components/Icon/fonts/icomoon.eot
Binary file not shown.
1 change: 1 addition & 0 deletions src/components/Icon/fonts/icomoon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/components/Icon/fonts/icomoon.ttf
Binary file not shown.
Binary file modified src/components/Icon/fonts/icomoon.woff
Binary file not shown.
15 changes: 8 additions & 7 deletions src/components/Icon/icon.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@
font-display: block;
}

[class^='i'],
[class*=' i'] {
.icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
font-weight: normal;
font-style: normal;
text-transform: none;
line-height: 1;
font-variant: normal;
speak: never;
speak-as: never;

/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
Expand Down Expand Up @@ -72,6 +71,12 @@
}
}

.iError {
&::before {
content: $iError;
}
}

span.small {
font-size: 0.75rem;
}
Expand All @@ -91,7 +96,3 @@ span.large {
.disable {
color: global.$secondaryGray;
}

.error {
color: global.$secondaryRed;
}
12 changes: 10 additions & 2 deletions src/components/Icon/icon.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ import { Story } from '@ladle/react';

import Icon from './Icon';

export const iconFont: Story = () => {
import { IIconProps } from './icon.types';

export const iconFont: Story<IIconProps> = (props) => {
return (
<div>
<Icon icon="at" color="active" size="large" />
<Icon {...props} />
</div>
);
};

iconFont.args = {
color: 'active',
icon: 'at',
size: 'large',
};
2 changes: 1 addition & 1 deletion src/components/Icon/icon.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface IIconProps {
icon: 'at' | 'emote' | 'hashtag' | 'link' | 'pin' | 'gpt' | 'canva';
icon: 'at' | 'emote' | 'hashtag' | 'link' | 'pin' | 'gpt' | 'canva' | 'error';
size?: 'small' | 'large';
color?: 'active' | 'disabled';
className?: string;
Expand Down
1 change: 1 addition & 0 deletions src/components/Icon/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ $iEmote: '\e903';
$iHashtag: '\e904';
$iLink: '\e905';
$iPin: '\e906';
$iError: '\e907';
4 changes: 0 additions & 4 deletions src/pages/home/home.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,3 @@
flex-direction: column;
grid-area: input;
}

.gridTabs {
grid-area: tabs;
}
3 changes: 2 additions & 1 deletion src/pages/home/home.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import FeedbackError from '~components/FeedbackError/FeedbackError';
import FirstComment from '~components/FirstComment/FirstComment';
import MainComposer from '~components/MainComposer/MainComposer';
import MediaInputs from '~components/MediaInputs/MediaInput';
Expand All @@ -21,8 +22,8 @@ const Home = () => {
<MainComposer />
<MediaInputs />
<FirstComment />
<FeedbackError />
</div>
<div className={scss.gridTabs} />
</div>
<SavBar />
</div>
Expand Down
46 changes: 46 additions & 0 deletions src/stores/useError/useError.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { renderHook, act } from '@testing-library/react';
import * as zustand from 'zustand';

import { useError } from './useError';

import { myCustomCreate, storeResetFns } from '../__mocks__/zunstandMock';

vi.mock('zustand', async () => {
const zustand = (await vi.importActual('zustand')) as object;

return {
__esModule: true,
...zustand,
};
});

vi.spyOn(zustand, 'create').mockImplementation(myCustomCreate as never);

describe('useError', () => {
afterEach(() => {
act(() => {
storeResetFns.forEach((resetFn) => {
resetFn();
});
});
});

describe('when initialize', () => {
it('error message is empty', () => {
const { result } = renderHook(() => useError((state) => state));
expect(result.current.errorMessage).toBe('');
});
});

describe('when change the error message', () => {
it('changes the error message', () => {
const { result } = renderHook(() => useError((state) => state));

act(() => {
result.current.setErrorMessage('update error message');
});

expect(result.current.errorMessage).toBe('update error message');
});
});
});
8 changes: 8 additions & 0 deletions src/stores/useError/useError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { create } from 'zustand';

import { IUseError } from './useError.types';

export const useError = create<IUseError>()((set) => ({
errorMessage: '',
setErrorMessage: (errorMessage) => set({ errorMessage }),
}));
4 changes: 4 additions & 0 deletions src/stores/useError/useError.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IUseError {
errorMessage: string;
setErrorMessage: (errorMessage: string) => void;
}
8 changes: 8 additions & 0 deletions src/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ $secondaryRed: #c00016;
$baseColor: #6750a4;
$hovering: #6750a414; // Hovering with 8% opacity
$pressing: #6750a41f; // Pressing with 12% opacity

$phoneScreen: 37.4375rem; //599px

@mixin from599 {
@media screen and (min-width: $phoneScreen) {
@content;
}
}

0 comments on commit 0b0316c

Please sign in to comment.