Skip to content

Commit

Permalink
fix(bottle, ui): implement design QA feedbacks (#56)
Browse files Browse the repository at this point in the history
* fix(bottle): broken layout on create-profile

* feat(ui): fixed bottom cta button

* refactor(bottle): use fixed bottom cta button from ui package

* fix(bottle): lint

* refactor(bottle): use fixed bottom cta button from ui package

* fix(bottle): open state selection bottom sheet automatically when city is selected

* fix(bottle): region bottom sheet tab space

* feat(ui): add close icon asset

* feat(bottle): add close button for region bottom sheet

* fix(ui): chip border radius

* fix(bottle): cta button text

* fix(ui): bubble style

* fix(bottle): show unit in height

* fix(ui): solid large button height

* fix(ui): change background color when text field is focused

* feat(ui): add delete icon asset

* fix(bottle): show delete button when message length is not zero

* fix(bottle): no bottle image should be responsive

* fix(e2e): implement fixed ui flow
  • Loading branch information
stakbucks authored Sep 14, 2024
1 parent b5ece2f commit 5d107f6
Show file tree
Hide file tree
Showing 31 changed files with 292 additions and 154 deletions.
39 changes: 21 additions & 18 deletions apps/bottle/src/app/bottles/Bottles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,31 +46,34 @@ export function Bottles() {
top={data => (
<BottlesList.Top>
{name => (
<>
<Paragraph typography="t1" color="black100">
{type === 'random'
? data.randomBottles?.length > 0
? `${name}님에게\n추천하는 분들이에요!`
: '아직 주변에 새로운\n보틀이 없어요'
: data.sentBottles?.length > 0
? `${name}님을 마음에\n들어한 분들이에요`
: '아직 받은 보틀이 없어요'}
</Paragraph>
<Paragraph typography="bo" color="neutral600">
{type === 'random'
? '시간이 지나면 새로운 분들을 추천해 드려요'
: '시간 내에 보틀을 열지 않으면 사라져요'}
</Paragraph>
</>
<Paragraph typography="t1" color="black100">
{type === 'random'
? data.randomBottles?.length > 0
? `${name}님에게\n추천하는 분들이에요!`
: '아직 주변에 새로운\n보틀이 없어요'
: data.sentBottles?.length > 0
? `${name}님을 마음에\n들어한 분들이에요`
: '아직 받은 보틀이 없어요'}
</Paragraph>
)}
</BottlesList.Top>
)}
>
{data => {
const bottles = type === 'random' ? data.randomBottles : data.sentBottles;
return bottles.length === 0 ? (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Image src={NO_BOTTLE_IMAGE} alt="no bottle" width={250} height={250} />
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%',
aspectRatio: '1 / 1',
padding: '60px',
}}
>
<div style={{ width: '100%', height: '100%', position: 'relative' }}>
<Image src={NO_BOTTLE_IMAGE} alt="no bottle" fill />
</div>
</div>
) : (
bottles?.map(bottle => (
Expand Down
5 changes: 2 additions & 3 deletions apps/bottle/src/app/bottles/[...slug]/ActionButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client';

import { FixedButton } from '@/features/steps/StepContainer';
import { useAcceptBottleMutation } from '@/store/mutation/useAcceptBottleMutation';
import { useRefuseBottleMutation } from '@/store/mutation/useRefuseBottleMutation';
import { CTAButton } from '@bottlesteam/ui';
import { CTAButton, FixedBottomCTAButton } from '@bottlesteam/ui';
import { overlay } from 'overlay-kit';
import { BottleType } from '../Bottles';
import { ExpressInterestBottomSheet } from './ExpressInterestBottomSheet';
Expand Down Expand Up @@ -31,7 +30,7 @@ export function ActionButtons({ type, id }: Props) {
: () => accept(null);

return (
<FixedButton
<FixedBottomCTAButton
variant="two"
left={<CTAButton.Left onClick={() => refuse()}>떠내려 보내기</CTAButton.Left>}
right={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Control } from '@/components/control';
import { BottomSheet, BottomSheetProps, Button, Paragraph, TextField, spacings } from '@bottlesteam/ui';
import { Asset, BottomSheet, BottomSheetProps, Button, Paragraph, TextField, spacings } from '@bottlesteam/ui';
import { useEffect, useState } from 'react';
import { emoticonsContainer } from './bottomSheetStyle.css';
import { deleteButtonStyle, emoticonsContainer } from './bottomSheetStyle.css';

interface Props extends Omit<BottomSheetProps, 'button' | 'body'> {
onExpress: (likeMessage: string) => void;
Expand Down Expand Up @@ -32,7 +32,24 @@ export function ExpressInterestBottomSheet({ onExpress, ...bottomSheetProps }: P
size="sm"
body={
<>
<TextField value={message} onChange={e => setMessage(e.currentTarget.value)} error={isError} />
<TextField
placeholder="호감이 생긴 이유를 적어보세요"
value={message}
onChange={e => setMessage(e.currentTarget.value)}
error={isError}
rightButton={
message.length > 0 && (
<button
className={deleteButtonStyle}
onClick={() => {
setMessage('');
}}
>
<Asset type="icon-delete" />
</button>
)
}
/>
<TextField.Caption>{isError && ERROR_CAPTION}</TextField.Caption>
<Paragraph typography="st2" color="neutral600" style={{ marginTop: spacings.xl }}>
이모티콘으로 표현해보세요
Expand Down
10 changes: 7 additions & 3 deletions apps/bottle/src/app/bottles/[...slug]/bottomSheetStyle.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export const emoticonsContainer = style({
justifyContent: 'space-between',
marginTop: spacings.sm,
});
// export const emoticonBox=style({

// })
export const deleteButtonStyle = style({
background: 'none',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: 'none',
});
74 changes: 36 additions & 38 deletions apps/bottle/src/app/create-profile/_steps/interests/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,44 +46,42 @@ export function Interests() {

return (
<>
<Step>
<Step.Title>푹 빠진 취미는 무엇인가요?</Step.Title>
<Step.Description style={{ marginTop: '12px' }}>최소 3개, 최대 10개까지 선택할 수 있어요</Step.Description>
<Step.Subtitle style={{ marginTop: spacings.xxl }}>문화 예술</Step.Subtitle>
<Control value={interests}>
<section className={interestsStyle}>
{culture.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
<Step.Subtitle style={{ marginTop: spacings.xl }}>스포츠</Step.Subtitle>
<section className={interestsStyle}>
{sports.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
<Step.Subtitle style={{ marginTop: spacings.xl }}>오락</Step.Subtitle>
<section className={interestsStyle}>
{entertainment.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
<Step.Subtitle style={{ marginTop: spacings.xl }}>기타</Step.Subtitle>
<section className={interestsStyle} style={{ marginBottom: spacings.xl }}>
{etc.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
</Control>
</Step>
<Step.Title>푹 빠진 취미는 무엇인가요?</Step.Title>
<Step.Description style={{ marginTop: '12px' }}>최소 3개, 최대 10개까지 선택할 수 있어요</Step.Description>
<Step.Subtitle style={{ marginTop: spacings.xxl }}>문화 예술</Step.Subtitle>
<Control value={interests}>
<section className={interestsStyle}>
{culture.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
<Step.Subtitle style={{ marginTop: spacings.xl }}>스포츠</Step.Subtitle>
<section className={interestsStyle}>
{sports.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
<Step.Subtitle style={{ marginTop: spacings.xl }}>오락</Step.Subtitle>
<section className={interestsStyle}>
{entertainment.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
<Step.Subtitle style={{ marginTop: spacings.xl }}>기타</Step.Subtitle>
<section className={interestsStyle} style={{ marginBottom: spacings.xl }}>
{etc.map((item, index) => (
<Control.Item value={item} key={index} onClick={() => handleClick(item)}>
<ItemButton>{item}</ItemButton>
</Control.Item>
))}
</section>
</Control>
<Step.FixedButton
disabled={interests.length < MIN_SELECTD}
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function KaKaoId() {
send({ type: AppBridgeMessageType.CREATE_PROFILE_COMPLETE });
}}
>
다음
완료
</Step.FixedButton>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Asset, Paragraph, colors, BottomSheet, BottomSheetProps, Chip } from '@bottlesteam/ui';
import { useEffect, useState } from 'react';
import { itemStyle, listStyle, tabBarStyle } from './regionBottomSheetStyle.css';
import { itemStyle, listStyle, tabBarStyle, tabItemsStyle } from './regionBottomSheetStyle.css';

interface Props extends Omit<BottomSheetProps, 'button' | 'body'> {
items: string[];
Expand All @@ -25,7 +25,7 @@ export function RegionBottomSheet({ onSelect, selected, items, type, ...bottomSh
{...bottomSheetProps}
body={
<>
<TabBar type={type} />
<TabBar onClose={bottomSheetProps.onClose} type={type} />
<ul className={listStyle}>
{items.map(item => (
<li
Expand Down Expand Up @@ -61,12 +61,17 @@ export function RegionBottomSheet({ onSelect, selected, items, type, ...bottomSh
);
}

function TabBar({ type }: { type: 'city' | 'state' }) {
function TabBar({ type, onClose }: { type: 'city' | 'state'; onClose: BottomSheetProps['onClose'] }) {
return (
<div className={tabBarStyle}>
<Chip active={type === 'city'}>전체 지역</Chip>
<Asset type="icon-right" />
<Chip active={type === 'state'}>상세 지역</Chip>
<div className={tabItemsStyle}>
<Chip active={type === 'city'}>전체 지역</Chip>
<Asset type="icon-right" />
<Chip active={type === 'state'}>상세 지역</Chip>
</div>
<div onClick={onClose}>
<Asset type="icon-close" />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,30 @@ export const tabBarStyle = style({
width: '100%',
display: 'flex',
alignItems: 'center',
gap: spacings.sm,
justifyContent: 'space-between',

marginBottom: spacings.xl,
});

export const tabItemsStyle = style({
display: 'flex',
gap: spacings.xxs,
alignItems: 'center',
});

export const listStyle = style({
width: '100%',
height: '208px',
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
gap: spacings.sm,
'::-webkit-scrollbar': {
display: 'none',
},
});

export const itemStyle = style({
width: '100%',
display: 'flex',
alignItems: 'center',
});

export const closeIconStyle = style({});
79 changes: 50 additions & 29 deletions apps/bottle/src/app/create-profile/_steps/region/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,52 +31,53 @@ export function Region() {
const [city, setCity] = useState<string | undefined>(selected != null ? selected.city : undefined);
const [state, setState] = useState<string | undefined>(selected != null ? selected.state : undefined);

const openRegionBottomSheet = (type: 'city' | 'state') => {
overlay.open(({ isOpen, unmount }) => {
return (
<>
{regionsData && (
<RegionBottomSheet
type={type}
selected={type === 'city' ? city : state}
onSelect={(item: string) => {
type === 'city' ? setCity(item) : setState(item);
type === 'city' && city !== item && setState(undefined);
}}
isOpen={isOpen}
onClose={unmount}
items={
type === 'city'
? regionsData.regions.map(({ city }) => city)
: (regionsData.regions.find(region => region.city === city) as RegionData).state
}
/>
)}
</>
);
});
};

return (
<>
<OverlayProvider>
<Step.Title>주로 생활하는 지역은 어딘가요?</Step.Title>
<Step.Subtitle style={{ marginTop: spacings.xxl }}>전체 지역</Step.Subtitle>
<div aria-hidden={true} className={spacingStyle} />
<SelectInput
onClick={() => openRegionBottomSheet('city')}
onClick={async () => {
if (regionsData == null) {
return;
}
const selectedCity = await openRegionBottomSheet(
'city',
regionsData?.regions.map(({ city }) => city),
city
);
if (selectedCity !== city) {
setCity(selectedCity);
setState(undefined);
}

if (selectedCity != null) {
const selectedState = await openRegionBottomSheet(
'state',
(regionsData?.regions.find(region => region.city === selectedCity) as RegionData).state,
state
);
setState(selectedState);
}
}}
placeholder={'전체 지역을 선택해 주세요'}
value={city}
/>
<Step.Subtitle style={{ marginTop: spacings.xl }}>시 · 군 · 구</Step.Subtitle>
<div aria-hidden={true} className={spacingStyle} />
<SelectInput
onClick={() => {
onClick={async () => {
if (city === undefined) {
send({ type: AppBridgeMessageType.TOAST_OPEN, payload: { message: '전체 지역을 먼저 선택해주세요.' } });
return;
}
openRegionBottomSheet('state');
const selectedState = await openRegionBottomSheet(
'state',
(regionsData?.regions.find(region => region.city === city) as RegionData).state,
state
);
setState(selectedState);
}}
placeholder={'상세 지역을 선택해 주세요'}
value={state}
Expand All @@ -98,3 +99,23 @@ export function Region() {
</>
);
}

const openRegionBottomSheet = async (
type: 'city' | 'state',
items: string[],
selected: string | undefined
): Promise<string | undefined> =>
await overlay.openAsync(({ isOpen, close, unmount }) => {
return (
<>
<RegionBottomSheet
type={type}
selected={selected}
onSelect={close}
isOpen={isOpen}
onClose={unmount}
items={items}
/>
</>
);
});
Loading

0 comments on commit 5d107f6

Please sign in to comment.