-
+
{
-
+
-
+
-
+
- {t("modals.settings.descriptions.private")}
+ {t('modals.settings.descriptions.private')}
@@ -243,7 +243,10 @@ const AccountModal = () => {
-
+
diff --git a/components/Alert/index.scss b/components/Alert/index.scss
index fe2e0df9..d8fdc1ea 100644
--- a/components/Alert/index.scss
+++ b/components/Alert/index.scss
@@ -11,7 +11,7 @@
}
.Alert {
- background: white;
+ background: $grey-100;
border-radius: $unit;
display: flex;
flex-direction: column;
@@ -42,7 +42,7 @@
&:not(.btn-disabled) {
background: $grey-90;
- color: $grey-40;
+ color: $grey-50;
&:hover {
background: $grey-80;
diff --git a/components/Alert/index.tsx b/components/Alert/index.tsx
index 8e785c70..7caa9469 100644
--- a/components/Alert/index.tsx
+++ b/components/Alert/index.tsx
@@ -1,9 +1,9 @@
-import React from "react"
-import * as AlertDialog from "@radix-ui/react-alert-dialog"
+import React from 'react'
+import * as AlertDialog from '@radix-ui/react-alert-dialog'
-import "./index.scss"
-import Button from "~components/Button"
-import { ButtonType } from "~utils/enums"
+import './index.scss'
+import Button from '~components/Button'
+import { ButtonType } from '~utils/enums'
// Props
interface Props {
@@ -23,7 +23,7 @@ const Alert = (props: Props) => {
- {props.title ? Error : ""}
+ {props.title ? Error : ''}
{props.message}
@@ -38,7 +38,7 @@ const Alert = (props: Props) => {
{props.primaryActionText}
) : (
- ""
+ ''
)}
diff --git a/components/AxSelect/index.scss b/components/AxSelect/index.scss
index 559645d3..98b5a435 100644
--- a/components/AxSelect/index.scss
+++ b/components/AxSelect/index.scss
@@ -1,48 +1,48 @@
.AXSelect {
- display: flex;
- flex-direction: column;
- gap: $unit;
+ display: flex;
+ flex-direction: column;
+ gap: $unit;
- .AXSet {
- &.hidden {
- display: none;
- }
-
- .errors {
- color: $error;
- display: none;
- padding: $unit 0;
-
- &.visible {
- display: block;
- }
- }
-
- .fields {
- display: flex;
- flex-direction: row;
- gap: $unit;
-
- select {
- flex-grow: 1;
- margin: 0;
- }
-
- .Input {
- -webkit-font-smoothing: antialiased;
- border: none;
- background-color: $grey-90;
- border-radius: 6px;
- box-sizing: border-box;
- color: $grey-00;
- height: $unit * 6;
- display: block;
- font-size: $font-regular;
- padding: $unit;
- text-align: right;
- min-width: 100px;
- width: 100px;
- }
- }
+ .AXSet {
+ &.hidden {
+ display: none;
}
-}
\ No newline at end of file
+
+ .errors {
+ color: $error;
+ display: none;
+ padding: $unit 0;
+
+ &.visible {
+ display: block;
+ }
+ }
+
+ .fields {
+ display: flex;
+ flex-direction: row;
+ gap: $unit;
+
+ select {
+ flex-grow: 1;
+ margin: 0;
+ }
+
+ .Input {
+ -webkit-font-smoothing: antialiased;
+ border: none;
+ background-color: $grey-90;
+ border-radius: 6px;
+ box-sizing: border-box;
+ color: $grey-15;
+ height: $unit * 6;
+ display: block;
+ font-size: $font-regular;
+ padding: $unit;
+ text-align: right;
+ min-width: 100px;
+ width: 100px;
+ }
+ }
+ }
+}
diff --git a/components/AxSelect/index.tsx b/components/AxSelect/index.tsx
index 510920c9..44da8fed 100644
--- a/components/AxSelect/index.tsx
+++ b/components/AxSelect/index.tsx
@@ -9,258 +9,338 @@ import { axData } from '~utils/axData'
import './index.scss'
interface ErrorMap {
- [index: string]: string
- axValue1: string
- axValue2: string
+ [index: string]: string
+ axValue1: string
+ axValue2: string
}
interface Props {
- axType: number
- currentSkills?: SimpleAxSkill[],
- sendValidity: (isValid: boolean) => void
- sendValues: (primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) => void
+ axType: number
+ currentSkills?: SimpleAxSkill[]
+ sendValidity: (isValid: boolean) => void
+ sendValues: (
+ primaryAxModifier: number,
+ primaryAxValue: number,
+ secondaryAxModifier: number,
+ secondaryAxValue: number
+ ) => void
}
const AXSelect = (props: Props) => {
- const router = useRouter()
- const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
- const { t } = useTranslation('common')
+ const router = useRouter()
+ const locale =
+ router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
+ const { t } = useTranslation('common')
- // Set up form states and error handling
- const [errors, setErrors] = useState
({
- axValue1: '',
- axValue2: ''
- })
+ // Set up form states and error handling
+ const [errors, setErrors] = useState({
+ axValue1: '',
+ axValue2: '',
+ })
- const primaryErrorClasses = classNames({
- 'errors': true,
- 'visible': errors.axValue1.length > 0
- })
+ const primaryErrorClasses = classNames({
+ errors: true,
+ visible: errors.axValue1.length > 0,
+ })
- const secondaryErrorClasses = classNames({
- 'errors': true,
- 'visible': errors.axValue2.length > 0
- })
+ const secondaryErrorClasses = classNames({
+ errors: true,
+ visible: errors.axValue2.length > 0,
+ })
- // Refs
- const primaryAxModifierSelect = React.createRef()
- const primaryAxValueInput = React.createRef()
- const secondaryAxModifierSelect = React.createRef()
- const secondaryAxValueInput = React.createRef()
+ // Refs
+ const primaryAxModifierSelect = React.createRef()
+ const primaryAxValueInput = React.createRef()
+ const secondaryAxModifierSelect = React.createRef()
+ const secondaryAxValueInput = React.createRef()
- // States
- const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
- const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
- const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
- const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
+ // States
+ const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
+ const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
+ const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
+ const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
- useEffect(() => {
- if (props.currentSkills && props.currentSkills[0]) {
- if (props.currentSkills[0].modifier != null)
- setPrimaryAxModifier(props.currentSkills[0].modifier)
+ useEffect(() => {
+ if (props.currentSkills && props.currentSkills[0]) {
+ if (props.currentSkills[0].modifier != null)
+ setPrimaryAxModifier(props.currentSkills[0].modifier)
- setPrimaryAxValue(props.currentSkills[0].strength)
- }
-
- if (props.currentSkills && props.currentSkills[1]) {
- if (props.currentSkills[1].modifier != null)
- setSecondaryAxModifier(props.currentSkills[1].modifier)
-
- setSecondaryAxValue(props.currentSkills[1].strength)
- }
- }, [props.currentSkills])
-
- useEffect(() => {
- props.sendValues(primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue)
- }, [props, primaryAxModifier, primaryAxValue, secondaryAxModifier, secondaryAxValue])
-
- useEffect(() => {
- props.sendValidity(primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === '')
- }, [props, primaryAxValue, errors])
-
- // Classes
- const secondarySetClasses = classNames({
- 'AXSet': true,
- 'hidden': primaryAxModifier < 0
- })
-
- function generateOptions(modifierSet: number) {
- const axOptions = axData[props.axType - 1]
-
- let axOptionElements: React.ReactNode[] = []
- if (modifierSet == 0) {
- axOptionElements = axOptions.map((ax, i) => {
- return (
-
- )
- })
- } else {
- // If we are loading data from the server, state doesn't set before render,
- // so our defaultValue is undefined.
- let modifier = -1;
- if (primaryAxModifier >= 0)
- modifier = primaryAxModifier
- else if (props.currentSkills)
- modifier = props.currentSkills[0].modifier
-
- if (modifier >= 0 && axOptions[modifier]) {
- const primarySkill = axOptions[modifier]
-
- if (primarySkill.secondary) {
- const secondaryAxOptions = primarySkill.secondary
- axOptionElements = secondaryAxOptions.map((ax, i) => {
- return (
-
- )
- })
- }
- }
- }
-
- axOptionElements?.unshift()
- return axOptionElements
+ setPrimaryAxValue(props.currentSkills[0].strength)
}
- function handleSelectChange(event: React.ChangeEvent) {
- const value = parseInt(event.target.value)
+ if (props.currentSkills && props.currentSkills[1]) {
+ if (props.currentSkills[1].modifier != null)
+ setSecondaryAxModifier(props.currentSkills[1].modifier)
- if (primaryAxModifierSelect.current == event.target) {
- setPrimaryAxModifier(value)
-
- if (primaryAxValueInput.current && secondaryAxModifierSelect.current && secondaryAxValueInput.current) {
- setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
-
- secondaryAxModifierSelect.current.value = "-1"
- secondaryAxValueInput.current.value = ""
- }
- } else {
- setSecondaryAxModifier(value)
-
- const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
- const currentAxSkill = (primaryAxSkill.secondary) ?
- primaryAxSkill.secondary.find(skill => skill.id == value) : undefined
-
- if (secondaryAxValueInput.current)
- setupInput(currentAxSkill, secondaryAxValueInput.current)
- }
+ setSecondaryAxValue(props.currentSkills[1].strength)
}
+ }, [props.currentSkills])
- function handleInputChange(event: React.ChangeEvent) {
- const value = parseFloat(event.target.value)
- let newErrors = {...errors}
-
- if (primaryAxValueInput.current == event.target) {
- if (handlePrimaryErrors(value))
- setPrimaryAxValue(value)
- } else {
- if (handleSecondaryErrors(value))
- setSecondaryAxValue(value)
- }
- }
-
- function handlePrimaryErrors(value: number) {
- const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
- let newErrors = {...errors}
-
- if (value < primaryAxSkill.minValue) {
- newErrors.axValue1 = t('ax.errors.value_too_low', {
- name: primaryAxSkill.name[locale],
- minValue: primaryAxSkill.minValue,
- suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''
- })
- } else if (value > primaryAxSkill.maxValue) {
- newErrors.axValue1 = t('ax.errors.value_too_high', {
- name: primaryAxSkill.name[locale],
- maxValue: primaryAxSkill.minValue,
- suffix: (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''
- })
- } else if (!value || value <= 0) {
- newErrors.axValue1 = t('ax.errors.value_empty', { name: primaryAxSkill.name[locale] })
- } else {
- newErrors.axValue1 = ''
- }
-
- setErrors(newErrors)
-
- return newErrors.axValue1.length === 0
- }
-
- function handleSecondaryErrors(value: number) {
- const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
- let newErrors = {...errors}
-
- if (primaryAxSkill.secondary) {
- const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondaryAxModifier)
-
- if (secondaryAxSkill) {
- if (value < secondaryAxSkill.minValue) {
- newErrors.axValue2 = t('ax.errors.value_too_low', {
- name: secondaryAxSkill.name[locale],
- minValue: secondaryAxSkill.minValue,
- suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''
- })
- } else if (value > secondaryAxSkill.maxValue) {
- newErrors.axValue2 = t('ax.errors.value_too_high', {
- name: secondaryAxSkill.name[locale],
- maxValue: secondaryAxSkill.minValue,
- suffix: (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''
- })
- } else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
- newErrors.axValue2 = t('ax.errors.value_not_whole', { name: secondaryAxSkill.name[locale] })
- } else if (primaryAxValue <= 0) {
- newErrors.axValue1 = t('ax.errors.value_empty', { name: primaryAxSkill.name[locale] })
- } else {
- newErrors.axValue2 = ''
- }
- }
- }
-
- setErrors(newErrors)
-
- return newErrors.axValue2.length === 0
- }
-
- function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) {
- if (ax) {
- const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
-
- element.disabled = false
- element.placeholder = rangeString
- element.min = `${ax.minValue}`
- element.max = `${ax.maxValue}`
- element.step = (ax.suffix) ? "0.5" : "1"
- } else {
- if (primaryAxValueInput.current && secondaryAxValueInput.current) {
- if (primaryAxValueInput.current == element) {
- primaryAxValueInput.current.disabled = true
- primaryAxValueInput.current.placeholder = ''
- }
-
- secondaryAxValueInput.current.disabled = true
- secondaryAxValueInput.current.placeholder = ''
- }
- }
- }
-
- return (
-
-
-
-
-
-
-
{errors.axValue1}
-
-
-
-
-
-
-
-
{errors.axValue2}
-
-
+ useEffect(() => {
+ props.sendValues(
+ primaryAxModifier,
+ primaryAxValue,
+ secondaryAxModifier,
+ secondaryAxValue
)
+ }, [
+ props,
+ primaryAxModifier,
+ primaryAxValue,
+ secondaryAxModifier,
+ secondaryAxValue,
+ ])
+
+ useEffect(() => {
+ props.sendValidity(
+ primaryAxValue > 0 && errors.axValue1 === '' && errors.axValue2 === ''
+ )
+ }, [props, primaryAxValue, errors])
+
+ // Classes
+ const secondarySetClasses = classNames({
+ AXSet: true,
+ hidden: primaryAxModifier < 0,
+ })
+
+ function generateOptions(modifierSet: number) {
+ const axOptions = axData[props.axType - 1]
+
+ let axOptionElements: React.ReactNode[] = []
+ if (modifierSet == 0) {
+ axOptionElements = axOptions.map((ax, i) => {
+ return (
+
+ )
+ })
+ } else {
+ // If we are loading data from the server, state doesn't set before render,
+ // so our defaultValue is undefined.
+ let modifier = -1
+ if (primaryAxModifier >= 0) modifier = primaryAxModifier
+ else if (props.currentSkills) modifier = props.currentSkills[0].modifier
+
+ if (modifier >= 0 && axOptions[modifier]) {
+ const primarySkill = axOptions[modifier]
+
+ if (primarySkill.secondary) {
+ const secondaryAxOptions = primarySkill.secondary
+ axOptionElements = secondaryAxOptions.map((ax, i) => {
+ return (
+
+ )
+ })
+ }
+ }
+ }
+
+ axOptionElements?.unshift(
+
+ )
+ return axOptionElements
+ }
+
+ function handleSelectChange(event: React.ChangeEvent) {
+ const value = parseInt(event.target.value)
+
+ if (primaryAxModifierSelect.current == event.target) {
+ setPrimaryAxModifier(value)
+
+ if (
+ primaryAxValueInput.current &&
+ secondaryAxModifierSelect.current &&
+ secondaryAxValueInput.current
+ ) {
+ setupInput(axData[props.axType - 1][value], primaryAxValueInput.current)
+
+ secondaryAxModifierSelect.current.value = '-1'
+ secondaryAxValueInput.current.value = ''
+ }
+ } else {
+ setSecondaryAxModifier(value)
+
+ const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
+ const currentAxSkill = primaryAxSkill.secondary
+ ? primaryAxSkill.secondary.find((skill) => skill.id == value)
+ : undefined
+
+ if (secondaryAxValueInput.current)
+ setupInput(currentAxSkill, secondaryAxValueInput.current)
+ }
+ }
+
+ function handleInputChange(event: React.ChangeEvent) {
+ const value = parseFloat(event.target.value)
+ let newErrors = { ...errors }
+
+ if (primaryAxValueInput.current == event.target) {
+ if (handlePrimaryErrors(value)) setPrimaryAxValue(value)
+ } else {
+ if (handleSecondaryErrors(value)) setSecondaryAxValue(value)
+ }
+ }
+
+ function handlePrimaryErrors(value: number) {
+ const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
+ let newErrors = { ...errors }
+
+ if (value < primaryAxSkill.minValue) {
+ newErrors.axValue1 = t('ax.errors.value_too_low', {
+ name: primaryAxSkill.name[locale],
+ minValue: primaryAxSkill.minValue,
+ suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : '',
+ })
+ } else if (value > primaryAxSkill.maxValue) {
+ newErrors.axValue1 = t('ax.errors.value_too_high', {
+ name: primaryAxSkill.name[locale],
+ maxValue: primaryAxSkill.minValue,
+ suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : '',
+ })
+ } else if (!value || value <= 0) {
+ newErrors.axValue1 = t('ax.errors.value_empty', {
+ name: primaryAxSkill.name[locale],
+ })
+ } else {
+ newErrors.axValue1 = ''
+ }
+
+ setErrors(newErrors)
+
+ return newErrors.axValue1.length === 0
+ }
+
+ function handleSecondaryErrors(value: number) {
+ const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
+ let newErrors = { ...errors }
+
+ if (primaryAxSkill.secondary) {
+ const secondaryAxSkill = primaryAxSkill.secondary.find(
+ (skill) => skill.id == secondaryAxModifier
+ )
+
+ if (secondaryAxSkill) {
+ if (value < secondaryAxSkill.minValue) {
+ newErrors.axValue2 = t('ax.errors.value_too_low', {
+ name: secondaryAxSkill.name[locale],
+ minValue: secondaryAxSkill.minValue,
+ suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : '',
+ })
+ } else if (value > secondaryAxSkill.maxValue) {
+ newErrors.axValue2 = t('ax.errors.value_too_high', {
+ name: secondaryAxSkill.name[locale],
+ maxValue: secondaryAxSkill.minValue,
+ suffix: secondaryAxSkill.suffix ? secondaryAxSkill.suffix : '',
+ })
+ } else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
+ newErrors.axValue2 = t('ax.errors.value_not_whole', {
+ name: secondaryAxSkill.name[locale],
+ })
+ } else if (primaryAxValue <= 0) {
+ newErrors.axValue1 = t('ax.errors.value_empty', {
+ name: primaryAxSkill.name[locale],
+ })
+ } else {
+ newErrors.axValue2 = ''
+ }
+ }
+ }
+
+ setErrors(newErrors)
+
+ return newErrors.axValue2.length === 0
+ }
+
+ function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) {
+ if (ax) {
+ const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
+
+ element.disabled = false
+ element.placeholder = rangeString
+ element.min = `${ax.minValue}`
+ element.max = `${ax.maxValue}`
+ element.step = ax.suffix ? '0.5' : '1'
+ } else {
+ if (primaryAxValueInput.current && secondaryAxValueInput.current) {
+ if (primaryAxValueInput.current == element) {
+ primaryAxValueInput.current.disabled = true
+ primaryAxValueInput.current.placeholder = ''
+ }
+
+ secondaryAxValueInput.current.disabled = true
+ secondaryAxValueInput.current.placeholder = ''
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
{errors.axValue1}
+
+
+
+
+
+
+
+
{errors.axValue2}
+
+
+ )
}
-export default AXSelect
\ No newline at end of file
+export default AXSelect
diff --git a/components/Button/index.scss b/components/Button/index.scss
index f9053487..ec6dbbf5 100644
--- a/components/Button/index.scss
+++ b/components/Button/index.scss
@@ -1,214 +1,260 @@
.Button {
- align-items: center;
+ align-items: center;
+ background: var(--button-bg);
+ border: none;
+ border-radius: $input-corner;
+ color: var(--button-text);
+ display: inline-flex;
+ font-size: $font-button;
+ font-weight: $normal;
+ gap: 6px;
+
+ &:hover,
+ &.Blended:hover {
+ background: var(--button-bg-hover);
+ cursor: pointer;
+ color: var(--button-text-hover);
+
+ .Accessory svg {
+ fill: var(--button-text-hover);
+ }
+
+ .Accessory svg.stroke {
+ fill: none;
+ stroke: var(--button-text-hover);
+ }
+ }
+
+ &.Blended {
background: transparent;
- border: none;
- border-radius: 6px;
- color: $grey-50;
- display: inline-flex;
- font-size: $font-button;
- font-weight: $normal;
- gap: 6px;
- padding: 8px 12px;
+ }
+
+ &.Contained {
+ background: var(--button-contained-bg);
&:hover {
- background: white;
- cursor: pointer;
- color: $grey-00;
-
- .icon svg {
- fill: $grey-00;
- }
-
- .icon.stroke svg {
- fill: none;
- stroke: $grey-00;
- }
+ background: var(--button-contained-bg-hover);
}
- &.destructive:hover {
- background: $error;
- color: white;
-
- .icon svg {
- fill: white;
- }
+ &.Save:hover .Accessory svg {
+ fill: #ff4d4d;
+ stroke: #ff4d4d;
}
- &.save:hover {
- color: #FF4D4D;
-
- .icon svg {
- fill: #FF4D4D;
- stroke: #FF4D4D;
+ &.Active.Save {
+ color: #ff4d4d;
+
+ .Accessory svg {
+ fill: #ff4d4d;
+ stroke: #ff4d4d;
+ }
+
+ &:hover {
+ color: darken(#ff4d4d, 30);
+
+ .Accessory svg {
+ fill: darken(#ff4d4d, 30);
+ stroke: darken(#ff4d4d, 30);
}
+ }
+ }
+ }
+
+ &.medium {
+ height: $unit * 5.5;
+ padding: ($unit * 1.5) $unit-2x;
+ }
+
+ &.small {
+ padding: $unit * 1.5;
+ }
+
+ &.destructive:hover {
+ background: $error;
+ color: $grey-100;
+
+ .Accessory svg {
+ fill: $grey-100;
+ }
+ }
+
+ &.save:hover {
+ color: #ff4d4d;
+
+ .Accessory svg {
+ fill: #ff4d4d;
+ stroke: #ff4d4d;
+ }
+ }
+
+ &.save.Active {
+ color: #ff4d4d;
+
+ &:hover {
+ color: darken(#ff4d4d, 30);
+
+ .icon svg {
+ fill: darken(#ff4d4d, 30);
+ stroke: darken(#ff4d4d, 30);
+ }
+ }
+ }
+
+ &.modal:hover {
+ background: $grey-90;
+ }
+
+ &.modal.destructive {
+ color: $error;
+
+ &:hover {
+ color: darken($error, 10);
+ }
+ }
+
+ .Accessory {
+ $dimension: $unit-2x;
+
+ display: flex;
+
+ svg {
+ fill: var(--button-text);
+ height: $dimension;
+ width: $dimension;
+
+ &.stroke {
+ fill: none;
+ stroke: var(--button-text);
+ }
+
+ &.Add {
+ height: 18px;
+ width: 18px;
+ }
+
+ &.Check {
+ height: 22px;
+ width: 22px;
+ }
}
- &.save.Active {
- color: #FF4D4D;
-
- .icon svg {
- fill: #FF4D4D;
- stroke: #FF4D4D;
- }
-
- &:hover {
- color: darken(#FF4D4D, 30);
-
- .icon svg {
- fill: darken(#FF4D4D, 30);
- stroke: darken(#FF4D4D, 30);
- }
- }
+ &.check svg {
+ margin-top: 1px;
+ height: 14px;
+ width: auto;
}
- &.modal:hover {
- background: $grey-90;
+ svg &.settings svg {
+ height: 13px;
+ width: 13px;
}
+ }
- &.modal.destructive {
- color: $error;
-
- &:hover {
- color: darken($error, 10)
- }
+ &.btn-blue {
+ background: $blue;
+ color: #8b8b8b;
+
+ &:hover {
+ background: #4b9be5;
+ color: #233e56;
+ }
+ }
+
+ &.btn-red {
+ background: #fa4242;
+ color: #860f0f;
+
+ &:hover {
+ background: #e91a1a;
+ color: #4e1717;
+
+ .icon {
+ color: #4e1717;
+ }
}
.icon {
- margin-top: 2px;
-
- svg {
- fill: $grey-50;
- height: 12px;
- width: 12px;
- }
-
- &.check svg {
- margin-top: 1px;
- height: 14px;
- width: auto;
- }
-
- &.stroke svg {
- fill: none;
- stroke: $grey-50;
- }
-
- &.settings svg {
- height: 13px;
- width: 13px;
- }
+ color: #860f0f;
}
+ }
- &.Active {
- background: white;
+ &.btn-disabled {
+ background: #e0e0e0;
+ color: #bababa;
+
+ &:hover {
+ background: #e0e0e0;
+ color: #bababa;
}
+ }
- &.btn-blue {
- background: $blue;
- color: #8b8b8b;
+ &.null {
+ background: $grey-90;
+ color: $grey-55;
- &:hover {
- background: #4B9BE5;
- color: #233E56;
- }
+ &:hover {
+ background: $grey-70;
+ color: $grey-15;
}
+ }
- &.btn-red {
- background: #fa4242;
- color: #860f0f;
+ &.wind {
+ background: $wind-bg-20;
+ color: $wind-text-10;
- &:hover {
- background: #e91a1a;
- color: #4e1717;
-
- .icon {
- color: #4e1717;
- }
- }
-
- .icon {
- color: #860f0f;
- }
+ &:hover {
+ background: darken($wind-bg-20, 10);
}
+ }
- &.btn-disabled {
- background: #e0e0e0;
- color: #bababa;
+ &.fire {
+ background: $fire-bg-20;
+ color: $fire-text-10;
- &:hover {
- background: #e0e0e0;
- color: #bababa;
- }
+ &:hover {
+ background: darken($fire-bg-20, 10);
}
+ }
- &.null {
- background: $grey-90;
- color: $grey-50;
+ &.water {
+ background: $water-bg-20;
+ color: $water-text-10;
- &:hover {
- background: $grey-70;
- color: $grey-00;
- }
+ &:hover {
+ background: darken($water-bg-20, 10);
}
+ }
- &.wind {
- background: $wind-bg-light;
- color: $wind-text-dark;
+ &.earth {
+ background: $earth-bg-20;
+ color: $earth-text-10;
- &:hover {
- background: darken($wind-bg-light, 10);
- }
+ &:hover {
+ background: darken($earth-bg-20, 10);
}
+ }
- &.fire {
- background: $fire-bg-light;
- color: $fire-text-dark;
+ &.dark {
+ background: $dark-bg-10;
+ color: $dark-text-10;
- &:hover {
- background: darken($fire-bg-light, 10);
- }
+ &:hover {
+ background: darken($dark-bg-10, 10);
}
+ }
- &.water {
- background: $water-bg-light;
- color: $water-text-dark;
+ &.light {
+ background: $light-bg-20;
+ color: $light-text-10;
- &:hover {
- background: darken($water-bg-light, 10);
- }
+ &:hover {
+ background: darken($light-bg-20, 10);
}
+ }
- &.earth {
- background: $earth-bg-light;
- color: $earth-text-dark;
-
- &:hover {
- background: darken($earth-bg-light, 10);
- }
- }
-
- &.dark {
- background: $dark-bg-light;
- color: $dark-text-dark;
-
- &:hover {
- background: darken($dark-bg-light, 10);
- }
- }
-
-
- &.light {
- background: $light-bg-light;
- color: $light-text-dark;
-
- &:hover {
- background: darken($light-bg-light, 10);
- }
- }
-
- .text {
- color: inherit;
- display: block;
- width: 100%;
- }
+ .Text {
+ color: inherit;
+ display: block;
+ width: 100%;
+ }
}
diff --git a/components/Button/index.tsx b/components/Button/index.tsx
index f83f5e76..2e0fcea8 100644
--- a/components/Button/index.tsx
+++ b/components/Button/index.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react'
+import React, { PropsWithChildren, useEffect, useState } from 'react'
import classNames from 'classnames'
import Link from 'next/link'
@@ -15,127 +15,174 @@ import SettingsIcon from '~public/icons/Settings.svg'
import './index.scss'
import { ButtonType } from '~utils/enums'
+import { access } from 'fs'
-interface Props {
- active?: boolean
- disabled?: boolean
- classes?: string[],
- icon?: string
- type?: ButtonType
- children?: React.ReactNode
- onClick?: (event: React.MouseEvent) => void
+interface Props
+ extends React.DetailedHTMLProps<
+ React.ButtonHTMLAttributes,
+ HTMLButtonElement
+ > {
+ accessoryIcon?: React.ReactNode
+ active?: boolean
+ blended?: boolean
+ contained?: boolean
+ size?: 'small' | 'medium' | 'large'
+ text?: string
}
-const Button = (props: Props) => {
- // States
- const [active, setActive] = useState(false)
- const [disabled, setDisabled] = useState(false)
- const [pressed, setPressed] = useState(false)
- const [buttonType, setButtonType] = useState(ButtonType.Base)
-
- const classes = classNames({
- Button: true,
- 'Active': active,
- 'btn-pressed': pressed,
- 'btn-disabled': disabled,
- 'save': props.icon === 'save',
- 'destructive': props.type == ButtonType.Destructive
- }, props.classes)
-
- useEffect(() => {
- if (props.active) setActive(props.active)
- if (props.disabled) setDisabled(props.disabled)
- if (props.type) setButtonType(props.type)
- }, [props.active, props.disabled, props.type])
-
- const addIcon = (
-
-
-
- )
-
- const menuIcon = (
-
-
-
- )
-
- const linkIcon = (
-
-
-
- )
-
- const checkIcon = (
-
-
-
- )
-
- const crossIcon = (
-
-
-
- )
-
- const editIcon = (
-
-
-
- )
-
- const saveIcon = (
-
-
-
- )
-
- const settingsIcon = (
-
-
-
- )
-
- function getIcon() {
- let icon: React.ReactNode
-
- switch(props.icon) {
- case 'new': icon = addIcon; break
- case 'menu': icon = menuIcon; break
- case 'link': icon = linkIcon; break
- case 'check': icon = checkIcon; break
- case 'cross': icon = crossIcon; break
- case 'edit': icon = editIcon; break
- case 'save': icon = saveIcon; break
- case 'settings': icon = settingsIcon; break
- }
-
- return icon
- }
-
- function handleMouseDown() {
- setPressed(true)
- }
-
- function handleMouseUp() {
- setPressed(false)
- }
- return (
-
- )
+const defaultProps = {
+ active: false,
+ blended: false,
+ contained: false,
+ size: 'medium',
}
-export default Button
\ No newline at end of file
+const Button = React.forwardRef(function button(
+ { accessoryIcon, active, blended, contained, size, text, ...props },
+ forwardedRef
+) {
+ const classes = classNames(
+ {
+ Button: true,
+ Active: active,
+ Blended: blended,
+ Contained: contained,
+ // 'btn-pressed': pressed,
+ // 'btn-disabled': disabled,
+ // save: props.icon === 'save',
+ // destructive: props.type == ButtonType.Destructive,
+ },
+ size,
+ props.className
+ )
+
+ const hasAccessory = () => {
+ if (accessoryIcon) return {accessoryIcon}
+ }
+
+ const hasText = () => {
+ if (text) return {text}
+ }
+
+ return (
+
+ )
+
+ // useEffect(() => {
+ // if (props.type) setButtonType(props.type)
+ // }, [props.type])
+
+ // const addIcon = (
+ //
+ //
+ //
+ // )
+
+ // const menuIcon = (
+ //
+ //
+ //
+ // )
+
+ // const linkIcon = (
+ //
+ //
+ //
+ // )
+
+ // const checkIcon = (
+ //
+ //
+ //
+ // )
+
+ // const crossIcon = (
+ //
+ //
+ //
+ // )
+
+ // const editIcon = (
+ //
+ //
+ //
+ // )
+
+ // const saveIcon = (
+ //
+ //
+ //
+ // )
+
+ // const settingsIcon = (
+ //
+ //
+ //
+ // )
+
+ // function getIcon() {
+ // let icon: React.ReactNode
+
+ // switch (props.icon) {
+ // case 'new':
+ // icon = addIcon
+ // break
+ // case 'menu':
+ // icon = menuIcon
+ // break
+ // case 'link':
+ // icon = linkIcon
+ // break
+ // case 'check':
+ // icon = checkIcon
+ // break
+ // case 'cross':
+ // icon = crossIcon
+ // break
+ // case 'edit':
+ // icon = editIcon
+ // break
+ // case 'save':
+ // icon = saveIcon
+ // break
+ // case 'settings':
+ // icon = settingsIcon
+ // break
+ // }
+
+ // return icon
+ // }
+
+ // function handleMouseDown() {
+ // setPressed(true)
+ // }
+
+ // function handleMouseUp() {
+ // setPressed(false)
+ // }
+ // return (
+ //
+ // )
+})
+
+Button.defaultProps = defaultProps
+
+export default Button
diff --git a/components/CharLimitedFieldset/index.scss b/components/CharLimitedFieldset/index.scss
index 8f81bf3b..dc9eb4a5 100644
--- a/components/CharLimitedFieldset/index.scss
+++ b/components/CharLimitedFieldset/index.scss
@@ -1,29 +1,34 @@
.Limited {
- background: white;
- border-radius: 6px;
- border: 2px solid transparent;
- box-sizing: border-box;
- display: flex;
- gap: $unit;
- padding-right: $unit * 2;
+ $offset: 2px;
- &:focus-within {
- border: 2px solid $blue;
- box-shadow: 0 2px rgba(255, 255, 255, 1);
+ background: var(--input-bg);
+ border-radius: $input-corner;
+ border: $offset solid transparent;
+ box-sizing: border-box;
+ display: flex;
+ gap: $unit;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-right: calc($unit-2x - $offset);
+
+ &:focus-within {
+ border: $offset solid $blue;
+ // box-shadow: 0 2px rgba(255, 255, 255, 1);
+ }
+
+ .Counter {
+ color: $grey-55;
+ font-weight: $bold;
+ line-height: 42px;
+ }
+
+ .Input {
+ background: transparent;
+ border-radius: 0;
+ padding-left: calc($unit-2x - $offset);
+
+ &:focus {
+ outline: none;
}
-
- .Counter {
- color: $grey-50;
- font-weight: $bold;
- line-height: 42px;
- }
-
- .Input {
- background: transparent;
- border-radius: 0;
-
- &:focus {
- outline: none;
- }
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/components/CharLimitedFieldset/index.tsx b/components/CharLimitedFieldset/index.tsx
index ff741c59..cb822299 100644
--- a/components/CharLimitedFieldset/index.tsx
+++ b/components/CharLimitedFieldset/index.tsx
@@ -2,53 +2,56 @@ import React, { useEffect, useState } from 'react'
import './index.scss'
interface Props {
- fieldName: string
- placeholder: string
- value?: string
- limit: number
- error: string
- onBlur?: (event: React.ChangeEvent) => void
- onChange?: (event: React.ChangeEvent) => void
+ fieldName: string
+ placeholder: string
+ value?: string
+ limit: number
+ error: string
+ onBlur?: (event: React.ChangeEvent) => void
+ onChange?: (event: React.ChangeEvent) => void
}
-const CharLimitedFieldset = React.forwardRef(function useFieldSet(props, ref) {
- const fieldType = (['password', 'confirm_password'].includes(props.fieldName)) ? 'password' : 'text'
+const CharLimitedFieldset = React.forwardRef(
+ function useFieldSet(props, ref) {
+ const fieldType = ['password', 'confirm_password'].includes(props.fieldName)
+ ? 'password'
+ : 'text'
const [currentCount, setCurrentCount] = useState(0)
useEffect(() => {
- setCurrentCount((props.value) ? props.limit - props.value.length : props.limit)
+ setCurrentCount(
+ props.value ? props.limit - props.value.length : props.limit
+ )
}, [props.limit, props.value])
function onChange(event: React.ChangeEvent) {
- setCurrentCount(props.limit - event.currentTarget.value.length)
- if (props.onChange) props.onChange(event)
+ setCurrentCount(props.limit - event.currentTarget.value.length)
+ if (props.onChange) props.onChange(event)
}
return (
-
+
)
-})
+ }
+)
-export default CharLimitedFieldset
\ No newline at end of file
+export default CharLimitedFieldset
diff --git a/components/CharacterConflictModal/index.scss b/components/CharacterConflictModal/index.scss
index 48176c81..866ec8f7 100644
--- a/components/CharacterConflictModal/index.scss
+++ b/components/CharacterConflictModal/index.scss
@@ -8,7 +8,7 @@
}
.arrow {
- color: $grey-50;
+ color: $grey-55;
font-size: 4rem;
text-align: center;
}
@@ -52,7 +52,7 @@
&:not(.btn-disabled) {
background: $grey-90;
- color: $grey-40;
+ color: $grey-50;
&:hover {
background: $grey-80;
diff --git a/components/CharacterConflictModal/index.tsx b/components/CharacterConflictModal/index.tsx
index 1f1ff6c0..3d54fb32 100644
--- a/components/CharacterConflictModal/index.tsx
+++ b/components/CharacterConflictModal/index.tsx
@@ -1,18 +1,18 @@
-import React, { useEffect, useState } from "react"
-import { setCookie } from "cookies-next"
-import Router, { useRouter } from "next/router"
-import { useTranslation } from "react-i18next"
-import { AxiosResponse } from "axios"
+import React, { useEffect, useState } from 'react'
+import { setCookie } from 'cookies-next'
+import Router, { useRouter } from 'next/router'
+import { useTranslation } from 'react-i18next'
+import { AxiosResponse } from 'axios'
-import * as Dialog from "@radix-ui/react-dialog"
+import * as Dialog from '@radix-ui/react-dialog'
-import api from "~utils/api"
-import { appState } from "~utils/appState"
-import { accountState } from "~utils/accountState"
+import api from '~utils/api'
+import { appState } from '~utils/appState'
+import { accountState } from '~utils/accountState'
-import Button from "~components/Button"
+import Button from '~components/Button'
-import "./index.scss"
+import './index.scss'
interface Props {
open: boolean
@@ -24,7 +24,7 @@ interface Props {
}
const CharacterConflictModal = (props: Props) => {
- const { t } = useTranslation("common")
+ const { t } = useTranslation('common')
// States
const [open, setOpen] = useState(false)
@@ -35,13 +35,13 @@ const CharacterConflictModal = (props: Props) => {
function imageUrl(character?: Character, uncap: number = 0) {
// Change the image based on the uncap level
- let suffix = "01"
- if (uncap == 6) suffix = "04"
- else if (uncap == 5) suffix = "03"
- else if (uncap > 2) suffix = "02"
+ let suffix = '01'
+ if (uncap == 6) suffix = '04'
+ else if (uncap == 5) suffix = '03'
+ else if (uncap > 2) suffix = '02'
// Special casing for Lyria (and Young Cat eventually)
- if (character?.granblue_id === "3030182000") {
+ if (character?.granblue_id === '3030182000') {
let element = 1
if (
appState.grid.weapons.mainWeapon &&
diff --git a/components/CharacterGrid/index.scss b/components/CharacterGrid/index.scss
index 01e6ada2..4a45134f 100644
--- a/components/CharacterGrid/index.scss
+++ b/components/CharacterGrid/index.scss
@@ -1,31 +1,31 @@
#CharacterGrid {
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin: auto;
- max-width: 761px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin: auto;
+ max-width: 761px;
}
#grid_characters {
- display: flex;
- margin: 0;
- padding: 0;
- max-width: 761px;
+ display: flex;
+ margin: 0;
+ padding: 0;
+ max-width: 761px;
+
+ @media (max-width: $medium-screen) {
+ justify-content: space-between;
+ width: 100%;
+ }
+
+ & > * {
+ margin-right: $unit * 3;
@media (max-width: $medium-screen) {
- justify-content: space-between;
- width: 100%;
+ margin-right: inherit;
}
+ }
- & > * {
- margin-right: $unit * 3;
-
- @media (max-width: $medium-screen) {
- margin-right: inherit;
- }
- }
-
- & > li:last-child {
- margin: 0;
- }
-}
\ No newline at end of file
+ & > li:last-child {
+ margin: 0;
+ }
+}
diff --git a/components/CharacterGrid/index.tsx b/components/CharacterGrid/index.tsx
index 521c54c2..9660cfe6 100644
--- a/components/CharacterGrid/index.tsx
+++ b/components/CharacterGrid/index.tsx
@@ -1,22 +1,22 @@
/* eslint-disable react-hooks/exhaustive-deps */
-import React, { useCallback, useEffect, useMemo, useState } from "react"
-import { getCookie } from "cookies-next"
-import { useSnapshot } from "valtio"
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import { getCookie } from 'cookies-next'
+import { useSnapshot } from 'valtio'
-import { AxiosResponse } from "axios"
-import debounce from "lodash.debounce"
+import { AxiosResponse } from 'axios'
+import debounce from 'lodash.debounce'
-import Alert from "~components/Alert"
-import JobSection from "~components/JobSection"
-import CharacterUnit from "~components/CharacterUnit"
-import CharacterConflictModal from "~components/CharacterConflictModal"
+import Alert from '~components/Alert'
+import JobSection from '~components/JobSection'
+import CharacterUnit from '~components/CharacterUnit'
+import CharacterConflictModal from '~components/CharacterConflictModal'
-import type { JobSkillObject, SearchableObject } from "~types"
+import type { JobSkillObject, SearchableObject } from '~types'
-import api from "~utils/api"
-import { appState } from "~utils/appState"
+import api from '~utils/api'
+import { appState } from '~utils/appState'
-import "./index.scss"
+import './index.scss'
// Props
interface Props {
@@ -31,7 +31,7 @@ const CharacterGrid = (props: Props) => {
const numCharacters: number = 5
// Cookies
- const cookie = getCookie("account")
+ const cookie = getCookie('account')
const accountData: AccountCookie = cookie
? JSON.parse(cookie as string)
: null
@@ -57,7 +57,7 @@ const CharacterGrid = (props: Props) => {
2: undefined,
3: undefined,
})
- const [errorMessage, setErrorMessage] = useState("")
+ const [errorMessage, setErrorMessage] = useState('')
// Create a temporary state to store previous character uncap values
const [previousUncapValues, setPreviousUncapValues] = useState<{
@@ -116,7 +116,7 @@ const CharacterGrid = (props: Props) => {
}
async function handleCharacterResponse(data: any) {
- if (data.hasOwnProperty("conflicts")) {
+ if (data.hasOwnProperty('conflicts')) {
setIncoming(data.incoming)
setConflicts(data.conflicts)
setPosition(data.position)
@@ -185,7 +185,7 @@ const CharacterGrid = (props: Props) => {
const saveJob = function (job: Job) {
const payload = {
party: {
- job_id: job ? job.id : "",
+ job_id: job ? job.id : '',
},
...headers,
}
@@ -231,9 +231,9 @@ const CharacterGrid = (props: Props) => {
})
.catch((error) => {
const data = error.response.data
- if (data.code == "too_many_skills_of_type") {
+ if (data.code == 'too_many_skills_of_type') {
const message = `You can only add up to 2 ${
- data.skill_type === "emp"
+ data.skill_type === 'emp'
? data.skill_type.toUpperCase()
: data.skill_type
} skills to your party at once.`
@@ -268,7 +268,7 @@ const CharacterGrid = (props: Props) => {
try {
if (uncapLevel != previousUncapValues[position])
- await api.updateUncap("character", id, uncapLevel).then((response) => {
+ await api.updateUncap('character', id, uncapLevel).then((response) => {
storeGridCharacter(response.data.grid_character)
})
} catch (error) {
@@ -332,7 +332,7 @@ const CharacterGrid = (props: Props) => {
}
function cancelAlert() {
- setErrorMessage("")
+ setErrorMessage('')
}
// Render: JSX components
@@ -342,7 +342,7 @@ const CharacterGrid = (props: Props) => {
open={errorMessage.length > 0}
message={errorMessage}
cancelAction={cancelAlert}
- cancelActionText={"Got it"}
+ cancelActionText={'Got it'}
/>
{
- const router = useRouter()
- const { t } = useTranslation('common')
- const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
+ const router = useRouter()
+ const { t } = useTranslation('common')
+ const locale =
+ router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
- const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
- const Proficiency = ['none', 'sword', 'dagger', 'axe', 'spear', 'bow', 'staff', 'fist', 'harp', 'gun', 'katana']
+ const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
+ const Proficiency = [
+ 'none',
+ 'sword',
+ 'dagger',
+ 'axe',
+ 'spear',
+ 'bow',
+ 'staff',
+ 'fist',
+ 'harp',
+ 'gun',
+ 'katana',
+ ]
- const tintElement = Element[props.gridCharacter.object.element]
- const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(' ', '_')}`
+ const tintElement = Element[props.gridCharacter.object.element]
+ const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(
+ ' ',
+ '_'
+ )}`
- function characterImage() {
- let imgSrc = ""
-
- if (props.gridCharacter) {
- const character = props.gridCharacter.object
+ function characterImage() {
+ let imgSrc = ''
- // Change the image based on the uncap level
- let suffix = '01'
- if (props.gridCharacter.uncap_level == 6)
- suffix = '04'
- else if (props.gridCharacter.uncap_level == 5)
- suffix = '03'
- else if (props.gridCharacter.uncap_level > 2)
- suffix = '02'
+ if (props.gridCharacter) {
+ const character = props.gridCharacter.object
- imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`
- }
+ // Change the image based on the uncap level
+ let suffix = '01'
+ if (props.gridCharacter.uncap_level == 6) suffix = '04'
+ else if (props.gridCharacter.uncap_level == 5) suffix = '03'
+ else if (props.gridCharacter.uncap_level > 2) suffix = '02'
- return imgSrc
+ imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`
}
- return (
-
-
- { props.children }
-
-
-
-
-
{ props.gridCharacter.object.name[locale] }
-
![{props.gridCharacter.object.name[locale]}]({characterImage()})
-
-
-
-
-
- { (props.gridCharacter.object.proficiency.proficiency2) ?
-
- : ''}
-
-
-
-
+ return imgSrc
+ }
- {t('buttons.wiki')}
-
-
-
- )
+ return (
+
+ {props.children}
+
+
+
+
{props.gridCharacter.object.name[locale]}
+
![{props.gridCharacter.object.name[locale]}]({characterImage()})
+
+
+
+
+
+ {props.gridCharacter.object.proficiency.proficiency2 ? (
+
+ ) : (
+ ''
+ )}
+
+
+
+
+
+
+ {t('buttons.wiki')}
+
+
+
+
+ )
}
export default CharacterHovercard
-
diff --git a/components/CharacterResult/index.scss b/components/CharacterResult/index.scss
index 7c19ab70..3850d5e2 100644
--- a/components/CharacterResult/index.scss
+++ b/components/CharacterResult/index.scss
@@ -1,63 +1,67 @@
.CharacterResult {
+ border-radius: 6px;
+ display: flex;
+ gap: $unit;
+ padding: $unit * 1.5;
+
+ &:hover {
+ background: var(--button-contained-bg);
+ cursor: pointer;
+
+ .Info h5 {
+ color: var(--text-primary);
+ }
+ }
+
+ img {
+ background: var(--card-bg);
border-radius: 6px;
+ display: inline-block;
+ height: 72px;
+ width: 120px;
+ }
+
+ .Info {
display: flex;
- gap: $unit;
- padding: $unit * 1.5;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: $unit-half;
- &:hover {
- background: $grey-90;
- cursor: pointer;
+ h5 {
+ color: var(--text-secondary);
+ display: inline-block;
+ font-size: $font-medium;
+ font-weight: $medium;
}
- img {
- background: $grey-80;
- border-radius: 6px;
- display: inline-block;
- height: 72px;
- width: 120px;
+ .UncapIndicator {
+ justify-content: left;
+ pointer-events: none;
}
- .Info {
- display: flex;
- flex-direction: column;
- flex-grow: 1;
- gap: calc($unit / 2);
+ .stars {
+ display: inline-block;
+ color: #ffa15e;
+ font-size: $font-xlarge;
- h5 {
- color: #555;
- display: inline-block;
- font-size: $font-medium;
- font-weight: $medium;
- }
-
- .UncapIndicator {
- justify-content: left;
- pointer-events: none;
- }
-
- .stars {
- display: inline-block;
- color: #FFA15E;
- font-size: $font-xlarge;
-
- & > span {
- color: #65DAFF;
- }
- }
-
- .tags {
- display: flex;
- flex-direction: row;
- gap: calc($unit / 2);
-
- .WeaponLabelIcon {
- $aspect-ratio: calc(25 / 60);
- $height: 22px;
- background-size: calc($height / $aspect-ratio) $height;
- background-repeat: no-repeat;
- height: $height;
- width: calc($height/ $aspect-ratio);
- }
- }
+ & > span {
+ color: #65daff;
+ }
}
-}
\ No newline at end of file
+
+ .tags {
+ display: flex;
+ flex-direction: row;
+ gap: calc($unit / 2);
+
+ .WeaponLabelIcon {
+ $aspect-ratio: calc(25 / 60);
+ $height: 22px;
+ background-size: calc($height / $aspect-ratio) $height;
+ background-repeat: no-repeat;
+ height: $height;
+ width: calc($height/ $aspect-ratio);
+ }
+ }
+ }
+}
diff --git a/components/CharacterResult/index.tsx b/components/CharacterResult/index.tsx
index cb6ad816..7b09409b 100644
--- a/components/CharacterResult/index.tsx
+++ b/components/CharacterResult/index.tsx
@@ -7,45 +7,46 @@ import WeaponLabelIcon from '~components/WeaponLabelIcon'
import './index.scss'
interface Props {
- data: Character
- onClick: () => void
+ data: Character
+ onClick: () => void
}
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const CharacterResult = (props: Props) => {
- const router = useRouter()
- const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
+ const router = useRouter()
+ const locale =
+ router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
- const character = props.data
+ const character = props.data
- const characterUrl = () => {
- let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01.jpg`
+ const characterUrl = () => {
+ let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01.jpg`
- if (character.granblue_id === '3030182000') {
- url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`
- }
-
- return url
+ if (character.granblue_id === '3030182000') {
+ url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg`
}
- return (
-
-
-
-
{character.name[locale]}
-
-
-
-
-
-
- )
+ return url
+ }
+
+ return (
+
+
+
+
{character.name[locale]}
+
+
+
+
+
+
+ )
}
-export default CharacterResult
\ No newline at end of file
+export default CharacterResult
diff --git a/components/CharacterSearchFilterBar/index.tsx b/components/CharacterSearchFilterBar/index.tsx
index df451ec5..3ab0e1a4 100644
--- a/components/CharacterSearchFilterBar/index.tsx
+++ b/components/CharacterSearchFilterBar/index.tsx
@@ -9,197 +9,260 @@ import SearchFilter from '~components/SearchFilter'
import SearchFilterCheckboxItem from '~components/SearchFilterCheckboxItem'
import './index.scss'
-import { emptyElementState, emptyProficiencyState, emptyRarityState } from '~utils/emptyStates'
+import {
+ emptyElementState,
+ emptyProficiencyState,
+ emptyRarityState,
+} from '~utils/emptyStates'
import { elements, proficiencies, rarities } from '~utils/stateValues'
interface Props {
- sendFilters: (filters: { [key: string]: number[] }) => void
+ sendFilters: (filters: { [key: string]: number[] }) => void
}
const CharacterSearchFilterBar = (props: Props) => {
- const { t } = useTranslation('common')
+ const { t } = useTranslation('common')
- const [rarityMenu, setRarityMenu] = useState(false)
- const [elementMenu, setElementMenu] = useState(false)
- const [proficiency1Menu, setProficiency1Menu] = useState(false)
- const [proficiency2Menu, setProficiency2Menu] = useState(false)
+ const [rarityMenu, setRarityMenu] = useState(false)
+ const [elementMenu, setElementMenu] = useState(false)
+ const [proficiency1Menu, setProficiency1Menu] = useState(false)
+ const [proficiency2Menu, setProficiency2Menu] = useState(false)
- const [rarityState, setRarityState] = useState(emptyRarityState)
- const [elementState, setElementState] = useState(emptyElementState)
- const [proficiency1State, setProficiency1State] = useState(emptyProficiencyState)
- const [proficiency2State, setProficiency2State] = useState(emptyProficiencyState)
+ const [rarityState, setRarityState] = useState(emptyRarityState)
+ const [elementState, setElementState] =
+ useState(emptyElementState)
+ const [proficiency1State, setProficiency1State] = useState(
+ emptyProficiencyState
+ )
+ const [proficiency2State, setProficiency2State] = useState(
+ emptyProficiencyState
+ )
- function rarityMenuOpened(open: boolean) {
- if (open) {
- setRarityMenu(true)
- setElementMenu(false)
- setProficiency1Menu(false)
- setProficiency2Menu(false)
- } else setRarityMenu(false)
+ function rarityMenuOpened(open: boolean) {
+ if (open) {
+ setRarityMenu(true)
+ setElementMenu(false)
+ setProficiency1Menu(false)
+ setProficiency2Menu(false)
+ } else setRarityMenu(false)
+ }
+
+ function elementMenuOpened(open: boolean) {
+ if (open) {
+ setRarityMenu(false)
+ setElementMenu(true)
+ setProficiency1Menu(false)
+ setProficiency2Menu(false)
+ } else setElementMenu(false)
+ }
+
+ function proficiency1MenuOpened(open: boolean) {
+ if (open) {
+ setRarityMenu(false)
+ setElementMenu(false)
+ setProficiency1Menu(true)
+ setProficiency2Menu(false)
+ } else setProficiency1Menu(false)
+ }
+
+ function proficiency2MenuOpened(open: boolean) {
+ if (open) {
+ setRarityMenu(false)
+ setElementMenu(false)
+ setProficiency1Menu(false)
+ setProficiency2Menu(true)
+ } else setProficiency2Menu(false)
+ }
+
+ function handleRarityChange(checked: boolean, key: string) {
+ let newRarityState = cloneDeep(rarityState)
+ newRarityState[key].checked = checked
+ setRarityState(newRarityState)
+ }
+
+ function handleElementChange(checked: boolean, key: string) {
+ let newElementState = cloneDeep(elementState)
+ newElementState[key].checked = checked
+ setElementState(newElementState)
+ }
+
+ function handleProficiency1Change(checked: boolean, key: string) {
+ let newProficiencyState = cloneDeep(proficiency1State)
+ newProficiencyState[key].checked = checked
+ setProficiency1State(newProficiencyState)
+ }
+
+ function handleProficiency2Change(checked: boolean, key: string) {
+ let newProficiencyState = cloneDeep(proficiency2State)
+ newProficiencyState[key].checked = checked
+ setProficiency2State(newProficiencyState)
+ }
+
+ function sendFilters() {
+ const checkedRarityFilters = Object.values(rarityState)
+ .filter((x) => x.checked)
+ .map((x, i) => x.id)
+ const checkedElementFilters = Object.values(elementState)
+ .filter((x) => x.checked)
+ .map((x, i) => x.id)
+ const checkedProficiency1Filters = Object.values(proficiency1State)
+ .filter((x) => x.checked)
+ .map((x, i) => x.id)
+ const checkedProficiency2Filters = Object.values(proficiency2State)
+ .filter((x) => x.checked)
+ .map((x, i) => x.id)
+
+ const filters = {
+ rarity: checkedRarityFilters,
+ element: checkedElementFilters,
+ proficiency1: checkedProficiency1Filters,
+ proficiency2: checkedProficiency2Filters,
}
- function elementMenuOpened(open: boolean) {
- if (open) {
- setRarityMenu(false)
- setElementMenu(true)
- setProficiency1Menu(false)
- setProficiency2Menu(false)
- } else setElementMenu(false)
- }
+ props.sendFilters(filters)
+ }
- function proficiency1MenuOpened(open: boolean) {
- if (open) {
- setRarityMenu(false)
- setElementMenu(false)
- setProficiency1Menu(true)
- setProficiency2Menu(false)
- } else setProficiency1Menu(false)
- }
+ useEffect(() => {
+ sendFilters()
+ }, [rarityState, elementState, proficiency1State, proficiency2State])
- function proficiency2MenuOpened(open: boolean) {
- if (open) {
- setRarityMenu(false)
- setElementMenu(false)
- setProficiency1Menu(false)
- setProficiency2Menu(true)
- } else setProficiency2Menu(false)
- }
-
- function handleRarityChange(checked: boolean, key: string) {
- let newRarityState = cloneDeep(rarityState)
- newRarityState[key].checked = checked
- setRarityState(newRarityState)
- }
-
- function handleElementChange(checked: boolean, key: string) {
- let newElementState = cloneDeep(elementState)
- newElementState[key].checked = checked
- setElementState(newElementState)
- }
-
- function handleProficiency1Change(checked: boolean, key: string) {
- let newProficiencyState = cloneDeep(proficiency1State)
- newProficiencyState[key].checked = checked
- setProficiency1State(newProficiencyState)
- }
-
- function handleProficiency2Change(checked: boolean, key: string) {
- let newProficiencyState = cloneDeep(proficiency2State)
- newProficiencyState[key].checked = checked
- setProficiency2State(newProficiencyState)
- }
-
- function sendFilters() {
- const checkedRarityFilters = Object.values(rarityState).filter(x => x.checked).map((x, i) => x.id)
- const checkedElementFilters = Object.values(elementState).filter(x => x.checked).map((x, i) => x.id)
- const checkedProficiency1Filters = Object.values(proficiency1State).filter(x => x.checked).map((x, i) => x.id)
- const checkedProficiency2Filters = Object.values(proficiency2State).filter(x => x.checked).map((x, i) => x.id)
-
- const filters = {
- rarity: checkedRarityFilters,
- element: checkedElementFilters,
- proficiency1: checkedProficiency1Filters,
- proficiency2: checkedProficiency2Filters
- }
-
- props.sendFilters(filters)
- }
-
- useEffect(() => {
- sendFilters()
- }, [rarityState, elementState, proficiency1State, proficiency2State])
-
- function renderProficiencyFilter(proficiency: 1 | 2) {
- const onCheckedChange = (proficiency == 1) ? handleProficiency1Change : handleProficiency2Change
- const numSelected = (proficiency == 1)
- ? Object.values(proficiency1State).map(x => x.checked).filter(Boolean).length
- : Object.values(proficiency2State).map(x => x.checked).filter(Boolean).length
- const open = (proficiency == 1) ? proficiency1Menu : proficiency2Menu
- const onOpenChange = (proficiency == 1) ? proficiency1MenuOpened : proficiency2MenuOpened
-
- return (
-
- {`${t('filters.labels.proficiency')} ${proficiency}`}
-
-
- { Array.from(Array(proficiencies.length / 2)).map((x, i) => {
- const checked = (proficiency == 1)
- ? proficiency1State[proficiencies[i]].checked
- : proficiency2State[proficiencies[i]].checked
-
- return (
-
- {t(`proficiencies.${proficiencies[i]}`)}
-
- )}
- ) }
-
-
- { Array.from(Array(proficiencies.length / 2)).map((x, i) => {
- const checked = (proficiency == 1)
- ? proficiency1State[proficiencies[i + (proficiencies.length / 2)]].checked
- : proficiency2State[proficiencies[i + (proficiencies.length / 2)]].checked
-
- return (
-
- {t(`proficiencies.${proficiencies[i + (proficiencies.length / 2)]}`)}
-
- )}
- ) }
-
-
-
- )
- }
+ function renderProficiencyFilter(proficiency: 1 | 2) {
+ const onCheckedChange =
+ proficiency == 1 ? handleProficiency1Change : handleProficiency2Change
+ const numSelected =
+ proficiency == 1
+ ? Object.values(proficiency1State)
+ .map((x) => x.checked)
+ .filter(Boolean).length
+ : Object.values(proficiency2State)
+ .map((x) => x.checked)
+ .filter(Boolean).length
+ const open = proficiency == 1 ? proficiency1Menu : proficiency2Menu
+ const onOpenChange =
+ proficiency == 1 ? proficiency1MenuOpened : proficiency2MenuOpened
return (
-
- x.checked).filter(Boolean).length} open={rarityMenu} onOpenChange={rarityMenuOpened}>
- {t('filters.labels.rarity')}
- { Array.from(Array(rarities.length)).map((x, i) => {
- return (
-
- {t(`rarities.${rarities[i]}`)}
-
- )}
- ) }
-
+
+ {`${t(
+ 'filters.labels.proficiency'
+ )} ${proficiency}`}
+
+
+ {Array.from(Array(proficiencies.length / 2)).map((x, i) => {
+ const checked =
+ proficiency == 1
+ ? proficiency1State[proficiencies[i]].checked
+ : proficiency2State[proficiencies[i]].checked
- x.checked).filter(Boolean).length} open={elementMenu} onOpenChange={elementMenuOpened}>
- {t('filters.labels.element')}
- { Array.from(Array(elements.length)).map((x, i) => {
- return (
-
- {t(`elements.${elements[i]}`)}
-
- )}
- ) }
-
+ return (
+
+ {t(`proficiencies.${proficiencies[i]}`)}
+
+ )
+ })}
+
+
+ {Array.from(Array(proficiencies.length / 2)).map((x, i) => {
+ const checked =
+ proficiency == 1
+ ? proficiency1State[
+ proficiencies[i + proficiencies.length / 2]
+ ].checked
+ : proficiency2State[
+ proficiencies[i + proficiencies.length / 2]
+ ].checked
- { renderProficiencyFilter(1) }
- { renderProficiencyFilter(2) }
-
+ return (
+
+ {t(
+ `proficiencies.${
+ proficiencies[i + proficiencies.length / 2]
+ }`
+ )}
+
+ )
+ })}
+
+
+
)
+ }
+
+ return (
+
+ x.checked)
+ .filter(Boolean).length
+ }
+ open={rarityMenu}
+ onOpenChange={rarityMenuOpened}
+ >
+
+ {t('filters.labels.rarity')}
+
+ {Array.from(Array(rarities.length)).map((x, i) => {
+ return (
+
+ {t(`rarities.${rarities[i]}`)}
+
+ )
+ })}
+
+
+ x.checked)
+ .filter(Boolean).length
+ }
+ open={elementMenu}
+ onOpenChange={elementMenuOpened}
+ >
+
+ {t('filters.labels.element')}
+
+ {Array.from(Array(elements.length)).map((x, i) => {
+ return (
+
+ {t(`elements.${elements[i]}`)}
+
+ )
+ })}
+
+
+ {renderProficiencyFilter(1)}
+ {renderProficiencyFilter(2)}
+
+ )
}
export default CharacterSearchFilterBar
diff --git a/components/CharacterUnit/index.scss b/components/CharacterUnit/index.scss
index b51bd057..e2f2736d 100644
--- a/components/CharacterUnit/index.scss
+++ b/components/CharacterUnit/index.scss
@@ -1,79 +1,78 @@
.CharacterUnit {
+ display: flex;
+ flex-direction: column;
+ gap: calc($unit / 2);
+ min-height: 320px;
+ max-width: 200px;
+ margin-bottom: $unit * 4;
+
+ &.editable .CharacterImage:hover {
+ border: $hover-stroke;
+ box-shadow: $hover-shadow;
+ cursor: pointer;
+ transform: $scale-tall;
+ }
+
+ &.filled h3 {
+ display: block;
+ }
+
+ &.filled ul {
display: flex;
- flex-direction: column;
- gap: calc($unit / 2);
- min-height: 320px;
- max-width: 200px;
- margin-bottom: $unit * 4;
+ }
- &.editable .CharacterImage:hover {
- border: $hover-stroke;
- box-shadow: $hover-shadow;
- cursor: pointer;
- transform: $scale-tall;
+ h3,
+ ul {
+ display: none;
+ }
+
+ h3 {
+ color: var(--text-primary);
+ font-size: $font-regular;
+ font-weight: $normal;
+ line-height: 1.1;
+ margin: 0;
+ max-width: 131px;
+ text-align: center;
+ word-wrap: normal;
+ }
+
+ img {
+ position: relative;
+ width: 100%;
+ z-index: 2;
+ }
+
+ .CharacterImage {
+ aspect-ratio: 131 / 273;
+ background: var(--card-bg);
+ border: 1px solid rgba(0, 0, 0, 0);
+ border-radius: $unit;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ transition: all 0.18s ease-in-out;
+ height: auto;
+ width: 131px;
+
+ @media (max-width: $medium-screen) {
+ width: 17vw;
}
- &.filled h3 {
- display: block;
+ &:hover .icon svg {
+ fill: var(--icon-secondary-hover);
}
- &.filled ul {
- display: flex;
- }
-
- h3,
- ul {
- display: none;
- }
-
- h3 {
- color: #333;
- font-size: $font-regular;
- font-weight: $normal;
- line-height: 1.1;
- margin: 0;
- max-width: 131px;
- text-align: center;
- word-wrap: normal;
- }
-
- img {
- position: relative;
- width: 100%;
- z-index: 2;
- }
-
-
- .CharacterImage {
- aspect-ratio: 131 / 273;
- background: white;
- border: 1px solid rgba(0, 0, 0, 0);
- border-radius: $unit;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- transition: all 0.18s ease-in-out;
- height: auto;
- width: 131px;
-
- @media (max-width: $medium-screen) {
- width: 17vw;
- }
-
- &:hover .icon svg {
- color: $grey-40;
- }
-
- .icon {
- position: absolute;
- height: $unit * 3;
- width: $unit * 3;
- z-index: 1;
-
- svg {
- fill: $grey-70;
- }
- }
+ .icon {
+ position: absolute;
+ height: $unit * 3;
+ width: $unit * 3;
+ z-index: 1;
+
+ svg {
+ fill: var(--icon-secondary);
+ }
}
+ }
}
diff --git a/components/CharacterUnit/index.tsx b/components/CharacterUnit/index.tsx
index 13e46635..7dbce073 100644
--- a/components/CharacterUnit/index.tsx
+++ b/components/CharacterUnit/index.tsx
@@ -1,19 +1,19 @@
-import React, { useEffect, useState } from "react"
-import { useRouter } from "next/router"
-import { useSnapshot } from "valtio"
-import { useTranslation } from "next-i18next"
-import classnames from "classnames"
+import React, { useEffect, useState } from 'react'
+import { useRouter } from 'next/router'
+import { useSnapshot } from 'valtio'
+import { useTranslation } from 'next-i18next'
+import classnames from 'classnames'
-import { appState } from "~utils/appState"
+import { appState } from '~utils/appState'
-import CharacterHovercard from "~components/CharacterHovercard"
-import SearchModal from "~components/SearchModal"
-import UncapIndicator from "~components/UncapIndicator"
-import PlusIcon from "~public/icons/Add.svg"
+import CharacterHovercard from '~components/CharacterHovercard'
+import SearchModal from '~components/SearchModal'
+import UncapIndicator from '~components/UncapIndicator'
+import PlusIcon from '~public/icons/Add.svg'
-import type { SearchableObject } from "~types"
+import type { SearchableObject } from '~types'
-import "./index.scss"
+import './index.scss'
interface Props {
gridCharacter?: GridCharacter
@@ -24,15 +24,15 @@ interface Props {
}
const CharacterUnit = (props: Props) => {
- const { t } = useTranslation("common")
+ const { t } = useTranslation('common')
const { party, grid } = useSnapshot(appState)
const router = useRouter()
const locale =
- router.locale && ["en", "ja"].includes(router.locale) ? router.locale : "en"
+ router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
- const [imageUrl, setImageUrl] = useState("")
+ const [imageUrl, setImageUrl] = useState('')
const classes = classnames({
CharacterUnit: true,
@@ -48,19 +48,19 @@ const CharacterUnit = (props: Props) => {
})
function generateImageUrl() {
- let imgSrc = ""
+ let imgSrc = ''
if (props.gridCharacter) {
const character = props.gridCharacter.object!
// Change the image based on the uncap level
- let suffix = "01"
- if (props.gridCharacter.uncap_level == 6) suffix = "04"
- else if (props.gridCharacter.uncap_level == 5) suffix = "03"
- else if (props.gridCharacter.uncap_level > 2) suffix = "02"
+ let suffix = '01'
+ if (props.gridCharacter.uncap_level == 6) suffix = '04'
+ else if (props.gridCharacter.uncap_level == 5) suffix = '03'
+ else if (props.gridCharacter.uncap_level > 2) suffix = '02'
// Special casing for Lyria (and Young Cat eventually)
- if (props.gridCharacter.object.granblue_id === "3030182000") {
+ if (props.gridCharacter.object.granblue_id === '3030182000') {
let element = 1
if (grid.weapons.mainWeapon && grid.weapons.mainWeapon.element) {
element = grid.weapons.mainWeapon.element
@@ -90,14 +90,14 @@ const CharacterUnit = (props: Props) => {
) : (
- ""
+ ''
)}
)
const editableImage = (
{
special={character.special}
/>
) : (
- ""
+ ''
)}
{character?.name[locale]}
diff --git a/components/Dialog/index.scss b/components/Dialog/index.scss
new file mode 100644
index 00000000..04c6e1c2
--- /dev/null
+++ b/components/Dialog/index.scss
@@ -0,0 +1,87 @@
+.Dialog {
+ $multiplier: 4;
+
+ animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running
+ openModal;
+ background: var(--dialog-bg);
+ border-radius: $card-corner;
+ display: flex;
+ flex-direction: column;
+ gap: $unit * $multiplier;
+ height: auto;
+ min-width: $unit * 48;
+ min-height: $unit-12x;
+ padding: $unit * $multiplier;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 21;
+
+ .DialogHeader {
+ display: flex;
+ align-items: center;
+ gap: $unit;
+
+ .left {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: $unit;
+
+ p {
+ font-size: $font-small;
+ line-height: 1.25;
+ }
+ }
+ }
+
+ .DialogClose {
+ background: transparent;
+
+ &:hover {
+ cursor: pointer;
+
+ svg {
+ fill: $error;
+ }
+ }
+
+ svg {
+ fill: $grey-50;
+ float: right;
+ height: 24px;
+ width: 24px;
+ }
+ }
+
+ .DialogTitle {
+ color: var(--text-primary);
+ font-size: $font-xlarge;
+ flex-grow: 1;
+ }
+
+ .DialogTop {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: calc($unit / 2);
+
+ .SubTitle {
+ color: var(--text-secondary);
+ font-size: $font-small;
+ font-weight: $medium;
+ }
+ }
+
+ .DialogDescription {
+ color: var(--text-secondary);
+ flex-grow: 1;
+ }
+
+ .actions {
+ display: flex;
+ justify-content: flex-end;
+ width: 100%;
+ }
+}
diff --git a/components/Dialog/index.tsx b/components/Dialog/index.tsx
new file mode 100644
index 00000000..2438a2ed
--- /dev/null
+++ b/components/Dialog/index.tsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import * as DialogPrimitive from '@radix-ui/react-dialog'
+import classNames from 'classnames'
+
+import './index.scss'
+
+interface Props
+ extends React.DetailedHTMLProps<
+ React.DialogHTMLAttributes