From 1f5ba810600a6c5c9458b5ab8b72436a3b6c5001 Mon Sep 17 00:00:00 2001 From: fleur Date: Wed, 22 Apr 2026 21:06:35 +0200 Subject: [PATCH 1/7] initial --- code/datums/sprite_accessories.dm | 4 + code/modules/client/preferences.dm | 57 +++- icons/map_icons/items/pda.dmi | Bin 11183 -> 9469 bytes .../code/character_preview_background.dm | 24 ++ .../icons/background_32x32.dmi | Bin 0 -> 1136 bytes .../icons/background_64x64.dmi | Bin 0 -> 1948 bytes .../icons/background_96x96.dmi | Bin 0 -> 3249 bytes .../code/human_accessories/human_horns.dm | 1 + .../code/lizard_accessories/lizard_tail.dm | 1 + .../code/monkey_accessories/monkey_tail.dm | 1 + .../canine_accessories/canine_ears.dm | 2 + .../cervid_accessories/cervid_horns.dm | 1 + .../leporid_accessories/leporid_ears.dm | 3 + tgstation.dme | 1 + .../CharacterPreferences/MainPage.tsx | 33 ++- .../CharacterPreferences/QuirksPage.tsx | 24 +- .../CharacterPreferences/loadout/index.tsx | 2 +- .../components/SideDropdown.tsx | 272 ++++++++++++++++++ .../doppler/character_preview_background.tsx | 9 + .../tgui/interfaces/PreferencesMenu/types.ts | 3 + .../PreferencesMenu/useServerPrefs.ts | 5 + .../interfaces/common/CharacterPreview.tsx | 11 +- 22 files changed, 432 insertions(+), 22 deletions(-) create mode 100644 modular_doppler/character_preview_background/code/character_preview_background.dm create mode 100644 modular_doppler/character_preview_background/icons/background_32x32.dmi create mode 100644 modular_doppler/character_preview_background/icons/background_64x64.dmi create mode 100644 modular_doppler/character_preview_background/icons/background_96x96.dmi create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/components/SideDropdown.tsx create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/doppler/character_preview_background.tsx diff --git a/code/datums/sprite_accessories.dm b/code/datums/sprite_accessories.dm index 61bd7d1e401561..513ca554adcd48 100644 --- a/code/datums/sprite_accessories.dm +++ b/code/datums/sprite_accessories.dm @@ -47,6 +47,10 @@ /// Determines if this is considered "sane" for the purpose of [/proc/randomize_human_normie] /// Basically this is to blacklist the extremely wacky stuff from being picked in random human generation. var/natural_spawn = TRUE + // DOPPLER SHIFT ADDITION START + /// If set TRUE, equipping this sprite accessory to your character will zoom out the character preview box. ONLY WORKS ON TAIL/HORNS/EARS + var/zooms_out_character_preview = FALSE + // DOPPLER SHIFT ADDITION END /datum/sprite_accessory/blank name = SPRITE_ACCESSORY_NONE diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 338fa2e661910f..21835430d3a84a 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -279,7 +279,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) preview_pref = params["updated_preview"] character_preview_view.update_body() return TRUE - + if("update_background") + update_preference(GLOB.preference_entries[/datum/preference/choiced/background_state], params["new_background"]) + return TRUE if ("set_tricolor_preference") var/requested_preference_key = params["preference"] var/index_key = params["value"] @@ -391,12 +393,21 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/datum/preferences/preferences /// Whether we show current job clothes or nude/loadout only var/show_job_clothes = TRUE + // DOPPLER SHIFT ADDITION START: Better character preview + var/image/canvas + var/last_canvas_size + var/last_canvas_state + // DOPPLER SHIFT ADDITION END /atom/movable/screen/map_view/char_preview/Initialize(mapload, datum/preferences/preferences) . = ..() src.preferences = preferences /atom/movable/screen/map_view/char_preview/Destroy() + // DOPPLER SHIFT ADDITION START + canvas?.cut_overlays() + canvas = null + // DOPPLER SHIFT ADDITION END QDEL_NULL(body) preferences?.character_preview_view = null preferences = null @@ -411,6 +422,50 @@ GLOBAL_LIST_EMPTY(preferences_datums) appearance = preferences.render_new_preview_appearance(body, show_job_clothes) + // DOPPLER SHIFT ADDITION BEGIN: Better character preview + var/canvas_state = preferences.read_preference(/datum/preference/choiced/background_state) + var/canvas_size = 0 + if ((body.dna.features[FEATURE_TAUR] != /datum/sprite_accessory/taur/none::name)) + canvas_size = 1 + if (body.mob_height > 12) + canvas_size = 1 + if (("Oversized" in preferences.all_quirks)) + canvas_size = 2 + if(canvas_size == 0) + var/obj/item/organ/ears/ears = body.get_organ_slot(ORGAN_SLOT_EARS) + if(ears && ears?.bodypart_overlay?.sprite_datum?.zooms_out_character_preview) + canvas_size = 1 + LAZYNULL(ears) + var/obj/item/organ/tail/tail = body.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) + if(tail && tail?.bodypart_overlay?.sprite_datum?.zooms_out_character_preview) + canvas_size = 1 + LAZYNULL(tail) + var/obj/item/organ/horns/horns = body.get_organ_slot(ORGAN_SLOT_EXTERNAL_HORNS) + if(horns && horns?.bodypart_overlay?.sprite_datum?.zooms_out_character_preview) + canvas_size = 1 + LAZYNULL(horns) + body.pixel_x = canvas_size * 16 + + if (isnull(canvas) || last_canvas_size != canvas_size || last_canvas_state != canvas_state) + switch (canvas_size) + if (0) + canvas = image('modular_doppler/character_preview_background/icons/background_32x32.dmi', icon_state = canvas_state) + if (1) + canvas = image('modular_doppler/character_preview_background/icons/background_64x64.dmi', icon_state = canvas_state) + if (2) + canvas = image('modular_doppler/character_preview_background/icons/background_96x96.dmi', icon_state = canvas_state) + + // Update the map view bounds when canvas size changes to properly display the scaled preview + set_position(1, 1) + last_canvas_size = canvas_size + last_canvas_state = canvas_state + + canvas.cut_overlays() + canvas.add_overlay(body.appearance) + + appearance = canvas.appearance + // DOPPLER SHIFT ADDITION END + /atom/movable/screen/map_view/char_preview/proc/create_body() QDEL_NULL(body) diff --git a/icons/map_icons/items/pda.dmi b/icons/map_icons/items/pda.dmi index 29ba74b1ca34cceace8dc4e479cad20a55701279..62f53f25d34e4dddc9f61045ea38bfcaec7ec4d6 100644 GIT binary patch literal 9469 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 PxERXv8gf-{OauQ1oL8{c diff --git a/modular_doppler/character_preview_background/code/character_preview_background.dm b/modular_doppler/character_preview_background/code/character_preview_background.dm new file mode 100644 index 00000000000000..e705a376b7226c --- /dev/null +++ b/modular_doppler/character_preview_background/code/character_preview_background.dm @@ -0,0 +1,24 @@ +/// Enables the choice of background in the character preview menu +/datum/preference/choiced/background_state + savefile_key = "background_state" + savefile_identifier = PREFERENCE_CHARACTER + +GLOBAL_LIST_INIT(background_state_options, list( + "Black", + "Grey", + "White", + "White Tiles", + "Plasteel", + "Dark Tiles" , + "Plating", + "Reinforced Floor", +)) + +/datum/preference/choiced/background_state/create_default_value() + return GLOB.background_state_options[1] + +/datum/preference/choiced/background_state/init_possible_values() + return GLOB.background_state_options + +/datum/preference/choiced/background_state/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences) + return diff --git a/modular_doppler/character_preview_background/icons/background_32x32.dmi b/modular_doppler/character_preview_background/icons/background_32x32.dmi new file mode 100644 index 0000000000000000000000000000000000000000..6cda1e2e5dc195398712c9ec2ff486b1556c8e36 GIT binary patch literal 1136 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGo%c?>mN?cNllZ!G7N;32F7#J$% z^j_G_b;v=0^+MHm?Y1T2@oRlgG>fna9<1_IyQ!Ehmd1W`zq!PQ$-d?KbF7apnIIZ^ z*P(9RS7FNu^B=GrKO44Myfo=~(T(iXiEP^5oJlOtXQZ6llDsI?UFO-o%d65qA5i@t za`Z>vqTQ+oOS$r%7`VJWI#Z!TkE{6b<6pC#E(?@~FIoBhP7?dl&-4EC25HRt^ZOV7 zq=mVMPb7;5GcYi>db&7G}3}d##buRnckCN=o)vCI3<2$^SY0&*oaZpX?JPac}0A>Ne-| zOpPez^>~=mW-Z`iI-__UV&ps|@UsbdI`sZ`=>ugPLn{NxPylSm5`DDhU zS2bEwo1O`Gu6||Hu7KACGa4Im%wF%|Jezh|;q%+FYSo`EN)PvN?!CufY8R$8Rj+2( zq-_;VYV|SJA76Rhdc--a=IxGWSFdh4DsJ*~$F}+F&u0Jrd23I;z5P_bRo9qv=DlX* z+9CHr-T|NgA67Lyta4a#ojK`f(#7Au8NXJ=me%ioV!ig=kub)5e(Z2nn0L<+vl`0{d8AJl(`G4?MHSe{FsVh7REJ7GOUHx3vIVCg!05VVm6#xJL literal 0 HcmV?d00001 diff --git a/modular_doppler/character_preview_background/icons/background_64x64.dmi b/modular_doppler/character_preview_background/icons/background_64x64.dmi new file mode 100644 index 0000000000000000000000000000000000000000..e448bae09346b12a3c5823b12091774a4f83e30b GIT binary patch literal 1948 zcmdT_TTqin6yBIfKq^7o0gE;v<5X*FMMkZHhzV>1F?Cjb7&Uf~FbM~Z{ zr`yscc1r*NOWolf>KC?+^MJSsMv8wo%{j<9WqWh&Kr zasTv7>5A9*#T})m!v={b&7B>bYto7>WxLwz7|UxKp(I@G)^vBGLvhW~L8f+Xv_0jn zMMWRG*f#E`i_!NhE9hzGt1ynKyZ1zgK5=X>T5YwP681>;M^r1t$FVkl{GfYOcItMg zPku~_TVwv$={t8`RT~T1TwME>hQB1;=&pa9sG2uhm-)ja&xp-)jc6S?W3~bSvvPOW z9eWdUUi2}zD`~5mCbv|09kvw4ocPSPLCl7BH9MtJx3t}Fxe)ga@0U@rtzyZ?)$tEaDz#Mysa zCi9Nqa&M3mtl<3i!Z>66vw1Jx7r( z`ucuo60W3vMB0`9pi8ZmbDIVR4qgpNNlt#duM=m6)@3{9ngWw~Tn*%tCx%b#gr-; zf#SQ6sSWZ>C4NK&Ca~s(szW4-0o9Fbt3eik_+SQotW!p?r@jjmbxvF>JX zwZ(7en{Ml08E1>mougc=jdX{Dc*aBA=av>(Rn!n`p)y#S{Y3(?%2zX_?lX<2T}@MN zoXu9YPP3KJ7M?+rj;oV+G8pop{`P#X(W+a=ir4Y4N2O?nR*vRQE5&@abHe)R>O}T* zZgm3`Hx07tatBiHmH7o7K z*=*gFTG9~-?4h@%(7ex(^gdn1{hum`*YG5oeEFibj3m(m7is_|uOJgIFNSDSW8gkI z?A$!&t8%wcygmL6*gWzye-qAHz0>mbPa1Yn)TQsXI0k()Iw{QJM0k|2I#!C5vXM47 zx0F_!Q*o^&Z~Oy!Ndu<6%J^^7uWQDz7gYW0F0=VgmHu|O%gY@NB7eWp7|#CSGpwKo zIa6~RoZ#{M=-8H4pPMMC-eyteZX_jZAliNOMx6Z*rcd)4790t~ z=(fxctQABxKGz5)?}^dt&jpJf@C9>on5bq6LOpLkCXe_Y$!3vtQlL<$EpXwlBRNll z$!#iBGWOuaj->O-P&~EA1%wcvR1iv?RX-2Taw1!7ZeMlg$0&wpdU9ET(h#!U%ifzW z0$1sWD&r&~?&J6-A%k4TPn?P!IQ($CSRIndmKJc$yo_6i9JKxY2grcODTR1NL$|u| zH#_0Cmtl`l18Q&ig1CKpvVVl+yEVE2Z}m3=dazVdm(B>39@nBfGduE?B`hV3%ED$e z!Kft@?T>NRI5jq`S?(4}p$4o!9@89GYGptT22&Kvoo2=-uqQ+eke&m@j-qO4z%E+X z{s;ENX|RA4?<1v~RtXY8#FQ9(IfFcL!)k({pf?FQJ2H`$& z9d!h$4Cm$Tv7j@T00P1(Jb;krcz`)iPSHSHkad0SrVDz5=`#FFHCcdYc6T7sK;Cns cLKFq%pCOV literal 0 HcmV?d00001 diff --git a/modular_doppler/character_preview_background/icons/background_96x96.dmi b/modular_doppler/character_preview_background/icons/background_96x96.dmi new file mode 100644 index 0000000000000000000000000000000000000000..50076e1f9e1cadd794e8a2dfba30d58a089552ff GIT binary patch literal 3249 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 literal 0 HcmV?d00001 diff --git a/modular_doppler/modular_customization/accessories/code/human_accessories/human_horns.dm b/modular_doppler/modular_customization/accessories/code/human_accessories/human_horns.dm index cd722755f1eb6e..0e0a7f1f49d958 100644 --- a/modular_doppler/modular_customization/accessories/code/human_accessories/human_horns.dm +++ b/modular_doppler/modular_customization/accessories/code/human_accessories/human_horns.dm @@ -95,6 +95,7 @@ name = "Painted Points" icon = 'modular_doppler/modular_customization/accessories/icons/human/horns_big.dmi' icon_state = "paintedpoints" + zooms_out_character_preview = TRUE /datum/sprite_accessory/horns/humanoid/big/whoshorns name = "Who's Horns" diff --git a/modular_doppler/modular_customization/accessories/code/lizard_accessories/lizard_tail.dm b/modular_doppler/modular_customization/accessories/code/lizard_accessories/lizard_tail.dm index afd13c944258e5..bb6a40854650b2 100644 --- a/modular_doppler/modular_customization/accessories/code/lizard_accessories/lizard_tail.dm +++ b/modular_doppler/modular_customization/accessories/code/lizard_accessories/lizard_tail.dm @@ -36,6 +36,7 @@ icon_state = "shadekin_large" dimension_x = 64 center = TRUE + zooms_out_character_preview = TRUE /datum/sprite_accessory/tails/lizard/big/shadekin_long name = "Shadekin (Big)(Long)" diff --git a/modular_doppler/modular_customization/accessories/code/monkey_accessories/monkey_tail.dm b/modular_doppler/modular_customization/accessories/code/monkey_accessories/monkey_tail.dm index aed9b1e8b89757..2f6f574a9d2a46 100644 --- a/modular_doppler/modular_customization/accessories/code/monkey_accessories/monkey_tail.dm +++ b/modular_doppler/modular_customization/accessories/code/monkey_accessories/monkey_tail.dm @@ -10,3 +10,4 @@ icon_state = "bigring_large" dimension_x = 64 center = TRUE + zooms_out_character_preview = TRUE diff --git a/modular_doppler/modular_customization/accessories/code/non_species_specific/canine_accessories/canine_ears.dm b/modular_doppler/modular_customization/accessories/code/non_species_specific/canine_accessories/canine_ears.dm index 2d1586604ad924..29a3f938d48e5e 100644 --- a/modular_doppler/modular_customization/accessories/code/non_species_specific/canine_accessories/canine_ears.dm +++ b/modular_doppler/modular_customization/accessories/code/non_species_specific/canine_accessories/canine_ears.dm @@ -13,6 +13,7 @@ icon = 'modular_doppler/modular_customization/accessories/icons/non_species_specific/canine/canine_ears_big.dmi' name = "Acrador" icon_state = "acrador" + zooms_out_character_preview = TRUE /datum/sprite_accessory/ears_more/dog/large/acrador_large name = "Acrador (Big)" @@ -29,6 +30,7 @@ icon = 'modular_doppler/modular_customization/accessories/icons/non_species_specific/canine/canine_ears_big.dmi' name = "Sandfox" icon_state = "sandfox" + zooms_out_character_preview = TRUE /datum/sprite_accessory/ears_more/dog/wolf name = "Wolf" diff --git a/modular_doppler/modular_customization/accessories/code/non_species_specific/cervid_accessories/cervid_horns.dm b/modular_doppler/modular_customization/accessories/code/non_species_specific/cervid_accessories/cervid_horns.dm index b4d5b902276ef8..067c023e8b2c4d 100644 --- a/modular_doppler/modular_customization/accessories/code/non_species_specific/cervid_accessories/cervid_horns.dm +++ b/modular_doppler/modular_customization/accessories/code/non_species_specific/cervid_accessories/cervid_horns.dm @@ -7,6 +7,7 @@ icon = 'modular_doppler/modular_customization/accessories/icons/non_species_specific/cervid/cervid_horns_big.dmi' name = "Antlers" icon_state = "antlers" + zooms_out_character_preview = TRUE /datum/sprite_accessory/horns/deer/big/wide name = "Antlers (Wide)" diff --git a/modular_doppler/modular_customization/accessories/code/non_species_specific/leporid_accessories/leporid_ears.dm b/modular_doppler/modular_customization/accessories/code/non_species_specific/leporid_accessories/leporid_ears.dm index 12bf58c5047534..95622df46853d9 100644 --- a/modular_doppler/modular_customization/accessories/code/non_species_specific/leporid_accessories/leporid_ears.dm +++ b/modular_doppler/modular_customization/accessories/code/non_species_specific/leporid_accessories/leporid_ears.dm @@ -17,7 +17,10 @@ icon = 'modular_doppler/modular_customization/accessories/icons/non_species_specific/leporid/leporid_ears_big.dmi' name = "Lop (Big)" icon_state = "rabbit_large" + zooms_out_character_preview = FALSE //ok this is a little awkward + // because these are the parent of the big subtype but also lop ears so niche case that doesn't need zooming. lol /datum/sprite_accessory/ears_more/bunny/big/rabbit name = "Bunny (Tall)" icon_state = "bunny_large" + zooms_out_character_preview = TRUE diff --git a/tgstation.dme b/tgstation.dme index e5385ecd05328d..779e9b76d0092c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6873,6 +6873,7 @@ #include "modular_doppler\carp_infusion\code\carp_organs.dm" #include "modular_doppler\cell_component\code\cell_component.dm" #include "modular_doppler\chamtoggle\code\datums\mutations\chameleon.dm" +#include "modular_doppler\character_preview_background\code\character_preview_background.dm" #include "modular_doppler\chatroom_soul\code\chatroom_keybinding.dm" #include "modular_doppler\chatroom_soul\code\irc_checks.dm" #include "modular_doppler\chatroom_soul\code\irc_verbs.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx index 148ddc52956962..f2a4cf273576b6 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx @@ -5,7 +5,6 @@ import { type sendAct, useBackend } from 'tgui/backend'; import { Box, Button, - Dropdown, // DOPPLER ADDITION Floating, Input, LabeledList, @@ -19,12 +18,13 @@ import { createSearch } from 'tgui-core/string'; import { CharacterPreview } from '../../common/CharacterPreview'; import { PageButton } from '../components/PageButton'; // DOPPLER ADDITION import { RandomizationButton } from '../components/RandomizationButton'; +import { SideDropdown } from '../components/SideDropdown'; // DOPPLER ADDITION import { features } from '../preferences/features'; import { type FeatureChoicedServerData, FeatureValueInput, } from '../preferences/features/base'; -import { Gender, GENDERS } from '../preferences/gender'; +import { GENDERS, Gender } from '../preferences/gender'; import { createSetPreference, type PreferencesMenuData, @@ -462,19 +462,12 @@ export function MainPage(props: MainPageProps) { useState(false); const [multiNameInputOpen, setMultiNameInputOpen] = useState(false); const [randomToggleEnabled] = useRandomToggleState(); - - { - /* DOPPLER ADDITION START */ - } enum PrefPage { Character, // The generic character options Markings, // Markings } const [currentPrefPage, setCurrentPrefPage] = useState(PrefPage.Character); - { - /* DOPPLER ADDITION END */ - } const serverData = useServerPrefs(); @@ -502,16 +495,9 @@ export function MainPage(props: MainPageProps) { const nonContextualPreferences = { ...data.character_preferences.non_contextual, }; - - { - /* DOPPLER ADDITION START */ - } const MarkingPreferences = { ...data.character_preferences.markings, }; - { - /* DOPPLER ADDITION END */ - } if (randomBodyEnabled) { nonContextualPreferences.random_species = @@ -611,7 +597,7 @@ export function MainPage(props: MainPageProps) { {/* DOPPLER ADDITION START */} - + {/* NOVA EDIT ADDITION START: Background Selection */} + + + act('update_background', { + new_background: value, + }) + } + /> + {/* DOPPLER ADDITION END */} (); // DOPPLER EDIT ADDITION const className = 'PreferencesMenu__Quirks__QuirkList__quirk'; @@ -455,7 +457,27 @@ function QuirkPage() { - + {/* // DOPPLER EDIT REMOVAL - moved down */} + {/* DOPPLER EDIT ADDITION START */} + + {/* Keep the CharacterPreview alive but "hidden", so that traits that affect appearance (e.g. Oversized) refresh rendering calculations immediately. */} + + + + + + {/* DOPPLER EDIT ADDITION END */} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/index.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/index.tsx index 510b21e8c864f6..3ee00781bc3ccd 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/index.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/index.tsx @@ -299,7 +299,7 @@ function LoadoutPreviewSection() { > - + {/* DOPPLER SHIFT CHANGE - ORIGINAL: */} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/components/SideDropdown.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/components/SideDropdown.tsx new file mode 100644 index 00000000000000..4418bccda940a1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/components/SideDropdown.tsx @@ -0,0 +1,272 @@ +// THIS IS A DOPPLER SHIFT UI FILE +// Ported from Bubberstation: https://github.com/Bubberstation/Bubberstation/commit/07daef9517a879f04281fe4b97b69d4f9a6d2338 + +import { type ComponentProps, type ReactNode, useRef, useState } from 'react'; +import { type Box, Button, Floating, Icon } from 'tgui-core/components'; +import { KEY } from 'tgui-core/keys'; +import { classes } from 'tgui-core/react'; +import { unit } from 'tgui-core/ui'; + +type SideDropdownEntry = { + displayText: ReactNode; + value: string | number; +}; + +type SideDropdownOption = string | SideDropdownEntry; + +type BoxProps = ComponentProps; + +type Props = { + /** Called when a value is picked from the list, `value` is the value that was picked */ + onSelected: (value: any) => void; + /** An array of strings which will be displayed in the + dropdown when open. See Dropdown.tsx for more advanced usage with DropdownEntry */ + options: SideDropdownOption[]; + /** Currently selected entry to display. Can be left stateless to permanently display this value. */ + selected: SideDropdownOption | null | undefined; +} & Partial<{ + /** Whether to scroll automatically on open. Defaults to true */ + autoScroll: boolean; + /** Whether to display previous / next buttons */ + buttons: boolean; + /** Color of dropdown button */ + color: string; + /** Disables the dropdown */ + disabled: boolean; + /** Overwrites selection text with this. Good for objects etc. */ + displayText: ReactNode; + /** Icon to display in dropdown button */ + icon: string; + /** Whether the icon should be displayed alone */ + iconOnly: boolean; + /** Angle of the icon */ + iconRotation: number; + /** Whether or not the icon should spin */ + iconSpin: boolean; + /** Width of the dropdown menu in box units. Default: 15 */ + menuWidth: string | number; + /** Whether or not the arrow on the right hand side of the dropdown button is visible */ + noChevron: boolean; + /** Text to show when nothing has been selected. */ + placeholder: string; + /** @deprecated If you want to allow dropdown breaks layout, set width 100% */ + clipSelectedText: boolean; + /** Called when dropdown button is clicked */ + onClick: (event) => void; + /** Dropdown renders over instead of below */ + over: boolean; +}> & + BoxProps; + +enum DIRECTION { + Current = 'current', + Next = 'next', + Previous = 'previous', +} + +const NONE = -1; + +function getOptionValue(option: SideDropdownOption) { + return typeof option === 'string' ? option : option.value; +} + +/** + * ## Dropdown + * A simple dropdown box component. Lets the user select from a list of options + * and displays selected entry. + */ +export function SideDropdown(props: Props) { + const { + autoScroll = true, + buttons, + className, + color = 'default', + disabled, + displayText, + icon, + iconRotation, + iconSpin, + iconOnly, + menuWidth, + noChevron, + onClick, + onSelected, + options = [], + placeholder = 'Select...', + selected, + width = 15, + } = props; + + const [open, setOpen] = useState(false); + const innerRef = useRef(null); + + const selectedIndex = + options.findIndex((option) => getOptionValue(option) === selected) || 0; + + function scrollToElement(position: number) { + let scrollPos = position; + if (position < selectedIndex) { + scrollPos = position < 2 ? 0 : position - 2; + } else { + scrollPos = + position > options.length - 3 ? options.length - 1 : position - 2; + } + + const dropdownMenu = innerRef.current; + const element = dropdownMenu?.children[scrollPos] as HTMLElement; + if (dropdownMenu && element) { + dropdownMenu.scrollTop = element.offsetTop; + } + } + + /** Update the selected value when clicking the left/right buttons */ + function updateSelected(direction: DIRECTION) { + if (options.length < 1 || disabled) { + return; + } + + const startIndex = 0; + const endIndex = options.length - 1; + + let newIndex: number; + if (selectedIndex < 0) { + newIndex = direction === 'next' ? endIndex : startIndex; // No selection yet + } else if (direction === 'next') { + newIndex = selectedIndex === endIndex ? startIndex : selectedIndex + 1; // Move to next option + } else { + newIndex = selectedIndex === startIndex ? endIndex : selectedIndex - 1; // Move to previous option + } + + if (open && autoScroll) { + scrollToElement(newIndex); + } + onSelected?.(getOptionValue(options[newIndex])); + } + + return ( +
+ + {options.length === 0 ? ( +
No options
+ ) : ( + options.map((option) => { + const value = getOptionValue(option); + return ( +
{ + onSelected?.(value); + }} + onKeyDown={(event) => { + if (event.key === KEY.Enter) { + onSelected?.(value); + } + }} + > + {typeof option === 'string' ? option : option.displayText} +
+ ); + }) + )} +
+ } + onMounted={() => { + if (open && autoScroll && selectedIndex !== NONE) { + /** + * Floating uses async FloatingPortal, + * the dropdown content is not yet ready when you open it. + */ + + scrollToElement(selectedIndex); + } + }} + > +
{ + if (disabled && !open) { + return; + } + onClick?.(event); + }} + onKeyDown={(event) => { + if (event.key === KEY.Enter && !disabled) { + onClick?.(event); + } + }} + > + {icon && ( + + )} + {!iconOnly && ( + <> + + {displayText || + (selected && getOptionValue(selected)) || + placeholder} + + + {!noChevron && ( + + )} + + )} +
+ + {buttons && ( + <> +