Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Top Bar 구현 #19

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/src/main/kotlin/com/yourssu/handy/demo/TopBarPreview.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.yourssu.handy.demo

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Preview(showBackground = true)
@Composable
fun TopBarPreview() {

}
259 changes: 259 additions & 0 deletions compose/src/main/kotlin/com/yourssu/handy/compose/TopAppBar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package com.yourssu.handy.compose

import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yourssu.handy.compose.TopBarDefaults.topBarHeight
import com.yourssu.handy.compose.TopBarDefaults.topBarHorizontalPadding
import com.yourssu.handy.compose.foundation.HandyTypography
import com.yourssu.handy.compose.icons.HandyIcons
import com.yourssu.handy.compose.icons.filled.Add
import com.yourssu.handy.compose.icons.filled.ArrowsChevronLeft
import com.yourssu.handy.compose.icons.filled.List
import com.yourssu.handy.compose.icons.line.Add

sealed interface NavIcon {
data object None : NavIcon
data class Back(val onClick: () -> Unit) : NavIcon
data class Menu(val onClick: () -> Unit) : NavIcon
}

/**
* Center-Aligned Top App Bar
*
* 타이틀이 중앙에 위치한 탑앱바입니다. 좌측에 메뉴 아이콘 및 뒤로 가기 아이콘이 요구될 경우 사용합니다.
* 아이콘으로 기능을 명확하게 표현하지 못하거나 확실하게 기능을 설명하고 싶을 때 아이콘 대신 Text를 사용할 수 있습니다.
* 이때, 텍스트의 사용은 한 개까지만 가능하며, 텍스트가 공백 포함 5자일 경우 우측에 아이콘을 같이 쓸 수 없습니다.
*
*
* @param title headline or Logo 최대 9자(공백 포함)
* @param navIcon 왼쪽 아이콘 NavIcon.None, NavIcon.Menu, NavIcon.Back
* @param actions 오른쪽 아이콘 or 텍스트 (아이콘은 임의로 변경할 수 있으며 Center-aligned의 우측엔 최대 2개의 아이콘 버튼)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아이콘 개수는 굳이 코드상에서 제한할 필요는 없을 것 같습니다..!
피그마 문서자체가 디자인 가이드이기도 해서요 디자인측에서 고려해서 넘겨주실 것 같아요
게일 PR에서는 require로 강제해주시긴 했는데, 의문이 있어 리뷰남겨둔 상황이라 한번 참고해주세요

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분은 우선 강제하지 않는 걸로 가도 될 것 같습니다!

**/
@Composable
fun CenterAlignedTopAppBar(
title: String,
navIcon: NavIcon,
actions: @Composable (RowScope.() -> Unit)? = null
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(color = HandyTheme.colors.bgBasicDefault)
.height(topBarHeight), //Top App Bar의 높이값을 임의로 변경하지 않습니다.
contentAlignment = Alignment.Center
) {
Text(
text = title,
color = HandyTheme.colors.textBasicPrimary,
style = HandyTypography.T2Sb18,
textAlign = TextAlign.Center,
)

Row(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = topBarHorizontalPadding),
verticalAlignment = Alignment.CenterVertically
) {
// Left Icon
IconButton(navIcon)

Spacer(modifier = Modifier.weight(1f))

// Actions
if (actions != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

별건 아니지만 전 이런식의 표현이 좀 더 kotlin스러워서 선호합니다

actions?.let{
    ... // anctions가 null이 아닌경우
}

Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxHeight()
) {
actions()
}
}
}
}
}

@Composable
private fun RowScope.IconButton(navIcon: NavIcon) {
when (navIcon) {
is NavIcon.Back -> Icon(
imageVector = HandyIcons.Filled.ArrowsChevronLeft,
contentDescription = "Back",
modifier = Modifier.clickable(onClick = navIcon.onClick)
)

is NavIcon.Menu -> Icon(
imageVector = HandyIcons.Filled.List,
contentDescription = "Menu",
modifier = Modifier.clickable(onClick = navIcon.onClick)
)

NavIcon.None -> Spacer(modifier = Modifier.width(24.dp)) // 여백 확보
}
}

/**
* Left-aligned
*
* 타이틀이 좌측에 위치한 탑앱바입니다. 기능의 첫 페이지에 사용되는 탑앱바로,
* 현재 페이지의 제목을 나타낼 때 사용합니다. 아이콘으로 기능을 명확하게 표현하지 못하거나 확실하게 기능을 설명하고 싶을 때 아이콘 대신 Text를 사용할 수 있습니다.
*
* @param title headline or Logo 최대 9자(공백 포함)
* @param actions 오른쪽 아이콘 or 텍스트 (아이콘은 임의로 변경할 수 있으며 Left-aligned의 우측엔 최대 3개의 아이콘 버튼)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actions보다는 다른 명칭이 어떨까용
왜 actions인가요??!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오른쪽에 들어가는 친구들은 주로 공유하기나 더보기, 신고하기 등 사용자가 주도적으로 액션을 취한다고 생각해서 action이라고 하였습니다! 사실 머테리얼 보고 그렇게 한 걸로 기억하는데, 혹시 추천할만한 네이밍이 있을까요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흠... 그런 의미가 있다면 우선은 굳이 수정할 필요는 없을 것 같아요
사용자 관점에서는 actions가 맞는 것 같긴하지만 해당 컴포넌트를 사용하는 개발자 관점에서의 actions라고 했을때 마땅히 떠오르는 게 없었어서 여쭤봤어요!

**/
@Composable
fun LeftAlignedTopAppBar(
title: String,
actions: @Composable (RowScope.() -> Unit)? = null
Comment on lines +101 to +102
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 modifier를 안넣은 이유가 있을까요??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

놓친 것 같습니다! 수정했습니닷 🧐

) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(color = HandyTheme.colors.bgBasicDefault)
.height(topBarHeight),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = topBarHorizontalPadding),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
style = HandyTypography.H3Sb24,
color = HandyTheme.colors.textBasicPrimary,
textAlign = TextAlign.Center,
)

Spacer(modifier = Modifier.weight(1f))

// Actions
if (actions != null) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(10.dp)
) {
actions()
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오른쪽 아이콘이 여러개 들어간 경우 figma상 각각 아이콘 사이의 패딩과 현재 프리뷰 상 패딩이 다른 것 같아요
이것도 사용 시에 패딩을 주는 것 말고 패딩이 지정되어있으면 어떨까요
[프리뷰]
image

[피그마]
image
image

}
}
}

@Preview(showBackground = true)
@Composable
fun CenterAlignedTopAppBarPreview() {
Column {
CenterAlignedTopAppBar(
title = "Centered",
navIcon = NavIcon.None,
actions = {}
)

CenterAlignedTopAppBar(
title = "Back Example",
navIcon = NavIcon.Back(onClick = { Log.d("TopBarPreview", "Back clicked") }),
actions = {
Text(
text = "Share",
color = HandyTheme.colors.textBrandPrimary,
modifier = Modifier.clickable { Log.d("TopBarPreview", "Share clicked") })
}
)

CenterAlignedTopAppBar(
title = "Menu Example",
navIcon = NavIcon.Menu(onClick = { Log.d("TopBarPreview", "Menu clicked") }),
actions = {
Icon(
imageVector = HandyIcons.Filled.Add,
contentDescription = "Add",
modifier = Modifier.clickable { Log.d("TopBarPreview", "Add clicked") }
)
}
)
}
}


@Preview(showBackground = true)
@Composable
fun LeftAlignedTopBarPreview() {
Column {
LeftAlignedTopAppBar(
title = "Title",
actions = {}
)
LeftAlignedTopAppBar(
title = "Title",
actions = {
Icon(
imageVector = HandyIcons.Line.Add,
contentDescription = "Add",
modifier = Modifier.clickable { Log.d("TopBarPreview", "Add clicked") }
)
}
)
LeftAlignedTopAppBar(
title = "Title",
actions = {
Icon(
imageVector = HandyIcons.Line.Add,
contentDescription = "Add",
modifier = Modifier.clickable { Log.d("TopBarPreview", "Add clicked") }
)
Icon(
imageVector = HandyIcons.Line.Add,
contentDescription = "Add",
modifier = Modifier.clickable { Log.d("TopBarPreview", "Add clicked") }
)
}
)
LeftAlignedTopAppBar(
title = "Title",
actions = {
Icon(
imageVector = HandyIcons.Line.Add,
contentDescription = "Add",
modifier = Modifier.clickable { Log.d("TopBarPreview", "Add clicked") }
)
Icon(
imageVector = HandyIcons.Line.Add,
contentDescription = "Add",
modifier = Modifier.clickable { Log.d("TopBarPreview", "Add clicked") }
)
Icon(
imageVector = HandyIcons.Line.Add,
contentDescription = "Add",
modifier = Modifier.clickable { Log.d("TopBarPreview", "Add clicked") }
)
}
)
}
}


object TopBarDefaults {
val topBarHeight = 56.dp
val topBarHorizontalPadding = 16.dp
}