diff --git a/package.json b/package.json index c3a7d6c7..a2fe5b6b 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@auth0/nextjs-auth0": "^1.9.1", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@floating-ui/react": "^0.26.23", "@mui/base": "5.0.0-beta.4", "@playwright/test": "^1.35.0", "@prisma/client": "^4.15.0", diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx deleted file mode 100644 index 2024ab3b..00000000 --- a/src/components/Divider.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import styled from '@emotion/styled'; - -import { Color } from '~/typings/theme'; - -import { FlexBox } from './box/FlexBox'; -import { Text } from './Text'; - -type DividerProps = { - label?: string; - className?: string; - color?: Color; -}; - -const DividerWrapper = styled(FlexBox)` - min-height: ${({ theme }) => theme.borderWidth[1]}; - width: 100%; -`; - -const Segment = styled.div>` - width: 100%; - height: ${({ theme }) => theme.borderWidth[1]}; - background-color: ${({ theme, color }) => theme.colors[color || 'text']}; -`; - -const Label = styled(Text)` - padding: 0 ${({ theme }) => theme.spacing[16]}; -`; - -export function Divider({ label, className, color }: DividerProps) { - return ( - - {label && ( - <> - - - - )} - - - ); -} diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx new file mode 100644 index 00000000..0056430b --- /dev/null +++ b/src/components/Tooltip.tsx @@ -0,0 +1,88 @@ +import styled from '@emotion/styled'; +import { + autoUpdate, + flip, + offset, + shift, + useDismiss, + useFloating, + useFocus, + useHover, + useInteractions, + useRole, +} from '@floating-ui/react'; +import { PropsWithChildren, useState } from 'react'; + +import { pxToRem } from '~/logic/utils/styles/pxToRem'; + +import { Box } from './box/Box'; + +interface TooltipProps { + id: string; + tipText: string; + isLabeled: boolean; +} + +const Target = styled.div``; + +const Tip = styled.span` + background: ${({ theme }) => theme.colors.background}; + color: ${({ theme }) => theme.colors.text}; + width: ${pxToRem(200)}; + padding: ${({ theme }) => theme.spacing[8]}; + border: 1px solid ${({ theme }) => theme.colors.text}; +`; + +export function Tooltip({ + id, + children, + tipText, + isLabeled, +}: PropsWithChildren) { + const [show, setShow] = useState(false); + const { refs, floatingStyles, context } = useFloating({ + open: show, + whileElementsMounted: autoUpdate, + onOpenChange: setShow, + middleware: [offset(10), flip(), shift()], + }); + + const hover = useHover(context, { move: false }); + const focus = useFocus(context); + const dismiss = useDismiss(context); + const role = useRole(context, { + role: isLabeled ? 'tooltip' : 'label', + }); + + // Merge all the interactions into prop getters + const { getReferenceProps, getFloatingProps } = useInteractions([ + hover, + focus, + dismiss, + role, + ]); + + return ( + + {show && ( + + {tipText} + + )} + + {children} + + + ); +} diff --git a/src/components/divider/Divider.tsx b/src/components/divider/Divider.tsx new file mode 100644 index 00000000..8d1902d0 --- /dev/null +++ b/src/components/divider/Divider.tsx @@ -0,0 +1,37 @@ +import { pxToRem } from '~/logic/utils/styles/pxToRem'; + +import { Box } from '../box/Box'; +import { DividerWrapper, Label, Segment } from './components'; +import { DividerProps } from './types'; + +export function Divider({ + color = 'text', + vertical, + className, + label, +}: DividerProps) { + if (vertical) { + return ( + + ); + } + + return ( + + {label && ( + <> + + + + )} + + + ); +} diff --git a/src/components/divider/components.tsx b/src/components/divider/components.tsx new file mode 100644 index 00000000..8eca6c1f --- /dev/null +++ b/src/components/divider/components.tsx @@ -0,0 +1,20 @@ +import styled from '@emotion/styled'; + +import { FlexBox } from '../box/FlexBox'; +import { Text } from '../Text'; +import { DividerProps } from './types'; + +export const DividerWrapper = styled(FlexBox)` + min-height: ${({ theme }) => theme.borderWidth[1]}; + width: 100%; +`; + +export const Segment = styled.div>` + width: 100%; + height: ${({ theme }) => theme.borderWidth[1]}; + background-color: ${({ theme, color }) => theme.colors[color || 'text']}; +`; + +export const Label = styled(Text)` + padding: 0 ${({ theme }) => theme.spacing[16]}; +`; diff --git a/src/components/divider/types.ts b/src/components/divider/types.ts new file mode 100644 index 00000000..4c5bf964 --- /dev/null +++ b/src/components/divider/types.ts @@ -0,0 +1,16 @@ +import { Color } from '~/typings/theme'; + +type HorizDividerProps = { + label?: string; + vertical?: never | false; +}; + +type VerticalDividerProps = { + label?: never; + vertical: true; +}; + +export type DividerProps = { + color?: Color; + className?: string; +} & (HorizDividerProps | VerticalDividerProps); diff --git a/src/components/dropdowns/DropdownMenu.tsx b/src/components/dropdowns/DropdownMenu.tsx index d897d730..23f9786f 100644 --- a/src/components/dropdowns/DropdownMenu.tsx +++ b/src/components/dropdowns/DropdownMenu.tsx @@ -5,7 +5,7 @@ import { MouseEventHandler, useState } from 'react'; import { FlexBox } from '../box/FlexBox'; import { TextButton } from '../buttons/TextButton'; -import { Divider } from '../Divider'; +import { Divider } from '../divider/Divider'; import { Link } from '../Link'; import { Pane } from '../Pane'; import { Text } from '../Text'; diff --git a/src/components/formNav/SaveButton.tsx b/src/components/formNav/SaveButton.tsx index 8d279bee..d3bcf3d9 100644 --- a/src/components/formNav/SaveButton.tsx +++ b/src/components/formNav/SaveButton.tsx @@ -28,14 +28,14 @@ export function SaveButton({ characterId = NEW_ID, rulebookName, }: SaveButtonProps) { - const [isSaving, setisSaving] = useState(false); + const [isSaving, setIsSaving] = useState(false); const { addNotifications } = useContext(NotificationsContext); const { getValues } = useFormContext(); const { push } = useRouter(); const onSave = async () => { - setisSaving(true); + setIsSaving(true); try { const resp = await saveCharacter({ id: characterId as number | typeof NEW_ID, @@ -66,7 +66,7 @@ export function SaveButton({ ), ]); } - setisSaving(false); + setIsSaving(false); }; return ( diff --git a/src/components/nav/NavBar.tsx b/src/components/nav/NavBar.tsx index 0673c24c..79a087f4 100644 --- a/src/components/nav/NavBar.tsx +++ b/src/components/nav/NavBar.tsx @@ -3,7 +3,10 @@ import styled from '@emotion/styled'; import { HOME_ROUTE } from '~/constants/routing/client'; import { createUsersRoute } from '~/constants/routing/shared'; -import { useBreakpointsLessThan } from '~/logic/hooks/useBreakpoints'; +import { + useBreakpointsAtLeast, + useBreakpointsLessThan, +} from '~/logic/hooks/useBreakpoints'; import { pxToRem } from '~/logic/utils/styles/pxToRem'; import { getIconFromUser, getNameFromUser } from '~/logic/utils/user'; import { Spacing } from '~/typings/theme'; @@ -13,6 +16,7 @@ import { LogoAscii } from '../ascii/LogoAscii'; import { Box } from '../box/Box'; import { FlexBox } from '../box/FlexBox'; import { GridBox } from '../box/GridBox'; +import { Divider } from '../divider/Divider'; import { DropdownMenuProps } from '../dropdowns/DropdownMenu'; import { ProfileDropdown } from '../dropdowns/ProfileDropdown'; import { RpgIcon } from '../icons/RpgIcon'; @@ -74,6 +78,11 @@ const UserName = styled(Text)` white-space: nowrap; `; +const VertDivider = styled(Divider)` + align-self: stretch; + height: unset; +`; + interface NavBarProps { title: string; setIconPortalNode: (node: HTMLDivElement) => void; @@ -88,6 +97,7 @@ export function NavBar({ dropdownMenuItems, }: NavBarProps) { const isXxs = useBreakpointsLessThan('xs'); + const atLeastMd = useBreakpointsAtLeast('md'); const flexGap = isXxs ? 8 : 16; const { user } = useUser(); const userName = getNameFromUser(user as StrictSessionUser); @@ -110,27 +120,30 @@ export function NavBar({ - {userName && !isXxs && ( - - + + - - - - - {userName} - - - + + + + + + {userName} + + + + )} diff --git a/src/components/pills/PropertyPills.tsx b/src/components/pills/PropertyPills.tsx deleted file mode 100644 index b8b746ce..00000000 --- a/src/components/pills/PropertyPills.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import styled from '@emotion/styled'; -import { startCase } from 'lodash'; -import { useFormContext } from 'react-hook-form'; - -import { FlexBox } from '../box/FlexBox'; -import { Text } from '../Text'; -import { Pill } from './Pill'; - -const TraitPill = styled(Pill)` - min-width: ${({ theme }) => theme.spacing[24]}; - text-align: center; -`; - -interface PropertyPillProps { - name: string; -} - -export function PropertyPills({ name }: PropertyPillProps) { - const { watch } = useFormContext(); - const weaponProperties: string[] = watch(name); - - if (!weaponProperties.length) { - return None; - } - - return ( - - {weaponProperties.map((p) => ( - - ))} - - ); -} diff --git a/src/components/rulebookSpecific/sotww/CharacterSheet.tsx b/src/components/rulebookSpecific/sotww/CharacterSheet.tsx index a5222d6e..15f98a4c 100644 --- a/src/components/rulebookSpecific/sotww/CharacterSheet.tsx +++ b/src/components/rulebookSpecific/sotww/CharacterSheet.tsx @@ -49,6 +49,10 @@ const tabLabels: TabLabelObject[] = [ label: 'Description', icon: RpgIcons.Scroll, }, + { + label: 'Stats', + icon: RpgIcons.Dice, + }, { label: 'Abilities', icon: RpgIcons.Ripple, @@ -122,11 +126,18 @@ export function CharacterSheet({ character }: SotwwCharacterSheetProps) { + {/* Stats */} + + + + + + + + {/* Abilities */} - - @@ -134,10 +145,9 @@ export function CharacterSheet({ character }: SotwwCharacterSheetProps) { {/* Combat */} - + - diff --git a/src/components/rulebookSpecific/sotww/FormNav.tsx b/src/components/rulebookSpecific/sotww/FormNav.tsx index 8975da90..8e57dcbb 100644 --- a/src/components/rulebookSpecific/sotww/FormNav.tsx +++ b/src/components/rulebookSpecific/sotww/FormNav.tsx @@ -10,6 +10,8 @@ import { FormNavBaseButtons } from '~/components/formNav/FormNavBaseButtons'; import { Text } from '~/components/Text'; import { NavContext } from '~/logic/contexts/navContext'; import { useBreakpointsAtLeast } from '~/logic/hooks/useBreakpoints'; +import { guaranteeNumberValue } from '~/logic/utils/form/guaranteeNumberValue'; +import { pxToRem } from '~/logic/utils/styles/pxToRem'; import { SotwwCharacterData } from '~/typings/sotww/characterData'; interface FormNavProps { @@ -23,14 +25,15 @@ interface CharacterHeaderProps { const HealthButton = styled(BaseButton)(({ theme }) => ({ padding: `${theme.spacing[4]} ${theme.spacing[8]}`, + minWidth: pxToRem(66), })); function CharacterHeader({ headerPortalNode, name }: CharacterHeaderProps) { - const atLeastSm = useBreakpointsAtLeast('sm'); + const atLeastXs = useBreakpointsAtLeast('xs'); const { watch, setValue } = useFormContext(); - const currentHealth = watch('health_current'); - const damage = watch('damage'); + const currentHealth = guaranteeNumberValue(watch('health_current')); + const damage = guaranteeNumberValue(watch('damage')); const ancestry = watch('ancestry'); const level = watch('level'); const novicePath = watch('path_novice'); @@ -57,8 +60,12 @@ function CharacterHeader({ headerPortalNode, name }: CharacterHeaderProps) { - {atLeastSm && ( - + {atLeastXs && ( + = currentHealth ? 'danger' : 'normal'} + title="Increment Damage" + onClick={onHealthClick} + > Damage diff --git a/src/components/rulebookSpecific/sotww/inputs/AttributeInputs.tsx b/src/components/rulebookSpecific/sotww/inputs/AttributeInputs.tsx index 640d9349..d1cc9e59 100644 --- a/src/components/rulebookSpecific/sotww/inputs/AttributeInputs.tsx +++ b/src/components/rulebookSpecific/sotww/inputs/AttributeInputs.tsx @@ -5,7 +5,6 @@ import { FormSection } from '~/components/form/containers/FormSection'; import { NumberInputProps } from '~/components/form/typings'; import { RpgIcons } from '~/constants/icons'; import { ATTRIBUTES } from '~/constants/sotww/game'; -import { useBreakpointsLessThan } from '~/logic/hooks/useBreakpoints'; import { SotwwCharacterData } from '~/typings/sotww/characterData'; type AttributeInputProps = Omit, 'type' | 'name'> & { @@ -37,10 +36,9 @@ function AttributeInput>({ } export function AttributeInputs() { - const isLessThanXs = useBreakpointsLessThan('xs'); return ( diff --git a/src/components/rulebookSpecific/sotww/inputs/PathInputs/PathInput.tsx b/src/components/rulebookSpecific/sotww/inputs/PathInputs/PathInput.tsx index cea9c001..b0f27e84 100644 --- a/src/components/rulebookSpecific/sotww/inputs/PathInputs/PathInput.tsx +++ b/src/components/rulebookSpecific/sotww/inputs/PathInputs/PathInput.tsx @@ -14,6 +14,7 @@ import { RpgIcons } from '~/constants/icons'; import { SotwwPathType } from '~/constants/sotww/game'; import { EditContext } from '~/logic/contexts/editContext'; import { useBreakpointsLessThan } from '~/logic/hooks/useBreakpoints'; +import { makeNestedFieldNameFn } from '~/logic/utils/form/makeNestedFieldNameFn'; import { SortableAddAnotherChildProps } from '~/typings/form'; import { SotwwCharacterData, @@ -40,17 +41,26 @@ function PathBenefitInput({ pathType, }: PathBenefitInputProps) { const { isEditMode } = useContext(EditContext); - const { watch } = useFormContext(); + const { watch } = useFormContext(); - const benefitName = watch( - `path_${pathType}_benefits.${postSortIndex}.path_benefit_name` + const pathBenefitName = `path_${pathType}_benefits` as const; + + const makePathBenefitFieldName = makeNestedFieldNameFn< + SotwwCharacterData, + typeof pathBenefitName + >(pathBenefitName); + + const benefitNameFieldName = makePathBenefitFieldName( + 'path_benefit_name', + postSortIndex ); + return ( label="Name" - name={`path_${pathType}_benefits.${postSortIndex}.path_benefit_name`} + name={benefitNameFieldName} /> {isEditMode && ( @@ -69,7 +79,10 @@ function PathBenefitInput({ label="Description" - name={`path_${pathType}_benefits.${postSortIndex}.path_benefit_description`} + name={makePathBenefitFieldName( + 'path_benefit_description', + postSortIndex + )} /> ); diff --git a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx new file mode 100644 index 00000000..3cb4a678 --- /dev/null +++ b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx @@ -0,0 +1,42 @@ +import styled from '@emotion/styled'; +import { startCase } from 'lodash'; +import { useFormContext } from 'react-hook-form'; + +import { Tooltip } from '~/components/Tooltip'; +import { WEAPON_TRAITS } from '~/constants/sotww/game'; +import { SotwwCharacterData } from '~/typings/sotww/characterData'; + +import { FlexBox } from '../../../../box/FlexBox'; +import { Pill } from '../../../../pills/Pill'; +import { Text } from '../../../../Text'; + +export const TraitPill = styled(Pill)` + min-width: ${({ theme }) => theme.spacing[24]}; + text-align: center; +`; + +export interface PropertyPillProps { + name: string; +} + +export function PropertyPills({ name }: PropertyPillProps) { + const { watch } = useFormContext(); + const weaponProperties = watch(name as `weapons.${number}.weapon_traits`); + + if (!weaponProperties.length) { + return None; + } + + return ( + + {weaponProperties.map((p) => { + const tipId = `${p}-trait-tip`; + return ( + + + + ); + })} + + ); +} diff --git a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx index bb37096b..3f8bf414 100644 --- a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx +++ b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx @@ -11,19 +11,14 @@ import { SelectInput } from '~/components/form/SelectInput'; import { TextAreaInput } from '~/components/form/TextAreaInput'; import { TextInput } from '~/components/form/TextInput'; import { SelectOption } from '~/components/form/typings'; -import { PropertyPills } from '~/components/pills/PropertyPills'; +import { PropertyPills } from '~/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills'; import { SotwwWeaponTrait, - WEAPON_ADVANTAGES, - WEAPON_DISADVANTAGES, WEAPON_PROPERTY_ABBREVIATIONS, - WEAPON_TRAITS, + WEAPON_TRAIT_KEYS, } from '~/constants/sotww/game'; import { EditContext } from '~/logic/contexts/editContext'; -import { - useBreakpointsAtLeast, - useBreakpointsLessThan, -} from '~/logic/hooks/useBreakpoints'; +import { useBreakpointsLessThan } from '~/logic/hooks/useBreakpoints'; import { makeSimpleSelectOptionsFromArray } from '~/logic/utils/form/makeSimpleSelectOptionsFromArray'; import { SortableAddAnotherChildProps } from '~/typings/form'; import { SotwwCharacterData, SotwwWeapon } from '~/typings/sotww/characterData'; @@ -48,13 +43,7 @@ const weaponGripOptions: SelectOption[] = [ }, ]; -const weaponTraitOptions = makeSimpleSelectOptionsFromArray(WEAPON_TRAITS); - -const weaponAdvantageOptions = - makeSimpleSelectOptionsFromArray(WEAPON_ADVANTAGES); - -const weaponDisadvantageOptions = - makeSimpleSelectOptionsFromArray(WEAPON_DISADVANTAGES); +const weaponTraitOptions = makeSimpleSelectOptionsFromArray(WEAPON_TRAIT_KEYS); export function WeaponInputItem({ postSortIndex: index, @@ -63,8 +52,6 @@ export function WeaponInputItem({ const { isEditMode } = useContext(EditContext); const { watch } = useFormContext(); const isLessThanSm = useBreakpointsLessThan('sm'); - const atLeastMd = useBreakpointsAtLeast('md'); - const exactlySm = !isLessThanSm && !atLeastMd; const exactlyXss = useBreakpointsLessThan('xs'); const weaponNameFieldName = createWeaponFieldName('weapon_name', index); @@ -129,22 +116,6 @@ export function WeaponInputItem({ name={weaponTraitsFieldName} options={weaponTraitOptions} /> - - - DisplayComponent={PropertyPills} - label="Advantages" - multiple - name={createWeaponFieldName('weapon_advantages', index)} - options={weaponAdvantageOptions} - /> - - DisplayComponent={PropertyPills} - label="Disadvantages" - multiple - name={createWeaponFieldName('weapon_disadvantages', index)} - options={weaponDisadvantageOptions} - /> - label="Description" name={createWeaponFieldName('weapon_description', index)} diff --git a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/index.tsx b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/index.tsx index dfa7eaf4..86e034a9 100644 --- a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/index.tsx +++ b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/index.tsx @@ -22,10 +22,8 @@ const HideCheckbox = styled(CheckboxInput)` const createDefaultWeapon = (): SotwwWeapon => ({ weapon_name: 'Club', - weapon_advantages: [], weapon_damage: '1d6', weapon_description: '', - weapon_disadvantages: [], weapon_grip: 'one', weapon_traits: [], weapon_equipped: false, diff --git a/src/components/rulebookSpecific/wwn/inputs/WeaponInputs/WeaponInputItem.tsx b/src/components/rulebookSpecific/wwn/inputs/WeaponInputs/WeaponInputItem.tsx index 893323df..a20847eb 100644 --- a/src/components/rulebookSpecific/wwn/inputs/WeaponInputs/WeaponInputItem.tsx +++ b/src/components/rulebookSpecific/wwn/inputs/WeaponInputs/WeaponInputItem.tsx @@ -13,7 +13,7 @@ import { TextAreaInput } from '~/components/form/TextAreaInput'; import { TextInput } from '~/components/form/TextInput'; import { SelectOption } from '~/components/form/typings'; import { Pill } from '~/components/pills/Pill'; -import { PropertyPills } from '~/components/pills/PropertyPills'; +import { PropertyPills } from '~/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills'; import { ATTRIBUTES, WEAPON_TRAITS, WeaponTrait } from '~/constants/wwn/game'; import { EditContext } from '~/logic/contexts/editContext'; import { diff --git a/src/constants/sotww/game.ts b/src/constants/sotww/game.ts index 1af31841..d69cf29f 100644 --- a/src/constants/sotww/game.ts +++ b/src/constants/sotww/game.ts @@ -4,39 +4,46 @@ export type SotwwAttribute = (typeof ATTRIBUTES)[number]; export const PATH_TYPES = ['novice', 'expert', 'master'] as const; export type SotwwPathType = (typeof PATH_TYPES)[number]; -export const WEAPON_TRAITS = [ - 'ammunition', - 'brutal', - 'firearm', - 'forceful', - 'long', - 'nimble', - 'precise', - 'range', - 'special', - 'sharp', - 'shattering', - 'thrown', - 'versatile', -] as const; -export type SotwwWeaponTrait = (typeof WEAPON_TRAITS)[number]; - -export const WEAPON_ADVANTAGES = [ - 'disarming', - 'disrupting', - 'guarding', - 'lunging', - 'pressing', -] as const; -export type SotwwWeaponAdvantage = (typeof WEAPON_ADVANTAGES)[number]; +export const WEAPON_TRAITS = { + ammunition: + "You must have at least one piece of ammunition of the indicated kind to attack with this weapon. Ammunition, and a container to hold it (such as a quiver, case, or bag), is included in the weapon's price. You can recover spent ammunition after combat and make replacements while you rest.", + bludgeoning: + 'When you attack with this weapon and get a critical success, the target becomes vulnerable until the end of your next turn.', + brutal: + 'When rolling damage for an attack made using this weapon, you can reroll each die that comes up as 1 once. You must use the new number rolled, even if it is another 1.', + disarming: + 'You ignore the bane imposed on your roll when you use this weapon to disarm.', + firearm: + 'Attacks you make with this weapon create a loud noise. If the weapon becomes submerged in water, you must dry and clean it before you can make ranged attacks with it. Cleaning the weapon takes 1 hour of work using a tool kit and a pint of oil. It takes 1 minute to load this weapon. If you do something else during this time, you must start over from the beginning.', + large: + 'The result of your roll to attack with this weapon while squeezing or while you are mounted result in an automatic failure.', + light: + 'When you would add Bonus Damage to an attack made using this weapon, you add one fewer die (minimum +1d6).', + long: 'When you attack with this weapon, increase your reach by 1 for the purpose of choosing targets.', + misfire: + 'When you get a critical failure on an attack with this weapon, the weapon misfires. Make a luck roll. On a success, you need only reload the weapon before you can attack with it again. On a failure, the weapon is ruined until repaired, which takes 1 hour, a tool kit, and spare parts whose value equals half the selling price of the weapon.', + nimble: + 'When you attack with this weapon, you can use Agility in place of Strength for the roll.', + piercing: + 'When you attack with this weapon and get a critical success, the target becomes weakened until the end of your next turn.', + range: + 'You select the target for your ranged attacks with this weapon from those within the listed number of yards.', + reload: + 'You must load this weapon before you can make ranged attacks with it. You can use an action to load the weapon or, if you are capable of moving and you have a Speed of 2 or higher, you can give up your move to load the weapon.', + slashing: + 'When you attack with this weapon and get a critical success, the target takes an extra 1d6 damage.', + slow: 'You can attack with this weapon just once per round.', + special: 'This weapon has special rules detailed in its description.', + thrown: + "You can make a ranged attack with this weapon by throwing it. You use Strength for the roll unless the weapon also has the Nimble trait. You choose your target from among those within the listed number of yards. If the attack results in a success, the weapon either sticks in the target's body or falls to its feet. If the attack results in a failure, the weapon lands 1d6 yards behind the target.", + versatile: + "When you wield this weapon with both hands, the weapon's damage increases by 1d6.", +} as const; -export const WEAPON_DISADVANTAGES = [ - 'fixed', - 'light', - 'reload', - 'slow', -] as const; -export type SotwwWeaponDisadvantage = (typeof WEAPON_DISADVANTAGES)[number]; +export const WEAPON_TRAIT_KEYS = Object.keys( + WEAPON_TRAITS +) as (keyof typeof WEAPON_TRAITS)[]; +export type SotwwWeaponTrait = (typeof WEAPON_TRAIT_KEYS)[number]; function recursiveSetAbbrv( obj: Record, @@ -52,18 +59,9 @@ function recursiveSetAbbrv( return obj; } -export type SotwwWeaponProperty = - | SotwwWeaponTrait - | SotwwWeaponAdvantage - | SotwwWeaponDisadvantage; - -export const WEAPON_PROPERTY_ABBREVIATIONS = [ - ...WEAPON_TRAITS, - ...WEAPON_ADVANTAGES, - ...WEAPON_DISADVANTAGES, -].reduce( +export const WEAPON_PROPERTY_ABBREVIATIONS = WEAPON_TRAIT_KEYS.reduce( (obj, currProperty) => recursiveSetAbbrv(obj, currProperty), - {} as Record + {} as Record ); export const spellLevelValueToName = { diff --git a/src/constants/theme.ts b/src/constants/theme.ts index 77dacf66..b4378aa9 100644 --- a/src/constants/theme.ts +++ b/src/constants/theme.ts @@ -57,7 +57,7 @@ const darkModeFilters = { const lightModeColors: ColorModeColors = { primary: '#3C91E6', background: '#fafafa', - text: '#17242b', + text: '#41413e', textAccent: '#4d4d4c', success: '#00784e', warning: '#c29e3b', diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 24567883..a1db491b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -7,7 +7,7 @@ import { AuthLink } from '~/components/AuthLink'; import { FlexBox } from '~/components/box/FlexBox'; import { GridBox } from '~/components/box/GridBox'; import { TextButton } from '~/components/buttons/TextButton'; -import { Divider } from '~/components/Divider'; +import { Divider } from '~/components/divider/Divider'; import { Link } from '~/components/Link'; import { LoadingSpinner } from '~/components/LoadingSpinner'; import { Layout } from '~/components/meta/Layout'; diff --git a/src/typings/characters.d.ts b/src/typings/characters.d.ts index 810e83a9..c0cc65cc 100644 --- a/src/typings/characters.d.ts +++ b/src/typings/characters.d.ts @@ -8,7 +8,7 @@ import { CwnCharacterData } from './cwn/characterData'; export type CharacterSaveData = Omit< character, - 'createdOn' | 'lastModifiedOn' | 'id' | 'deleted' + 'createdOn' | 'lastModifiedOn' | 'id' | 'deleted' | 'inactive' > & { id: number | 'new'; }; diff --git a/src/typings/sotww/characterData.d.ts b/src/typings/sotww/characterData.d.ts index 3aa6c6df..718bb92e 100644 --- a/src/typings/sotww/characterData.d.ts +++ b/src/typings/sotww/characterData.d.ts @@ -14,8 +14,6 @@ export type SotwwWeapon = { weapon_name: string; weapon_damage: string; weapon_traits: SotwwWeaponTrait[]; - weapon_advantages: SotwwWeaponAdvantage[]; - weapon_disadvantages: SotwwWeaponDisadvantage[]; weapon_grip: 'off' | 'one' | 'two'; weapon_description: string; weapon_equipped: boolean; diff --git a/yarn.lock b/yarn.lock index e3865ba1..fdd400f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -593,6 +593,42 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== +"@floating-ui/core@^1.6.0": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12" + integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g== + dependencies: + "@floating-ui/utils" "^0.2.7" + +"@floating-ui/dom@^1.0.0": + version "1.6.10" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f" + integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.7" + +"@floating-ui/react-dom@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0" + integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/react@^0.26.23": + version "0.26.23" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.23.tgz#28985e5ce482c34f347f28076f11267e47a933bd" + integrity sha512-9u3i62fV0CFF3nIegiWiRDwOs7OW/KhSUJDNx2MkQM3LbE5zQOY01sL3nelcVBXvX7Ovvo3A49I8ql+20Wg/Hw== + dependencies: + "@floating-ui/react-dom" "^2.1.1" + "@floating-ui/utils" "^0.2.7" + tabbable "^6.0.0" + +"@floating-ui/utils@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e" + integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== + "@hapi/hoek@^9.0.0": version "9.2.1" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" @@ -5277,6 +5313,11 @@ synckit@^0.8.4: "@pkgr/utils" "^2.3.1" tslib "^2.5.0" +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"