Skip to content

Commit

Permalink
Migrate ListItem component with scss (#1925)
Browse files Browse the repository at this point in the history
<!--
  How to write a good PR title:
- Follow [the Conventional Commits
specification](https://www.conventionalcommits.org/en/v1.0.0/).
  - Give as much context as necessary and as little as possible
  - Prefix it with [WIP] while it’s a work in progress
-->

## Self Checklist

- [x] I wrote a PR title in **English** and added an appropriate
**label** to the PR.
- [x] I wrote the commit message in **English** and to follow [**the
Conventional Commits
specification**](https://www.conventionalcommits.org/en/v1.0.0/).
- [x] I [added the
**changeset**](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md)
about the changes that needed to be released. (or didn't have to)
- [x] I wrote or updated **documentation** related to the changes. (or
didn't have to)
- [x] I wrote or updated **tests** related to the changes. (or didn't
have to)
- [x] I tested the changes in various browsers. (or didn't have to)
  - Windows: Chrome, Edge, (Optional) Firefox
  - macOS: Chrome, Edge, Safari, (Optional) Firefox

## Related Issue
<!-- Please link to issue if one exists -->

- #1733
- Fixes #1117 
- Fixes #1314

## Summary
<!-- Please brief explanation of the changes made -->

Migrate `ListItem` component with scss

## Details
<!-- Please elaborate description of the changes -->

- 컴포넌트 인터페이스를 다른 컴포넌트보다 비교적 많이 변경했습니다. 레거시 컴포넌트이며, 사용하지 않는 속성들이 너무나 많고
현재 다른 베지어 컴포넌트의 사용 방식과 일관성이 맞지 않는 부분이 있었습니다.
- `interpolation`, `name`, `optionKey`, `leftIcon` 속성을 제외하고 채널 데스크
애플리케이션 기준 사용처는 없는 것으로 보입니다.
  - `name`, `optionKey` 는 사실상 무의미한 속성이라, 제거해도 동작은 동일합니다.
- `leftIcon` 속성은 Button 컴포넌트 등과 동일하게 `leftContent` 속성에서 `isBezierIcon`
여부를 체크하도록 변경했습니다. 라이브러리 사용자가 일관적으로 컴포넌트를 사용할 수 있도록 하는 변경입니다.
- `useAdjacentElementBorderRadius.ts` : 이제 `:has` 선택자로 해당 기능 구현이 가능해짐에따라
제거했습니다.
- #1117 을 함께 수정합니다. Breaking change 있는 김에 포함하고자 했습니다.
- XL 사이즈의 잘못된 타이포 사이즈를 수정합니다.

**기타**

- `ActivatableProps` 에서 `active` 속성을 제외하고 제거합니다. 사용하지도 않고, 불필요한 속성이 너무
많았습니다.

### Breaking change? (Yes/No)
<!-- If Yes, please describe the impact and migration path for users -->

Yes

**Breaking Changes: Property updates in `ListItem` component**

- No longer support `interpolation` property. Replace any usage of
`interpolation` property with appropriate `style` or `className`
implementations.
- No longer support `iconStyle`, `iconClassName`, `iconInterpolation`,
`contentStyle`, `contentClassName` and `contentInterpolation`. This
decision was made to reduce excessive flexibility in the interface.
- No longer support `leftIcon` property. Removed for consistency with
other component interfaces. Replace it to `leftContent`.
- No longer support `name` property. The second argument (name) of
`onClick` is also removed. If you need an identifier, combine functions
outside of the component.
- No longer support `hide`, `nested`, `optionKey` and
`disableIconActive` property. Removed because it is a legacy property.
Replace `hide` property with conditional rendering.
- The size changes according to the `ListItemSize`. This is a change to
unify the design. Please change it like below.
  - `ListItemSize.S` -> `ListItemSize.XS`
  - `ListItemSize.M` -> `ListItemSize.S`
  - `ListItemSize.L` -> `ListItemSize.M`
  - `ListItemSize.XL` -> `ListItemSize.L`

**Minor Changes:**

- Fix incorrect text size for `XL`(now `L`) size.

## References
<!-- Please list any other resources or points the reviewer should be
aware of -->
  • Loading branch information
sungik-choi committed Jan 19, 2024
1 parent 4538dd2 commit 6e62fb6
Show file tree
Hide file tree
Showing 17 changed files with 334 additions and 1,026 deletions.
20 changes: 20 additions & 0 deletions .changeset/cuddly-crews-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@channel.io/bezier-react": major
---

**Breaking Changes: Property updates in `ListItem` component**

- No longer support `interpolation` property. Replace any usage of `interpolation` property with appropriate `style` or `className` implementations.
- No longer support `iconStyle`, `iconClassName`, `iconInterpolation`, `contentStyle`, `contentClassName` and `contentInterpolation`. This decision was made to reduce excessive flexibility in the interface.
- No longer support `leftIcon` property. Removed for consistency with other component interfaces. Replace it to `leftContent`.
- No longer support `name` property. The second argument (name) of `onClick` is also removed. If you need an identifier, combine functions outside of the component.
- No longer support `hide`, `nested`, `optionKey` and `disableIconActive` property. Removed because it is a legacy property. Replace `hide` property with conditional rendering.
- The size changes according to the `ListItemSize`. This is a change to unify the design. Please change it like below.
- `ListItemSize.S` -> `ListItemSize.XS`
- `ListItemSize.M` -> `ListItemSize.S`
- `ListItemSize.L` -> `ListItemSize.M`
- `ListItemSize.XL` -> `ListItemSize.L`

**Minor Changes:**

- Fix incorrect text size for `XL`(now `L`) size.
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export const UsageComposite: StoryObj<{}> = {
</LegacyStackItem>
<LegacyStackItem>
<ListItem
leftIcon={TagIcon}
leftContent={TagIcon}
content="KR/Product"
rightContent={(
<LegacyHStack>
Expand All @@ -295,7 +295,7 @@ export const UsageComposite: StoryObj<{}> = {
</LegacyStackItem>
<LegacyStackItem>
<ListItem
leftIcon={TagIcon}
leftContent={TagIcon}
content="KR/Design"
rightContent={(
<LegacyHStack>
Expand Down
159 changes: 159 additions & 0 deletions packages/bezier-react/src/components/ListItem/ListItem.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
.ListItemLeftIcon {
transition: color var(--transition-m);
}

.ListItem {
all: unset;

cursor: pointer;

display: flex;
align-items: center;

text-decoration: none;

transition: background-color var(--transition-s);

&:where(.variant-monochrome) {
color: var(--txt-black-darkest);

& :where(.ListItemLeftIcon) {
color: var(--txt-black-dark);
}
}

&:where(.size-xs) {
padding: 4px 6px;
border-radius: var(--radius-6);
}

&:where(.size-s) {
padding: 6px;
border-radius: var(--radius-6);
}

&:where(.size-m) {
padding: 8px 6px;
border-radius: var(--radius-8);
}

&:where(.size-l) {
padding: 10px 6px;
border-radius: var(--radius-12);
}

&:where(.disabled) {
cursor: default;
opacity: var(--opacity-disabled);
}

&:where(:not(.disabled, .active)) {
&:where(.focused, :focus-visible) {
background-color: var(--bg-black-lighter);
}

&:where(:hover) {
background-color: var(--bg-black-lighter);
}
}

&:where(.active) {
color: var(--bgtxt-blue-normal);
background-color: var(--bgtxt-blue-lightest);

&:where(.focused, :focus-visible) {
background-color: var(--bgtxt-blue-lighter);
}

& :where(.ListItemLeftIcon) {
color: var(--bgtxt-blue-normal);
}

/* NOTE: When multiple adjacent elements are active, it naturally stitches the border together. */
/* stylelint-disable selector-max-specificity */
&:has(+ .ListItem.active) {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}

& + .ListItem.active {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
/* stylelint-enable selector-max-specificity */
}

&.variant-blue {
color: var(--bgtxt-blue-normal);

& :where(.ListItemLeftIcon) {
color: var(--bgtxt-blue-normal);
}
}

&.variant-red {
color: var(--bgtxt-red-normal);

& :where(.ListItemLeftIcon) {
color: var(--bgtxt-red-normal);
}
}

&.variant-green {
color: var(--bgtxt-green-normal);

& :where(.ListItemLeftIcon) {
color: var(--bgtxt-green-normal);
}
}

&.variant-cobalt {
color: var(--bgtxt-cobalt-normal);

& :where(.ListItemLeftIcon) {
color: var(--bgtxt-cobalt-normal);
}
}
}

.ListItemContent {
display: grid;
grid-template-columns: fit-content(100%) minmax(0, 1fr);
width: 100%;
}

.ListItemRightContent {
display: flex;
margin-left: 8px;
white-space: nowrap;
}

.ListItemTitle {
display: flex;
grid-column: 2;
grid-row: 1;
align-items: center;

& * {
transition: color var(--transition-s);
}
}

.ListItemDescription {
display: flex;
grid-column: 2;
grid-row: 2;
align-items: center;

width: 100%;
margin-top: 2px;
}

.ListItemLeftContent {
display: flex;
grid-column: 1;
grid-row: 1;
align-items: center;

margin-right: 8px;
}
105 changes: 22 additions & 83 deletions packages/bezier-react/src/components/ListItem/ListItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import React, {
useMemo,
useState,
} from 'react'
import React, { useState } from 'react'

import { InboxIcon } from '@channel.io/bezier-icons'
import {
Expand All @@ -10,126 +7,68 @@ import {
type StoryObj,
} from '@storybook/react'

import { compact } from '~/src/utils/array'
import { range } from '~/src/utils/number'

import ListItem from './ListItem'
import type ListItemProps from './ListItem.types'
import { ListItem } from './ListItem'
import {
type ListItemProps,
ListItemSize,
ListItemVariant,
} from './ListItem.types'

const meta: Meta<typeof ListItem> = {
component: ListItem,
}
export default meta

interface ArgTypes extends ListItemProps {
width: number
}

const Template: StoryFn<ArgTypes> = ({ width, ...listItemProps }) => (
<div style={{ width }}>
<ListItem optionKey="menu-item-0" {...listItemProps} />
const Template: StoryFn<ListItemProps> = (props) => (
<div style={{ width: 400 }}>
<ListItem {...props} />
</div>
)

export const Primary = {
export const Primary: StoryObj<ListItemProps> = {
render: Template,

args: {
width: 388,
size: ListItemSize.M,
size: ListItemSize.S,
content: '상담이 열릴 때',
description:
'고객이 첫 메시지를 보내거나, 매니저가 상담을 다시 열거나, 자동으로 리오픈되면 트리거됩니다.',
rightContent: '',
leftIcon: InboxIcon,
leftContent: InboxIcon,
active: false,
focused: false,
disableIconActive: false,
descriptionMaxLines: 0,
href: '',
},

argTypes: {
width: {
control: {
type: 'range',
min: 50,
max: 500,
step: 1,
},
},
disabled: { control: { type: 'boolean' } },
variant: {
control: {
type: 'radio',
},
options: [...Object.values(ListItemVariant)],
},
active: { control: { type: 'boolean' } },
size: {
control: {
type: 'select',
},
options: ListItemSize,
},
description: '고객이 첫 메시지를 보내거나, 매니저가 상담을 다시 열거나, 자동으로 리오픈되면 트리거됩니다.',
descriptionMaxLines: 2,
},
}

interface CompositionProps {
listRange: number
}
const list = range(0, 10)

const CompositionTemplate = ({ listRange }: CompositionProps) => {
const [activeIndex, setActiveIndex] = useState<Set<number>>(() => {
const randomActiveIndex = Array.from(Array(listRange).keys()).map((index) => (Math.random() < 0.5 ? index : null))
return new Set(compact<number>([...randomActiveIndex]))
})
const CompositionTemplate = () => {
const [activeIndex, setActiveIndex] = useState<Set<number>>(new Set([0, 1, 2, 4]))

const isActive = (index: number) => activeIndex.has(index)

const toggleActive = (index: number) => setActiveIndex((prevSet) => {
if (prevSet.has(index)) {
prevSet.delete(index)
return new Set(Array.from(prevSet))
return new Set(prevSet)
}
return new Set(Array.from(prevSet.add(index)))
return new Set(prevSet.add(index))
})

const list = useMemo(() => Array.from(Array(listRange).keys()), [listRange])

return (
<div>
<div style={{ width: 200 }}>
{ list.map((index) => (
<ListItem
key={index}
className={isActive(index) ? 'active' : undefined}
optionKey={`menu-item-${index}`}
active={isActive(index)}
onClick={() => toggleActive(index)}
content={`이것은 ${index}번 아이템입니다.`}
content="Click me!"
/>
)) }
</div>
)
}

export const Composition: StoryObj<CompositionProps> = {
export const Composition: StoryObj = {
render: CompositionTemplate,

args: {
listRange: 10,
},

argTypes: {
listRange: {
control: {
type: 'range',
min: 2,
max: 20,
},
},
},
}

export default meta
Loading

0 comments on commit 6e62fb6

Please sign in to comment.