dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

commit bcb51269c9b54091df460c96d5652b00d5bca11f
parent fd3426866dda07bcb1d1854f28aefba5b71cee8a
Author: Yongbin Kim <iam@yongbin.kim>
Date:   Thu, 19 Jan 2023 15:57:07 +0900

feat: NavbarDropdownItem 컴포넌트 추가

Signed-off-by: Yongbin Kim <iam@yongbin.kim>

Diffstat:
Mcomponents/layout/Header.tsx | 27+++++++++++++++++++++------
Mcomponents/layout/Navbar.module.css | 2+-
Mcomponents/layout/Navbar.module.css.map | 4++--
Mcomponents/layout/Navbar.module.scss | 142++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Acomponents/layout/NavbarDropdownItem.tsx | 21+++++++++++++++++++++
Acomponents/layout/NavbarDropdownLabel.tsx | 13+++++++++++++
6 files changed, 174 insertions(+), 35 deletions(-)

diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx @@ -1,5 +1,7 @@ import { useToken } from '@/components/contexts/TokenContext' import Navbar from '@/components/layout/Navbar' +import NavbarDropdownItem from '@/components/layout/NavbarDropdownItem' +import NavbarDropdownLabel from '@/components/layout/NavbarDropdownLabel' import NavbarItem from '@/components/layout/NavbarItem' import classNames from '@/lib/classnames' import { UserProfile } from '@/lib/models/user_profile' @@ -36,12 +38,25 @@ export default function Header () { </> ) : ( <> - <NavbarItem href={`/wiki/`}> - 내 위키 - </NavbarItem> - <NavbarItem href={`/users/${userProfile.loginId}`}> - {userProfile.nickname} - </NavbarItem> + <NavbarDropdownItem label={userProfile.nickname}> + <NavbarItem href={`/users/${userProfile.loginId}`}> + 내 프로필 + </NavbarItem> + + <hr /> + + <NavbarDropdownLabel>내 위키</NavbarDropdownLabel> + + <NavbarItem href={`/wiki/test`}> + 테스트 + </NavbarItem> + + <hr /> + + <NavbarItem href={`/users/logout`}> + 로그아웃 + </NavbarItem> + </NavbarDropdownItem> </> )} </Navbar> diff --git a/components/layout/Navbar.module.css b/components/layout/Navbar.module.css @@ -1 +1 @@ -.navbar{padding:.5rem 0;z-index:10;background-color:#fafdfa;color:#191c1b}.navbar .item:hover{background-color:rgba(25,28,27,.1)}.navbar.is-primary{background-color:#58fbda;color:#00201a}.navbar.is-primary .item:hover{background-color:rgba(0,32,26,.1)}.navbar.is-secondary{background-color:#f5d9ff;color:#30004a}.navbar.is-secondary .item:hover{background-color:rgba(48,0,74,.1)}.navbar.is-tertiary{background-color:#c7e7ff;color:#001e2e}.navbar.is-tertiary .item:hover{background-color:rgba(0,30,46,.1)}.navbar.is-error{background-color:#ffdad6;color:#410002}.navbar.is-error .item:hover{background-color:rgba(65,0,2,.1)}@media(prefers-color-scheme: dark){.navbar{background-color:#191c1b;color:#e1e3e0}.navbar .item:hover{background-color:rgba(225,227,224,.1)}.navbar.is-primary{background-color:#005143;color:#58fbda}.navbar.is-primary .item:hover{background-color:rgba(88,251,218,.1)}.navbar.is-secondary{background-color:#61317e;color:#f5d9ff}.navbar.is-secondary .item:hover{background-color:rgba(245,217,255,.1)}.navbar.is-tertiary{background-color:#2a4a5f;color:#c7e7ff}.navbar.is-tertiary .item:hover{background-color:rgba(199,231,255,.1)}.navbar.is-error{background-color:#93000a;color:#ffdad6}.navbar.is-error .item:hover{background-color:rgba(255,218,214,.1)}}.navbar>.container{display:flex;padding:0 1rem;align-items:stretch;min-height:2.5rem}.navbar .brand,.navbar .menu{display:flex;align-items:stretch}.navbar .brand{flex-shrink:0;line-height:1.5rem;font-size:1rem;letter-spacing:.009375rem;font-weight:500}.navbar .brand .navbar-item{font-weight:bold}.navbar .menu{flex:1;justify-content:flex-end}.navbar .menu.is-centered{justify-content:center;margin-right:0}.navbar .item{display:flex;align-items:center;padding:0 1rem;line-height:2.5rem;border-radius:9999px}.navbar a.item{color:inherit}.navbar a.item,.navbar a.item:hover{text-decoration:none}/*# sourceMappingURL=Navbar.module.css.map */ +.navbar{padding:.5rem 0;z-index:10;background-color:#fafdfa;color:#191c1b}.navbar .item:hover{background-color:rgba(25,28,27,.1)}.navbar.is-primary{background-color:#58fbda;color:#00201a}.navbar.is-primary .item:hover{background-color:rgba(0,32,26,.1)}.navbar.is-secondary{background-color:#f5d9ff;color:#30004a}.navbar.is-secondary .item:hover{background-color:rgba(48,0,74,.1)}.navbar.is-tertiary{background-color:#c7e7ff;color:#001e2e}.navbar.is-tertiary .item:hover{background-color:rgba(0,30,46,.1)}.navbar.is-error{background-color:#ffdad6;color:#410002}.navbar.is-error .item:hover{background-color:rgba(65,0,2,.1)}@media(prefers-color-scheme: dark){.navbar{background-color:#191c1b;color:#e1e3e0}.navbar .item:hover{background-color:rgba(225,227,224,.1)}.navbar.is-primary{background-color:#005143;color:#58fbda}.navbar.is-primary .item:hover{background-color:rgba(88,251,218,.1)}.navbar.is-secondary{background-color:#61317e;color:#f5d9ff}.navbar.is-secondary .item:hover{background-color:rgba(245,217,255,.1)}.navbar.is-tertiary{background-color:#2a4a5f;color:#c7e7ff}.navbar.is-tertiary .item:hover{background-color:rgba(199,231,255,.1)}.navbar.is-error{background-color:#93000a;color:#ffdad6}.navbar.is-error .item:hover{background-color:rgba(255,218,214,.1)}}.navbar>.container{display:flex;padding:0 1rem;align-items:stretch;min-height:2.5rem}.brand,.menu{display:flex;align-items:stretch}.brand{flex-shrink:0;line-height:1.5rem;font-size:1rem;letter-spacing:.009375rem;font-weight:500}.brand .navbar-item{font-weight:bold}.menu{flex:1;justify-content:flex-end}.menu.is-centered{justify-content:center;margin-right:0}.item{display:flex;align-items:center;padding:0 1rem;line-height:2.5rem;border-radius:9999px}.item[href]{color:inherit}.item[href],.item[href]:hover{text-decoration:none}.item.is-dropdown{position:relative;cursor:default}.item.is-dropdown .dropdown{position:absolute;top:100%;z-index:10;min-width:10rem;margin-top:.5rem;padding:.5rem 0;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);border-radius:.5rem;opacity:0;visibility:hidden;transform:translateY(-0.5rem);transition:transform 100ms ease-in-out,opacity 150ms,visibility 150ms;box-shadow:0px 4px 4px 0px rgba(0, 0, 0, 0.2),0px 8px 12px 6px rgba(0, 0, 0, 0.1);background-color:#fafdfa;color:#191c1b;background-color:#f1f6f2}.menu .item.is-dropdown .dropdown{right:0}.brand .item.is-dropdown .dropdown{left:0}@media(prefers-color-scheme: dark){.item.is-dropdown .dropdown{background-color:#191c1b;color:#e1e3e0;background-color:#242a28}}.item.is-dropdown .dropdown hr{margin:.5rem 0;border:0;border-top:1px solid rgba(63,73,70,.24)}@media(prefers-color-scheme: dark){.item.is-dropdown .dropdown hr{border-top:1px solid rgba(191,201,196,.24)}}.item.is-dropdown .dropdown .item{padding:0 1rem;line-height:2.5rem;border-radius:0}.item.is-dropdown:hover .dropdown{opacity:1;visibility:visible;transform:translateY(0)}.dropdown-label{display:flex;align-items:center;padding:.5rem 1rem;line-height:2.5rem;border-radius:9999px;cursor:default;line-height:1rem;font-size:.75rem;letter-spacing:.0416666667rem;font-weight:500}/*# sourceMappingURL=Navbar.module.css.map */ diff --git a/components/layout/Navbar.module.css.map b/components/layout/Navbar.module.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["Navbar.module.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss"],"names":[],"mappings":"AAsBA,QACE,gBACA,WAVA,yBACA,cAEA,oBACE,mCAYE,mBAhBJ,yBACA,cAEA,+BACE,kCAYE,qBAhBJ,yBACA,cAEA,iCACE,kCAYE,oBAhBJ,yBACA,cAEA,gCACE,kCAYE,iBAhBJ,yBACA,cAEA,6BACE,iCC8GF,mCD1GF,QARE,yBACA,cAEA,oBACE,sCAYE,mBAhBJ,yBACA,cAEA,+BACE,qCAYE,qBAhBJ,yBACA,cAEA,iCACE,sCAYE,oBAhBJ,yBACA,cAEA,gCACE,sCAYE,iBAhBJ,yBACA,cAEA,6BACE,uCAkBF,mBACE,aACA,eACA,oBACA,WAhCU,OAmCZ,6BAEE,aACA,oBAGF,eACE,cEoEA,mBACA,eACA,0BACA,gBFpEA,4BACE,iBAIJ,cACE,OACA,yBAEA,0BACE,uBACA,eAIJ,cACE,aACA,mBACA,QA9DW,OA+DX,YAhEU,OAiEV,cA/DU,OAkEZ,eACE,cAEA,oCACE","file":"Navbar.module.css"} -\ No newline at end of file +{"version":3,"sourceRoot":"","sources":["Navbar.module.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss","../../styles/core/_elevate.scss"],"names":[],"mappings":"AA0BA,QACE,gBACA,WAVA,yBACA,cAEA,oBACE,mCAYE,mBAhBJ,yBACA,cAEA,+BACE,kCAYE,qBAhBJ,yBACA,cAEA,iCACE,kCAYE,oBAhBJ,yBACA,cAEA,gCACE,kCAYE,iBAhBJ,yBACA,cAEA,6BACE,iCC0GF,mCDtGF,QARE,yBACA,cAEA,oBACE,sCAYE,mBAhBJ,yBACA,cAEA,+BACE,qCAYE,qBAhBJ,yBACA,cAEA,iCACE,sCAYE,oBAhBJ,yBACA,cAEA,gCACE,sCAYE,iBAhBJ,yBACA,cAEA,6BACE,uCAkBF,mBACE,aACA,eACA,oBACA,WAnCU,OA2Cd,aAEE,aACA,oBAGF,OACE,cE2DE,mBACA,eACA,0BACA,gBF3DF,oBACE,iBAIJ,MACE,OACA,yBAEA,kBACE,uBACA,eAQJ,MACE,aACA,mBACA,QA1Ea,OA2Eb,YA5EY,OA6EZ,cA3EY,OA6EZ,YACE,cAEA,8BACE,qBASN,kBACE,kBACA,eAEA,4BACE,kBACA,SACA,WACA,UAhGa,MAiGb,iBACA,gBACA,WAzGK,wDA0GL,cAnGc,MAoGd,UACA,kBACA,8BACA,sEGvDF,kFHoEI,yBACA,cG/EJ,yBHmEE,kCACE,QAGF,mCACE,OCIJ,mCDvBA,4BAyBI,yBACA,cG/EJ,0BHoFE,+BACE,eACA,SAEE,wCCZN,mCDQE,+BAII,4CAIJ,kCACE,QAvIS,OAwIT,YAzIQ,OA0IR,gBASF,kCACE,UACA,mBACA,wBAKN,gBACE,aACA,mBACA,QAxJuB,WAyJvB,YA/JY,OAgKZ,cA9JY,OA+JZ,eEpDE,iBACA,iBACA,8BACA","file":"Navbar.module.css"} +\ No newline at end of file diff --git a/components/layout/Navbar.module.scss b/components/layout/Navbar.module.scss @@ -2,6 +2,7 @@ @use 'core/colors'; @use 'core/vars'; @use 'core/typography'; +@use 'core/elevate'; $height: 3.5rem; $shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); @@ -10,6 +11,9 @@ $item-height: 2.5rem; $item-padding: 0 vars.$gap; $item-radius: 9999px; $item-hover-background-opacity: 10%; +$dropdown-width: 10rem; +$dropdown-radius: 0.5rem; +$dropdown-label-padding: 0.5rem vars.$gap; @mixin _apply-navbar-color($theme, $color, $on-color) { background-color: colors.get($theme, $color); @@ -40,41 +44,49 @@ $item-hover-background-opacity: 10%; align-items: stretch; min-height: $item-height; } +} - .brand, - .menu { - display: flex; - align-items: stretch; - } +// ---------------------------------------- +// Branding, Menu +// ---------------------------------------- + +.brand, +.menu { + display: flex; + align-items: stretch; +} - .brand { - flex-shrink: 0; - @include typography.apply('title-medium'); +.brand { + flex-shrink: 0; + @include typography.apply('title-medium'); - .navbar-item { - font-weight: bold; - } + .navbar-item { + font-weight: bold; } +} - .menu { - flex: 1; - justify-content: flex-end; +.menu { + flex: 1; + justify-content: flex-end; - &.is-centered { - justify-content: center; - margin-right: 0; - } + &.is-centered { + justify-content: center; + margin-right: 0; } +} - .item { - display: flex; - align-items: center; - padding: $item-padding; - line-height: $item-height; - border-radius: $item-radius; - } +// ---------------------------------------- +// Item +// ---------------------------------------- + +.item { + display: flex; + align-items: center; + padding: $item-padding; + line-height: $item-height; + border-radius: $item-radius; - a.item { + &[href] { color: inherit; &, &:hover { @@ -82,3 +94,81 @@ $item-hover-background-opacity: 10%; } } } + +// ---------------------------------------- +// Dropdown item +// ---------------------------------------- + +.item.is-dropdown { + position: relative; + cursor: default; + + .dropdown { + position: absolute; + top: 100%; + z-index: 10; + min-width: $dropdown-width; + margin-top: 0.5rem; + padding: 0.5rem 0; + box-shadow: $shadow; + border-radius: $dropdown-radius; + opacity: 0; + visibility: hidden; + transform: translateY(-0.5rem); + transition: transform 100ms ease-in-out, opacity 150ms, visibility 150ms; + + .menu & { + right: 0; + } + + .brand & { + left: 0; + } + + @include elevate.apply-box-shadow(5); + + @include colors.apply-themes() using ($theme) { + background-color: colors.get($theme, 'surface'); + color: colors.get($theme, 'on-surface'); + + @include elevate.apply-background-color($theme, 5, 'surface-variant'); + } + + hr { + margin: 0.5rem 0; + border: 0; + @include colors.apply-themes() using ($theme) { + border-top: 1px solid rgba(colors.get($theme, 'on-surface-variant'), 0.24); + } + } + + .item { + padding: $item-padding; + line-height: $item-height; + border-radius: 0; + } + + .item:hover { + //background-color: rgba(colors.get('on-surface'), $item-hover-background-opacity); + } + } + + &:hover { + .dropdown { + opacity: 1; + visibility: visible; + transform: translateY(0); + } + } +} + +.dropdown-label { + display: flex; + align-items: center; + padding: $dropdown-label-padding; + line-height: $item-height; + border-radius: $item-radius; + cursor: default; + + @include typography.apply('label-medium'); +} diff --git a/components/layout/NavbarDropdownItem.tsx b/components/layout/NavbarDropdownItem.tsx @@ -0,0 +1,21 @@ +import classNames from '@/lib/classnames' +import Link, { LinkProps } from 'next/link' +import { AnchorHTMLAttributes, ReactNode } from 'react' +import styles from './Navbar.module.css' + +export interface NavbarItemProps { + label: ReactNode + children?: ReactNode +} + +export default function NavbarDropdownItem (props: NavbarItemProps) { + return ( + <div {...classNames(styles['item'], styles['is-dropdown'])}> + {props.label} + + <div {...classNames(styles['dropdown'])}> + {props.children} + </div> + </div> + ) +} diff --git a/components/layout/NavbarDropdownLabel.tsx b/components/layout/NavbarDropdownLabel.tsx @@ -0,0 +1,13 @@ +import classNames from '@/lib/classnames' +import { ReactNode } from 'react' +import styles from './Navbar.module.css' + +export interface NavbarDropdownLabelProps { + children?: ReactNode +} + +export default function NavbarDropdownLabel (props: NavbarDropdownLabelProps) { + return ( + <div {...classNames(styles['dropdown-label'])}>{props.children}</div> + ) +}