diff --git a/README.md b/README.md index fa06354..64f3d8e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This project uses postgres database. By getting some config data from the database, I as a maintainer can manage some of the contents without modifying the source code and redeploying the application. I leverage Server Component to let Next.js server get the config data directly from the database and render it on the page. These config data are embedded in the HTML as a script tag and no API calls happen on the client side, which is more secure than using normal API. +![Architecture](./Takuma's_portfolio_architecture.gif) + I use npm as package manager for this project, because it works well in any case including CIs. ### Install prisma cli diff --git a/Takuma's_portfolio_architecture.gif b/Takuma's_portfolio_architecture.gif new file mode 100644 index 0000000..8937a4a Binary files /dev/null and b/Takuma's_portfolio_architecture.gif differ diff --git a/app/AppBodyStyle.tsx b/app/AppBodyStyle.tsx index f5582fd..7838c37 100644 --- a/app/AppBodyStyle.tsx +++ b/app/AppBodyStyle.tsx @@ -6,6 +6,9 @@ const AppBodyStyle = createGlobalStyle<{ $colorScheme: ColorScheme }>` body { min-width: 100vw; min-height: 100vh; + @supports (min-height: 100dvh) { + min-height: 100dvh; + } } body { background-color: ${props => (props.theme as ProjectTheme)?.colors.surfacePrimary[props.$colorScheme]}; diff --git a/app/lib/backend.ts b/app/lib/backend.ts index fa860bd..4cc1b97 100644 --- a/app/lib/backend.ts +++ b/app/lib/backend.ts @@ -10,5 +10,5 @@ const client = generateClient() export async function getData(): Promise { const { data } = await client.queries.getDbData() - return data !== null && typeof data === 'string' ? JSON.parse(data) : {} + return data !== null && typeof data === 'string' ? JSON.parse(data) : { config: {} } } diff --git a/app/page.tsx b/app/page.tsx index d7e33d5..df489ce 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -23,7 +23,10 @@ const Container = styled.div` flex-direction: column; align-items: center; justify-content: center; - height: 100vh; + min-height: 100vh; + @supports (min-height: 100dvh) { + min-height: 100dvh; + } ` const HeaderContainer = styled.div` position: absolute; diff --git a/stories/Toggle.tsx b/stories/Toggle.tsx index 764a65d..56eb28d 100644 --- a/stories/Toggle.tsx +++ b/stories/Toggle.tsx @@ -14,34 +14,59 @@ type ToggleItems = { const Container = styled.div<{ $colorScheme: ColorScheme }>` display: flex; align-items: center; - gap: 0.5rem; + gap: 0.35rem; ` +const TOGGLE_BG_HEIGHT_DEFAULT = 19 +const TOGGLE_BG_WIDTH_DEFAULT = 38 +const TOGGLE_KNOB_SIZE_DEFAULT = 13 +const TOGGLE_BG_HEIGHT_MOBILE = 18 +const TOGGLE_BG_WIDTH_MOBILE = 34 +const TOGGLE_KNOB_SIZE_MOBILE = 12 const ToggleUI = styled.div<{ $colorScheme: ColorScheme, $toggleDuration: number, $selectedSide: SelectedSide }>` - height: 1.5rem; - width: ${50 / 16}rem; - border-radius: ${1.5 / 2}rem; + height: ${({ theme }) => TOGGLE_BG_HEIGHT_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + width: ${({ theme }) => TOGGLE_BG_WIDTH_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + border-radius: ${({ theme }) => TOGGLE_BG_HEIGHT_DEFAULT / (theme as ProjectTheme).rootFontSize.default / 2}rem; + @media (max-width: ${({ theme }) => (theme as ProjectTheme).breakPoints.minWidthTablet - 1}px) { + height: ${({ theme }) => TOGGLE_BG_HEIGHT_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + width: ${({ theme }) => TOGGLE_BG_WIDTH_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + border-radius: ${({ theme }) => TOGGLE_BG_HEIGHT_MOBILE / (theme as ProjectTheme).rootFontSize.mobile / 2}rem; + } background-color: ${({ $colorScheme, theme }) => (theme as ProjectTheme).colors.toggleBg[$colorScheme]}; position: relative; &::before { position: absolute; content: ""; - height: ${18 / 16}rem; - width: ${18 / 16}rem; - border-radius: ${18 / 16 / 2}rem; + height: ${({ theme }) => TOGGLE_KNOB_SIZE_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + width: ${({ theme }) => TOGGLE_KNOB_SIZE_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + border-radius: ${({ theme }) => TOGGLE_KNOB_SIZE_DEFAULT / (theme as ProjectTheme).rootFontSize.default / 2}rem; + left: ${({ theme, $selectedSide }) => $selectedSide === 'left' ? 3 / (theme as ProjectTheme).rootFontSize.default : (TOGGLE_BG_WIDTH_DEFAULT - TOGGLE_KNOB_SIZE_DEFAULT - 3) / (theme as ProjectTheme).rootFontSize.default}rem; + @media (max-width: ${({ theme }) => (theme as ProjectTheme).breakPoints.minWidthTablet - 1}px) { + height: ${({ theme }) => TOGGLE_KNOB_SIZE_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + width: ${({ theme }) => TOGGLE_KNOB_SIZE_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + border-radius: ${({ theme }) => TOGGLE_KNOB_SIZE_MOBILE / (theme as ProjectTheme).rootFontSize.mobile / 2}rem; + left: ${({ theme, $selectedSide }) => $selectedSide === 'left' ? 3 / (theme as ProjectTheme).rootFontSize.mobile : (TOGGLE_BG_WIDTH_MOBILE - TOGGLE_KNOB_SIZE_MOBILE - 3) / (theme as ProjectTheme).rootFontSize.mobile}rem; + } background-color: ${({ $colorScheme, theme }) => (theme as ProjectTheme).colors.toggleKnob[$colorScheme]}; box-shadow: ${({ $colorScheme, theme }) => (theme as ProjectTheme).shadows.toggleKnob[$colorScheme]}; top: 50%; transform: translateY(-50%); - left: ${({ $selectedSide }) => $selectedSide === 'left' ? 3 / 16 : (50 - 3 - 18) / 16}rem; transition: left ${({ $toggleDuration }) => $toggleDuration}ms ease-in-out; } ` const IconWrapper = styled.div<{ $colorScheme: ColorScheme }>` - height: 1.5rem; - width: 1.5rem; + height: ${({ theme }) => TOGGLE_BG_HEIGHT_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + width: ${({ theme }) => TOGGLE_BG_HEIGHT_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + @media (max-width: ${({ theme }) => (theme as ProjectTheme).breakPoints.minWidthTablet - 1}px) { + height: ${({ theme }) => TOGGLE_BG_HEIGHT_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + width: ${({ theme }) => TOGGLE_BG_HEIGHT_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + } svg { - height: 1.5rem; - width: 1.5rem; + height: ${({ theme }) => TOGGLE_BG_HEIGHT_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + width: ${({ theme }) => TOGGLE_BG_HEIGHT_DEFAULT / (theme as ProjectTheme).rootFontSize.default}rem; + @media (max-width: ${({ theme }) => (theme as ProjectTheme).breakPoints.minWidthTablet - 1}px) { + height: ${({ theme }) => TOGGLE_BG_HEIGHT_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + width: ${({ theme }) => TOGGLE_BG_HEIGHT_MOBILE / (theme as ProjectTheme).rootFontSize.mobile}rem; + } path { fill: ${({ $colorScheme, theme }) => (theme as ProjectTheme).colors.iconPrimary[$colorScheme]}; }