initial commit and first version release
This commit is contained in:
parent
b3603c51cf
commit
10b179f6e3
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
APP_NAME=
|
||||
APP_LANG=
|
@ -1,3 +1,7 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
"extends": ["next/core-web-vitals", "prettier"],
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off",
|
||||
"@next/next/no-page-custom-font": "off"
|
||||
}
|
||||
}
|
||||
|
53
.prettierignore
Normal file
53
.prettierignore
Normal file
@ -0,0 +1,53 @@
|
||||
# Using this project's .gitignore file as base.
|
||||
#
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next
|
||||
/out
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# configs
|
||||
package.json
|
||||
package-lock.json
|
||||
next.config.js
|
||||
pnpm-lock.yaml
|
||||
postcss.config.js
|
||||
tsconfig.json
|
||||
yarn.lock
|
||||
.eslintrc.json
|
||||
.prettierrc.json
|
||||
|
||||
# public
|
||||
/public
|
||||
README.md
|
14
.prettierrc.json
Normal file
14
.prettierrc.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"jsxSingleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"singleAttributePerLine": true
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"discord.enabled": false
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import "@mantine/core/styles.css";
|
||||
import React from "react";
|
||||
import { MantineProvider, ColorSchemeScript } from "@mantine/core";
|
||||
import { theme } from "../theme";
|
||||
|
||||
export const metadata = {
|
||||
title: "Mantine Next.js template",
|
||||
description: "I am using Mantine with Next.js!",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: any }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<ColorSchemeScript />
|
||||
<link rel="shortcut icon" href="/favicon.svg" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<MantineProvider theme={theme}>{children}</MantineProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export default function HomePage() {
|
||||
return <div>Home page</div>;
|
||||
}
|
17
package.json
17
package.json
@ -1,19 +1,28 @@
|
||||
{
|
||||
"name": "mantine-minimal-next-template",
|
||||
"name": "portfolio",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Refansa",
|
||||
"email": "m.refansa.am@gmail.com",
|
||||
"url": "https://github.com/Refansa"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"prettier": "prettier . \"./**/*.{js,jsx,ts,tsx}\" --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/core": "7.1.3",
|
||||
"@mantine/hooks": "7.1.3",
|
||||
"@tabler/icons-react": "^2.39.0",
|
||||
"framer-motion": "^10.16.4",
|
||||
"next": "13.4.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"react-intersection-observer": "^9.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.2.5",
|
||||
@ -21,9 +30,11 @@
|
||||
"@types/react-dom": "18.2.4",
|
||||
"eslint": "8.41.0",
|
||||
"eslint-config-next": "13.4.4",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-preset-mantine": "1.8.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "3.0.3",
|
||||
"typescript": "5.0.4"
|
||||
}
|
||||
}
|
2656
pnpm-lock.yaml
Normal file
2656
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
1
public/assets/hand-coding.svg
Normal file
1
public/assets/hand-coding.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 56 KiB |
4
public/grid.svg
Normal file
4
public/grid.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg id="download" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
|
||||
<path id="Path_3" data-name="Path 3" d="M96,95h4v1H96v4H95V96H86v4H85V96H76v4H75V96H66v4H65V96H56v4H55V96H46v4H45V96H36v4H35V96H26v4H25V96H16v4H15V96H0V95H15V86H0V85H15V76H0V75H15V66H0V65H15V56H0V55H15V46H0V45H15V36H0V35H15V26H0V25H15V16H0V15H15V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96Zm-1,0V86H86v9ZM85,95V86H76v9ZM75,95V86H66v9ZM65,95V86H56v9ZM55,95V86H46v9ZM45,95V86H36v9ZM35,95V86H26v9ZM25,95V86H16v9ZM16,85h9V76H16Zm10,0h9V76H26Zm10,0h9V76H36Zm10,0h9V76H46Zm10,0h9V76H56Zm10,0h9V76H66Zm10,0h9V76H76Zm10,0h9V76H86Zm9-10V66H86v9ZM85,75V66H76v9ZM75,75V66H66v9ZM65,75V66H56v9ZM55,75V66H46v9ZM45,75V66H36v9ZM35,75V66H26v9ZM25,75V66H16v9ZM16,65h9V56H16Zm10,0h9V56H26Zm10,0h9V56H36Zm10,0h9V56H46Zm10,0h9V56H56Zm10,0h9V56H66Zm10,0h9V56H76Zm10,0h9V56H86Zm9-10V46H86v9ZM85,55V46H76v9ZM75,55V46H66v9ZM65,55V46H56v9ZM55,55V46H46v9ZM45,55V46H36v9ZM35,55V46H26v9ZM25,55V46H16v9ZM16,45h9V36H16Zm10,0h9V36H26Zm10,0h9V36H36Zm10,0h9V36H46Zm10,0h9V36H56Zm10,0h9V36H66Zm10,0h9V36H76Zm10,0h9V36H86Zm9-10V26H86v9ZM85,35V26H76v9ZM75,35V26H66v9ZM65,35V26H56v9ZM55,35V26H46v9ZM45,35V26H36v9ZM35,35V26H26v9ZM25,35V26H16v9ZM16,25h9V16H16Zm10,0h9V16H26Zm10,0h9V16H36Zm10,0h9V16H46Zm10,0h9V16H56Zm10,0h9V16H66Zm10,0h9V16H76Zm10,0h9V16H86Z" fill="rgba(255,255,255,0.1)" fill-rule="evenodd" opacity="0.5"/>
|
||||
<path id="Path_4" data-name="Path 4" d="M6,5V0H5V5H0V6H5v94H6V6h94V5Z" fill="rgba(255,255,255,0.1)" fill-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
12
src/app/global.css
Normal file
12
src/app/global.css
Normal file
@ -0,0 +1,12 @@
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Offset the anchor hashtag link
|
||||
*/
|
||||
:target::before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 144px;
|
||||
margin-top: -144px;
|
||||
}
|
67
src/app/layout.tsx
Normal file
67
src/app/layout.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { MantineProvider, ColorSchemeScript } from '@mantine/core'
|
||||
import { Metadata } from 'next'
|
||||
|
||||
import './global.css'
|
||||
import '@mantine/core/styles.css'
|
||||
import { resolver, theme } from '../styles/theme'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
authors: [
|
||||
{
|
||||
name: 'Refansa',
|
||||
url: 'https://github.com/Refansa',
|
||||
},
|
||||
],
|
||||
title: 'Refansa - Software Developer',
|
||||
description: "Refansa's portfolio website",
|
||||
viewport: 'width=device-width, initial-scale=1',
|
||||
colorScheme: 'dark',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang={process.env.APP_LANG ?? 'en'}>
|
||||
<head>
|
||||
<link
|
||||
rel='preconnect'
|
||||
href='https://fonts.googleapis.com'
|
||||
crossOrigin='anonymous'
|
||||
/>
|
||||
<link
|
||||
rel='preconnect'
|
||||
href='https://fonts.gstatic.com'
|
||||
crossOrigin='anonymous'
|
||||
/>
|
||||
<link
|
||||
href='https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap'
|
||||
rel='preload'
|
||||
/>
|
||||
<link
|
||||
href='https://fonts.googleapis.com/css2?family=Gabarito:wght@400;500;600;700;800;900&display=swap'
|
||||
rel='preload'
|
||||
/>
|
||||
<link
|
||||
href='https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'
|
||||
rel='preload'
|
||||
/>
|
||||
<ColorSchemeScript
|
||||
defaultColorScheme={'dark'}
|
||||
forceColorScheme={'dark'}
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<MantineProvider
|
||||
defaultColorScheme={'dark'}
|
||||
forceColorScheme={'dark'}
|
||||
cssVariablesResolver={resolver}
|
||||
theme={theme}>
|
||||
{children}
|
||||
</MantineProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
33
src/app/page.tsx
Normal file
33
src/app/page.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import { AppShell, Container, rem } from '@mantine/core'
|
||||
import Introduction from '../partials/Introduction'
|
||||
import About from '../partials/About'
|
||||
import Navbar from '../components/Navbar'
|
||||
import Footer from '../components/Footer'
|
||||
import SlideUpWhenVisible from '../hooks/slideUpWhenVisible'
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<AppShell
|
||||
withBorder={false}
|
||||
padding={'xl'}
|
||||
header={{ height: 72 }}
|
||||
className={'shell-root'}>
|
||||
<AppShell.Header>
|
||||
<Navbar />
|
||||
</AppShell.Header>
|
||||
<AppShell.Main>
|
||||
<SlideUpWhenVisible>
|
||||
<Container
|
||||
size={rem(1920)}
|
||||
px={{ base: '0rem', lg: '4rem', xl: '8rem' }}>
|
||||
<Introduction />
|
||||
<About />
|
||||
<Footer />
|
||||
</Container>
|
||||
</SlideUpWhenVisible>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
)
|
||||
}
|
18
src/components/Footer.tsx
Normal file
18
src/components/Footer.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Anchor, Container, Text } from '@mantine/core'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<Container>
|
||||
<Text
|
||||
fw='bold'
|
||||
ta='center'>
|
||||
Created with ❤️ by{' '}
|
||||
<Anchor
|
||||
fw='bold'
|
||||
href='https://github.com/Refansa'>
|
||||
Refansa
|
||||
</Anchor>
|
||||
</Text>
|
||||
</Container>
|
||||
)
|
||||
}
|
3
src/components/NavIcon.module.css
Normal file
3
src/components/NavIcon.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.bracket {
|
||||
color: var(--mantine-color-dark-4);
|
||||
}
|
21
src/components/NavIcon.tsx
Normal file
21
src/components/NavIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Anchor, Title } from '@mantine/core'
|
||||
import Link from 'next/link'
|
||||
|
||||
import classes from './NavIcon.module.css'
|
||||
|
||||
function NavIcon() {
|
||||
return (
|
||||
<Anchor
|
||||
component={Link}
|
||||
href='/'
|
||||
underline='never'>
|
||||
<Title ff='Fira Code'>
|
||||
<span className={classes.bracket}>{'{'}</span>
|
||||
<span>R</span>
|
||||
<span className={classes.bracket}>{'}'}</span>
|
||||
</Title>
|
||||
</Anchor>
|
||||
)
|
||||
}
|
||||
|
||||
export default NavIcon
|
20
src/components/NavLink.tsx
Normal file
20
src/components/NavLink.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { Button } from '@mantine/core'
|
||||
|
||||
interface NavLinkProps {
|
||||
content: string
|
||||
href: string
|
||||
}
|
||||
|
||||
function NavLink({ content, href }: NavLinkProps) {
|
||||
return (
|
||||
<Button
|
||||
component={'a'}
|
||||
variant='subtle'
|
||||
size='md'
|
||||
href={href}>
|
||||
{content}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default NavLink
|
8
src/components/Navbar.module.css
Normal file
8
src/components/Navbar.module.css
Normal file
@ -0,0 +1,8 @@
|
||||
.navigation {
|
||||
display: flex;
|
||||
padding-inline: 3vw;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
border-bottom: 1px solid var(--mantine-color-dark-7);
|
||||
}
|
27
src/components/Navbar.tsx
Normal file
27
src/components/Navbar.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { Box, Group } from '@mantine/core'
|
||||
|
||||
import classes from './Navbar.module.css'
|
||||
import NavIcon from './NavIcon'
|
||||
import NavLink from './NavLink'
|
||||
|
||||
function Navbar() {
|
||||
return (
|
||||
<Box
|
||||
component='nav'
|
||||
className={classes.navigation}>
|
||||
<NavIcon />
|
||||
<Group visibleFrom='sm'>
|
||||
<NavLink
|
||||
content='About'
|
||||
href='/#about'
|
||||
/>
|
||||
<NavLink
|
||||
content='Projects'
|
||||
href='https://github.com/Refansa?tab=repositories'
|
||||
/>
|
||||
</Group>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
33
src/hooks/slideUpWhenVisible.tsx
Normal file
33
src/hooks/slideUpWhenVisible.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { motion, useAnimation } from 'framer-motion'
|
||||
import { useEffect } from 'react'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
|
||||
export default function SlideUpWhenVisible({
|
||||
children,
|
||||
threshold,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
threshold?: number | number[]
|
||||
}) {
|
||||
const controls = useAnimation()
|
||||
const [ref, inView] = useInView({ threshold: threshold ? threshold : 0.35 })
|
||||
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
controls.start('visible')
|
||||
}
|
||||
}, [controls, inView])
|
||||
return (
|
||||
<motion.div
|
||||
ref={ref}
|
||||
animate={controls}
|
||||
initial='hidden'
|
||||
transition={{ duration: 0.4 }}
|
||||
variants={{
|
||||
visible: { opacity: 1, y: 0 },
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
}}>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
3
src/partials/About.module.css
Normal file
3
src/partials/About.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.grayscale {
|
||||
filter: grayscale(1);
|
||||
}
|
76
src/partials/About.tsx
Normal file
76
src/partials/About.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import {
|
||||
Anchor,
|
||||
Box,
|
||||
Container,
|
||||
Image,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core'
|
||||
import classes from './About.module.css'
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<Box
|
||||
id='about'
|
||||
mt='xl'
|
||||
mah='100vh'
|
||||
h='100vh'>
|
||||
<SimpleGrid
|
||||
cols={{ base: 1, sm: 2 }}
|
||||
spacing={{ base: 'sm', md: 'md', lg: 'lg' }}>
|
||||
<Stack>
|
||||
<Title>A bit About Me</Title>
|
||||
<Stack gap='md'>
|
||||
<Text>
|
||||
Hello 👋 Refansa here. Muhammad Refansa Ali Muzky is my full name.
|
||||
I'm an 18 years old software developer, just came fresh out of the
|
||||
oven. Constantly seeking new open source projects to contribute to
|
||||
and enjoy working with others to make a meaningful contribution.
|
||||
</Text>
|
||||
<Text>
|
||||
I thrive on challenging coding projects and love nothing more than
|
||||
diving into complex software development tasks. As a lover of open
|
||||
source, I believe that sharing knowledge and collaborating on
|
||||
projects is essential for the advancement of the tech industry.
|
||||
</Text>
|
||||
<Text
|
||||
mt='lg'
|
||||
c='dark.1'
|
||||
fs='italic'>
|
||||
I use Archlinux btw.
|
||||
</Text>
|
||||
<Text
|
||||
mt='xl'
|
||||
fw='bold'>
|
||||
Peace from Indonesia 🇮🇩
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Container visibleFrom='sm'>
|
||||
<Stack
|
||||
justify='center'
|
||||
w={360}>
|
||||
<Image
|
||||
className={classes.grayscale}
|
||||
src='/assets/hand-coding.svg'
|
||||
alt='Coding Illustration'
|
||||
width={360}
|
||||
height='auto'
|
||||
/>
|
||||
<Anchor
|
||||
ta='center'
|
||||
fs='italic'
|
||||
fz='xs'
|
||||
href='https://storyset.com/work'>
|
||||
Work illustrations by Storyset
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</Container>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default About
|
20
src/partials/Introduction.module.css
Normal file
20
src/partials/Introduction.module.css
Normal file
@ -0,0 +1,20 @@
|
||||
.background {
|
||||
&::before {
|
||||
content: '';
|
||||
background-image: url('/grid.svg');
|
||||
background-repeat: repeat;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.4;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 4rem;
|
||||
height: 0.125rem;
|
||||
background-color: var(--mantine-color-coffee-8);
|
||||
border-radius: 1rem;
|
||||
}
|
66
src/partials/Introduction.tsx
Normal file
66
src/partials/Introduction.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { Box, Button, Flex, Group, Text, Title } from '@mantine/core'
|
||||
import { IconBrandGithub, IconMail } from '@tabler/icons-react'
|
||||
import classes from './Introduction.module.css'
|
||||
|
||||
function Introduction() {
|
||||
return (
|
||||
<Box
|
||||
mt='xl'
|
||||
mah='100vh'
|
||||
h='80vh'
|
||||
className={classes.background}>
|
||||
<Flex
|
||||
align='center'
|
||||
gap='md'>
|
||||
<Box className={classes.line} />
|
||||
<Text
|
||||
fz='display-lg'
|
||||
fw='bold'
|
||||
c='coffee.6'>
|
||||
Hi There!
|
||||
</Text>
|
||||
</Flex>
|
||||
<Title
|
||||
variant='shadow'
|
||||
fz='display-xl'
|
||||
fw='bold'
|
||||
lh={1.2}>
|
||||
I'm Refansa.
|
||||
</Title>
|
||||
<Text
|
||||
fz='display-lg'
|
||||
fw='bold'
|
||||
lh='xs'>
|
||||
A Passionate, self-taught Software Developer{' '}
|
||||
<Text
|
||||
component='span'
|
||||
display='block'
|
||||
inherit
|
||||
c='dark.1'>
|
||||
A supporter of Open Source Software.
|
||||
</Text>
|
||||
</Text>
|
||||
<Group mt={'xl'}>
|
||||
<Button
|
||||
component='a'
|
||||
href='https://github.com/Refansa'
|
||||
target='_blank'
|
||||
leftSection={<IconBrandGithub />}
|
||||
size='lg'
|
||||
variant='light'>
|
||||
Github
|
||||
</Button>
|
||||
<Button
|
||||
component='a'
|
||||
href='mailto:m.refansa.am@gmail.com'
|
||||
leftSection={<IconMail />}
|
||||
size='lg'
|
||||
variant='light'>
|
||||
Email
|
||||
</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Introduction
|
9
src/styles/extends/Button.module.css
Normal file
9
src/styles/extends/Button.module.css
Normal file
@ -0,0 +1,9 @@
|
||||
.button {
|
||||
transition: all linear 150ms;
|
||||
}
|
||||
|
||||
.button {
|
||||
&:active {
|
||||
transform: scale(calc(0.95 * var(--mantine-scale)));
|
||||
}
|
||||
}
|
8
src/styles/extends/Button.ts
Normal file
8
src/styles/extends/Button.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Button as Component } from '@mantine/core'
|
||||
import classes from './Button.module.css'
|
||||
|
||||
export const Button = Component.extend({
|
||||
defaultProps: {
|
||||
className: classes.button,
|
||||
},
|
||||
})
|
13
src/styles/extends/Text.module.css
Normal file
13
src/styles/extends/Text.module.css
Normal file
@ -0,0 +1,13 @@
|
||||
.text {
|
||||
@mixin dark {
|
||||
&[data-variant='curvy'] {
|
||||
text-decoration-style: wavy;
|
||||
text-decoration-color: var(--mantine-color-coffee-4);
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
&[data-variant='shadow'] {
|
||||
text-shadow: 0.25rem 0.25rem var(--mantine-color-dark-4);
|
||||
}
|
||||
}
|
||||
}
|
9
src/styles/extends/Text.ts
Normal file
9
src/styles/extends/Text.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Text as Component } from '@mantine/core'
|
||||
|
||||
import classes from './Text.module.css'
|
||||
|
||||
export const Text = Component.extend({
|
||||
defaultProps: {
|
||||
className: classes.text,
|
||||
},
|
||||
})
|
5
src/styles/extends/TextInput.module.css
Normal file
5
src/styles/extends/TextInput.module.css
Normal file
@ -0,0 +1,5 @@
|
||||
.input {
|
||||
@mixin dark {
|
||||
background-color: var(--mantine-color-dark-6);
|
||||
}
|
||||
}
|
11
src/styles/extends/TextInput.ts
Normal file
11
src/styles/extends/TextInput.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { TextInput as Component } from '@mantine/core'
|
||||
|
||||
import classes from './TextInput.module.css'
|
||||
|
||||
export const TextInput = Component.extend({
|
||||
defaultProps: {
|
||||
classNames: {
|
||||
input: classes.input,
|
||||
},
|
||||
},
|
||||
})
|
5
src/styles/extends/Textarea.module.css
Normal file
5
src/styles/extends/Textarea.module.css
Normal file
@ -0,0 +1,5 @@
|
||||
.input {
|
||||
@mixin dark {
|
||||
background-color: var(--mantine-color-dark-6);
|
||||
}
|
||||
}
|
11
src/styles/extends/Textarea.ts
Normal file
11
src/styles/extends/Textarea.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Textarea as Component } from '@mantine/core'
|
||||
|
||||
import classes from './Textarea.module.css'
|
||||
|
||||
export const Textarea = Component.extend({
|
||||
defaultProps: {
|
||||
classNames: {
|
||||
input: classes.input,
|
||||
},
|
||||
},
|
||||
})
|
13
src/styles/extends/Title.module.css
Normal file
13
src/styles/extends/Title.module.css
Normal file
@ -0,0 +1,13 @@
|
||||
.title {
|
||||
@mixin dark {
|
||||
&[data-variant='curvy'] {
|
||||
text-decoration-style: wavy;
|
||||
text-decoration-color: var(--mantine-color-coffee-4);
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
&[data-variant='shadow'] {
|
||||
text-shadow: 0.25rem 0.25rem var(--mantine-color-dark-4);
|
||||
}
|
||||
}
|
||||
}
|
9
src/styles/extends/Title.ts
Normal file
9
src/styles/extends/Title.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Title as Component } from '@mantine/core'
|
||||
|
||||
import classes from './Title.module.css'
|
||||
|
||||
export const Title = Component.extend({
|
||||
defaultProps: {
|
||||
className: classes.title,
|
||||
},
|
||||
})
|
5
src/styles/extends/index.ts
Normal file
5
src/styles/extends/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export { Button } from './Button'
|
||||
export { Text } from './Text'
|
||||
export { Textarea } from './Textarea'
|
||||
export { TextInput } from './TextInput'
|
||||
export { Title } from './Title'
|
168
src/styles/theme.ts
Normal file
168
src/styles/theme.ts
Normal file
@ -0,0 +1,168 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
CSSVariablesResolver,
|
||||
MantineColorsTuple,
|
||||
createTheme,
|
||||
} from '@mantine/core'
|
||||
import { fluidDisplay, fluidTypography } from '../utils/typography'
|
||||
import { Button, Text, TextInput, Textarea, Title } from './extends'
|
||||
|
||||
const coffee: MantineColorsTuple = [
|
||||
'#fff4e5',
|
||||
'#f4e7d8',
|
||||
'#e5cfb5',
|
||||
'#d4b590',
|
||||
'#c59e6f',
|
||||
'#bd905a',
|
||||
'#ba894f',
|
||||
'#a3763e',
|
||||
'#926835',
|
||||
'#805928',
|
||||
]
|
||||
|
||||
export const theme = createTheme({
|
||||
// Controls focus ring styles. Supports the following options:
|
||||
// - `auto` – focus ring is displayed only when the user navigates with keyboard (default value)
|
||||
// - `always` – focus ring is displayed when the user navigates with keyboard and mouse
|
||||
// - `never` – focus ring is always hidden (not recommended)
|
||||
//
|
||||
focusRing: 'auto',
|
||||
|
||||
// rem units scale, change if you customize font-size of `<html />` element
|
||||
// default value is `1` (for `100%`/`16px` font-size on `<html />`)
|
||||
//
|
||||
scale: 1,
|
||||
|
||||
// Determines whether `font-smoothing` property should be set on the body, `true` by default
|
||||
fontSmoothing: true,
|
||||
|
||||
// White color
|
||||
white: '#ffffff',
|
||||
|
||||
// Black color
|
||||
black: '#000000',
|
||||
|
||||
// Object of colors, key is color name, value is an array of at least 10 strings (colors)
|
||||
colors: {
|
||||
coffee,
|
||||
},
|
||||
|
||||
// Index of theme.colors[color].
|
||||
// Primary shade is used in all components to determine which color from theme.colors[color] should be used.
|
||||
// Can be either a number (0–9) or an object to specify different color shades for light and dark color schemes.
|
||||
// Default value `{ light: 6, dark: 8 }`
|
||||
//
|
||||
// For example,
|
||||
// { primaryShade: 6 } // shade 6 is used both for dark and light color schemes
|
||||
// { primaryShade: { light: 6, dark: 7 } } // different shades for dark and light color schemes
|
||||
//
|
||||
primaryShade: { light: 4, dark: 8 },
|
||||
|
||||
// Key of `theme.colors`, hex/rgb/hsl values are not supported.
|
||||
// Determines which color will be used in all components by default.
|
||||
// Default value – `blue`.
|
||||
//
|
||||
primaryColor: 'coffee',
|
||||
|
||||
// Function to resolve colors based on variant.
|
||||
// Can be used to deeply customize how colors are applied to `Button`, `ActionIcon`, `ThemeIcon`
|
||||
// and other components that use colors from theme.
|
||||
//
|
||||
// variantColorResolver: VariantColorsResolver;
|
||||
|
||||
// font-family used in all components, system fonts by default
|
||||
fontFamily:
|
||||
'Poppins, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji',
|
||||
|
||||
// Monospace font-family, used in code and other similar components, system fonts by default
|
||||
fontFamilyMonospace:
|
||||
'Fira Code, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
|
||||
|
||||
// Controls various styles of h1-h6 elements, used in TypographyStylesProvider and Title components
|
||||
headings: {
|
||||
fontFamily:
|
||||
'Gabarito, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji',
|
||||
},
|
||||
|
||||
// Object of values that are used to set `border-radius` in all components that support it
|
||||
// radius: MantineRadiusValues;
|
||||
|
||||
// Key of `theme.radius` or any valid CSS value. Default `border-radius` used by most components
|
||||
defaultRadius: 0,
|
||||
|
||||
// Object of values that are used to set various CSS properties that control spacing between elements
|
||||
// spacing: MantineSpacingValues;
|
||||
|
||||
// Object of values that are used to control `font-size` property in all components
|
||||
fontSizes: {
|
||||
'fluid-xs': fluidTypography(8, 12, 480, 1440),
|
||||
'fluid-sm': fluidTypography(10, 14, 480, 1440),
|
||||
'fluid-md': fluidTypography(12, 16, 480, 1440),
|
||||
'fluid-lg': fluidTypography(14, 18, 480, 1440),
|
||||
'fluid-xl': fluidTypography(16, 20, 480, 1440),
|
||||
'display-xl': fluidDisplay(80, 144, 768, 1920),
|
||||
'display-lg': fluidDisplay(24, 36, 768, 1920),
|
||||
'display-md': fluidDisplay(16, 24, 768, 1920),
|
||||
},
|
||||
|
||||
// Object of values that are used to control `line-height` property in `Text` component
|
||||
// lineHeights: MantineLineHeightValues;
|
||||
|
||||
// Object of values that are used to control breakpoints in all components,
|
||||
// values are expected to be defined in em
|
||||
//
|
||||
// breakpoints: MantineBreakpointsValues;
|
||||
|
||||
// Object of values that are used to add `box-shadow` styles to components that support `shadow` prop
|
||||
// shadows: MantineShadowsValues;
|
||||
|
||||
// Determines whether user OS settings to reduce motion should be respected, `false` by default
|
||||
// respectReducedMotion: boolean;
|
||||
|
||||
// Determines which cursor type will be used for interactive elements
|
||||
// - `default` – cursor that is used by native HTML elements, for example, `input[type="checkbox"]` has `cursor: default` styles
|
||||
// - `pointer` – sets `cursor: pointer` on interactive elements that do not have these styles by default
|
||||
//
|
||||
cursorType: 'pointer',
|
||||
|
||||
// Default gradient configuration for components that support `variant="gradient"`
|
||||
// defaultGradient: MantineGradient;
|
||||
|
||||
// Class added to the elements that have active styles, for example, `Button` and `ActionIcon`
|
||||
// activeClassName: string;
|
||||
|
||||
// Class added to the elements that have focus styles, for example, `Button` or `ActionIcon`.
|
||||
// Overrides `theme.focusRing` property.
|
||||
//
|
||||
// focusClassName: string;
|
||||
|
||||
// Allows adding `classNames`, `styles` and `defaultProps` to any component
|
||||
components: {
|
||||
Button,
|
||||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
Title,
|
||||
},
|
||||
|
||||
// Any other properties that you want to access with the theme objects
|
||||
other: {
|
||||
bodyLight: '#ffffff',
|
||||
textLight: '#000000',
|
||||
bodyDark: '#000000',
|
||||
textDark: '#ffffff',
|
||||
},
|
||||
})
|
||||
|
||||
export const resolver: CSSVariablesResolver = (t) => ({
|
||||
variables: {},
|
||||
light: {
|
||||
'--mantine-color-body': t.other.bodyLight,
|
||||
'--mantine-color-text': t.other.textLight,
|
||||
},
|
||||
dark: {
|
||||
'--mantine-color-body': t.other.bodyDark,
|
||||
'--mantine-color-text': t.other.textDark,
|
||||
},
|
||||
})
|
58
src/utils/typography.ts
Normal file
58
src/utils/typography.ts
Normal file
@ -0,0 +1,58 @@
|
||||
export const PIXEL_PER_REM = 16
|
||||
|
||||
/**
|
||||
* Returns a number from pixel to rem.
|
||||
*
|
||||
* @param value The value in pixel.
|
||||
*/
|
||||
export const rem = (value: number): number => {
|
||||
return value / PIXEL_PER_REM
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string of calculated value used for font size in display typography.
|
||||
*
|
||||
* Formula based from https://github.com/abdulrcs/abdulrahman.id/blob/main/styles/theme.js
|
||||
*
|
||||
* @param minFont Minimal font size in pixel.
|
||||
* @param maxFont Maximum font size in pixel.
|
||||
* @param minVW Minimum font size in pixel.
|
||||
* @param maxVW Maximum font size in pixel.
|
||||
*/
|
||||
export const fluidDisplay = (
|
||||
minFont: number,
|
||||
maxFont: number,
|
||||
minVW: number,
|
||||
maxVW: number,
|
||||
) => {
|
||||
let XX = minVW / 100
|
||||
let YY = (100 * (maxFont - minFont)) / (maxVW - minVW)
|
||||
let ZZ = rem(minFont)
|
||||
return `calc(${ZZ}rem + ((1vw - ${XX}px) * ${YY}))`
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string of clamp value used for font size in normal typography.
|
||||
*
|
||||
* Formula based from https://www.aleksandrhovhannisyan.com/blog/fluid-type-scale-with-css-clamp/
|
||||
*
|
||||
* @param minFont Minimal font size in pixel.
|
||||
* @param maxFont Maximum font size in pixel.
|
||||
* @param minVW Minimum font size in pixel.
|
||||
* @param maxVW Maximum font size in pixel.
|
||||
*/
|
||||
export const fluidTypography = (
|
||||
minFont: number,
|
||||
maxFont: number,
|
||||
minVW: number,
|
||||
maxVW: number,
|
||||
): string => {
|
||||
const slope = (maxFont - minFont) / (maxVW - minVW)
|
||||
const intercept = rem(minFont - slope * minVW)
|
||||
const slopeVW = slope * 100
|
||||
|
||||
const minSize = rem(minFont)
|
||||
const maxSize = rem(maxFont)
|
||||
|
||||
return `clamp(${minSize}rem, ${slopeVW}vw + ${intercept}rem, ${maxSize}rem)`
|
||||
}
|
Loading…
Reference in New Issue
Block a user