From 08463d04852866fc6e60a5b4de0174ce76f87284 Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Sun, 8 Sep 2024 22:11:23 -0700 Subject: [PATCH 1/8] working on item properties --- src/components/Tooltip.tsx | 55 +++++++++++++ src/components/formNav/SaveButton.tsx | 6 +- src/components/pills/PropertyPills.tsx | 33 -------- .../sotww/inputs/PathInputs/PathInput.tsx | 25 ++++-- .../inputs/WeaponInputs/PropertyPills.tsx | 42 ++++++++++ .../inputs/WeaponInputs/WeaponInputItem.tsx | 36 +-------- .../inputs/WeaponInputs/WeaponInputItem.tsx | 2 +- src/constants/sotww/game.ts | 80 +++++++++---------- src/typings/characters.d.ts | 2 +- src/typings/sotww/characterData.d.ts | 2 - 10 files changed, 162 insertions(+), 121 deletions(-) create mode 100644 src/components/Tooltip.tsx delete mode 100644 src/components/pills/PropertyPills.tsx create mode 100644 src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx new file mode 100644 index 00000000..ef6ec982 --- /dev/null +++ b/src/components/Tooltip.tsx @@ -0,0 +1,55 @@ +import styled from '@emotion/styled'; +import { PropsWithChildren, useRef, useState } from 'react'; + +import { pxToRem } from '~/logic/utils/styles/pxToRem'; + +import { Box } from './box/Box'; + +interface TooltipProps { + id: string; + tipText: string; +} + +const Target = styled.div``; + +const Tip = styled.span` + position: absolute; + z-index: 999; + background: ${({ theme }) => theme.colors.background}; + color: ${({ theme }) => theme.colors.text}; + top: 0; + transform: translateY(calc(-100% - ${({ theme }) => theme.spacing[8]})); + width: 200px; + padding: ${({ theme }) => theme.spacing[8]}; + border: 1px solid ${({ theme }) => theme.colors.text}; +`; + +export function Tooltip({ + id, + children, + tipText, +}: PropsWithChildren) { + const targetRef = useRef(null); + const [show, setShow] = useState(false); + + return ( + + {show && ( + + {tipText} + + )} + setShow(false)} + onFocus={() => setShow(true)} + onMouseEnter={() => setShow(true)} + onMouseLeave={() => setShow(false)} + > + {children} + + + ); +} 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/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/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..8bd7f6a5 --- /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..487909b8 100644 --- a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx +++ b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx @@ -11,19 +11,15 @@ 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_TRAIT_KEYS, WEAPON_TRAITS, } 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 +44,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 +53,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 +117,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/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..b79ab236 100644 --- a/src/constants/sotww/game.ts +++ b/src/constants/sotww/game.ts @@ -4,39 +4,42 @@ 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 = { + 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 +55,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/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; From 2b75eebde808d412cb7defdc3791a2d3ceb8e703 Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Mon, 9 Sep 2024 18:47:11 -0700 Subject: [PATCH 2/8] tooltip pills --- package.json | 1 + src/components/Tooltip.tsx | 59 ++++++++++++++----- .../inputs/WeaponInputs/PropertyPills.tsx | 2 +- src/constants/sotww/game.ts | 4 ++ yarn.lock | 41 +++++++++++++ 5 files changed, 92 insertions(+), 15 deletions(-) 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/Tooltip.tsx b/src/components/Tooltip.tsx index ef6ec982..07a71228 100644 --- a/src/components/Tooltip.tsx +++ b/src/components/Tooltip.tsx @@ -1,24 +1,31 @@ import styled from '@emotion/styled'; -import { PropsWithChildren, useRef, useState } from 'react'; - -import { pxToRem } from '~/logic/utils/styles/pxToRem'; +import { + autoUpdate, + flip, + offset, + shift, + useDismiss, + useFloating, + useFocus, + useHover, + useInteractions, + useRole, +} from '@floating-ui/react'; +import { PropsWithChildren, useState } from 'react'; import { Box } from './box/Box'; interface TooltipProps { id: string; tipText: string; + isLabeled: boolean; } const Target = styled.div``; const Tip = styled.span` - position: absolute; - z-index: 999; background: ${({ theme }) => theme.colors.background}; color: ${({ theme }) => theme.colors.text}; - top: 0; - transform: translateY(calc(-100% - ${({ theme }) => theme.spacing[8]})); width: 200px; padding: ${({ theme }) => theme.spacing[8]}; border: 1px solid ${({ theme }) => theme.colors.text}; @@ -28,25 +35,49 @@ export function Tooltip({ id, children, tipText, + isLabeled, }: PropsWithChildren) { - const targetRef = useRef(null); 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} )} setShow(false)} - onFocus={() => setShow(true)} - onMouseEnter={() => setShow(true)} - onMouseLeave={() => setShow(false)} + {...getReferenceProps()} > {children} diff --git a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx index 8bd7f6a5..3cb4a678 100644 --- a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx +++ b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/PropertyPills.tsx @@ -32,7 +32,7 @@ export function PropertyPills({ name }: PropertyPillProps) { {weaponProperties.map((p) => { const tipId = `${p}-trait-tip`; return ( - + ); diff --git a/src/constants/sotww/game.ts b/src/constants/sotww/game.ts index b79ab236..d69cf29f 100644 --- a/src/constants/sotww/game.ts +++ b/src/constants/sotww/game.ts @@ -5,6 +5,10 @@ export const PATH_TYPES = ['novice', 'expert', 'master'] as const; export type SotwwPathType = (typeof PATH_TYPES)[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: 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" From 7f4c5dcb21adfdf72d7c87310b7c9dec491431ca Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Mon, 9 Sep 2024 19:13:23 -0700 Subject: [PATCH 3/8] column rearrangement --- src/components/nav/NavBar.tsx | 8 ++++++-- .../rulebookSpecific/sotww/CharacterSheet.tsx | 18 ++++++++++++++---- .../rulebookSpecific/sotww/FormNav.tsx | 4 ++-- .../sotww/inputs/AttributeInputs.tsx | 4 +--- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/components/nav/NavBar.tsx b/src/components/nav/NavBar.tsx index 0673c24c..8b9cb16c 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'; @@ -88,6 +91,7 @@ export function NavBar({ dropdownMenuItems, }: NavBarProps) { const isXxs = useBreakpointsLessThan('xs'); + const atLeastSm = useBreakpointsAtLeast('sm'); const flexGap = isXxs ? 8 : 16; const { user } = useUser(); const userName = getNameFromUser(user as StrictSessionUser); @@ -110,7 +114,7 @@ export function NavBar({ - {userName && !isXxs && ( + {userName && atLeastSm && ( + {/* 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..78b398e7 100644 --- a/src/components/rulebookSpecific/sotww/FormNav.tsx +++ b/src/components/rulebookSpecific/sotww/FormNav.tsx @@ -26,7 +26,7 @@ const HealthButton = styled(BaseButton)(({ theme }) => ({ })); function CharacterHeader({ headerPortalNode, name }: CharacterHeaderProps) { - const atLeastSm = useBreakpointsAtLeast('sm'); + const atLeastXs = useBreakpointsAtLeast('xs'); const { watch, setValue } = useFormContext(); const currentHealth = watch('health_current'); @@ -57,7 +57,7 @@ function CharacterHeader({ headerPortalNode, name }: CharacterHeaderProps) { - {atLeastSm && ( + {atLeastXs && ( 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 ( From 90f0144c0bd7382f067a046a32e06a72243ce934 Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Mon, 9 Sep 2024 20:50:22 -0700 Subject: [PATCH 4/8] vertical divider in nav --- src/components/Divider.tsx | 43 ---------------- src/components/divider/Divider.tsx | 37 ++++++++++++++ src/components/divider/components.tsx | 20 ++++++++ src/components/divider/types.ts | 16 ++++++ src/components/dropdowns/DropdownMenu.tsx | 2 +- src/components/nav/NavBar.tsx | 51 +++++++++++-------- .../rulebookSpecific/sotww/FormNav.tsx | 13 +++-- src/pages/index.tsx | 2 +- 8 files changed, 115 insertions(+), 69 deletions(-) delete mode 100644 src/components/Divider.tsx create mode 100644 src/components/divider/Divider.tsx create mode 100644 src/components/divider/components.tsx create mode 100644 src/components/divider/types.ts 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/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/nav/NavBar.tsx b/src/components/nav/NavBar.tsx index 8b9cb16c..79a087f4 100644 --- a/src/components/nav/NavBar.tsx +++ b/src/components/nav/NavBar.tsx @@ -16,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'; @@ -77,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; @@ -91,7 +97,7 @@ export function NavBar({ dropdownMenuItems, }: NavBarProps) { const isXxs = useBreakpointsLessThan('xs'); - const atLeastSm = useBreakpointsAtLeast('sm'); + const atLeastMd = useBreakpointsAtLeast('md'); const flexGap = isXxs ? 8 : 16; const { user } = useUser(); const userName = getNameFromUser(user as StrictSessionUser); @@ -114,27 +120,30 @@ export function NavBar({ - {userName && atLeastSm && ( - - + + - - - - - {userName} - - - + + + + + + {userName} + + + + )} diff --git a/src/components/rulebookSpecific/sotww/FormNav.tsx b/src/components/rulebookSpecific/sotww/FormNav.tsx index 78b398e7..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 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'); @@ -58,7 +61,11 @@ function CharacterHeader({ headerPortalNode, name }: CharacterHeaderProps) { {atLeastXs && ( - + = currentHealth ? 'danger' : 'normal'} + title="Increment Damage" + onClick={onHealthClick} + > Damage 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'; From 0945a841675d0cf622af9372520814354f4bc940 Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Mon, 9 Sep 2024 20:59:18 -0700 Subject: [PATCH 5/8] more readable lightmode text --- src/constants/theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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', From 5fb97f18bb463d4b753d21ebb097b79cda0a488a Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Mon, 9 Sep 2024 21:02:43 -0700 Subject: [PATCH 6/8] cleanup --- src/components/Tooltip.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx index 07a71228..0056430b 100644 --- a/src/components/Tooltip.tsx +++ b/src/components/Tooltip.tsx @@ -13,6 +13,8 @@ import { } from '@floating-ui/react'; import { PropsWithChildren, useState } from 'react'; +import { pxToRem } from '~/logic/utils/styles/pxToRem'; + import { Box } from './box/Box'; interface TooltipProps { @@ -26,7 +28,7 @@ const Target = styled.div``; const Tip = styled.span` background: ${({ theme }) => theme.colors.background}; color: ${({ theme }) => theme.colors.text}; - width: 200px; + width: ${pxToRem(200)}; padding: ${({ theme }) => theme.spacing[8]}; border: 1px solid ${({ theme }) => theme.colors.text}; `; From 5e3ed32621ce44b900c2de5d9b76e09ee6ab4f80 Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Mon, 9 Sep 2024 21:04:13 -0700 Subject: [PATCH 7/8] cleanup 2 --- .../sotww/inputs/WeaponInputs/WeaponInputItem.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx index 487909b8..3f8bf414 100644 --- a/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx +++ b/src/components/rulebookSpecific/sotww/inputs/WeaponInputs/WeaponInputItem.tsx @@ -16,7 +16,6 @@ import { SotwwWeaponTrait, WEAPON_PROPERTY_ABBREVIATIONS, WEAPON_TRAIT_KEYS, - WEAPON_TRAITS, } from '~/constants/sotww/game'; import { EditContext } from '~/logic/contexts/editContext'; import { useBreakpointsLessThan } from '~/logic/hooks/useBreakpoints'; From b504c60677981e235c9545fb3d576c1f06bec114 Mon Sep 17 00:00:00 2001 From: Troy Chryssos Date: Mon, 9 Sep 2024 21:04:49 -0700 Subject: [PATCH 8/8] cleanup 3 --- .../rulebookSpecific/sotww/inputs/WeaponInputs/index.tsx | 2 -- 1 file changed, 2 deletions(-) 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,