diff --git a/eslint.config.js b/eslint.config.js index e2f1709d..22a75c26 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -70,7 +70,7 @@ export default [ ['^\\u0000'], ['^@?\\w'], [ - '^(actions|components|eventManagers|middleware|permissions|reducers|serverPropTypes|style|thirdpartyExtensions|utils)', + '^(actions|components|contexts|eventManagers|middleware|permissions|reducers|serverPropTypes|style|utils)', ], ['^'], ['^\\.'], diff --git a/package-lock.json b/package-lock.json index 75661595..4fc52f3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "react-highlight-words": "^0.21.0", "react-redux": "^9.2.0", "react-router": "^7.12.0", - "react-transition-group": "^4.4.5", + "react-transitioning": "^1.0.9", "redux": "^5.0.1", "redux-localstorage": "^0.4.1", "redux-thunk": "^3.1.0", @@ -98,15 +98,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@csstools/css-parser-algorithms": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", @@ -2785,9 +2776,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001716", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz", - "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==", + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "dev": true, "funding": [ { @@ -3026,12 +3017,6 @@ "node": ">=4" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -3212,16 +3197,6 @@ "node": ">=0.10.0" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -6294,20 +6269,13 @@ } } }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, + "node_modules/react-transitioning": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/react-transitioning/-/react-transitioning-1.0.9.tgz", + "integrity": "sha512-XnICsOdR/B5C0nyrM66MzF5OFSrigbCpwDNyNLgkMoyk93fFvehNgLc9ByIUHKcvJK7FR3BvM2cFMccJzO80Fg==", + "license": "MIT", "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" + "react": ">=16.8.0" } }, "node_modules/readdirp": { diff --git a/package.json b/package.json index 0cefda78..a6b01ae6 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "react-highlight-words": "^0.21.0", "react-redux": "^9.2.0", "react-router": "^7.12.0", - "react-transition-group": "^4.4.5", + "react-transitioning": "^1.0.9", "redux": "^5.0.1", "redux-localstorage": "^0.4.1", "redux-thunk": "^3.1.0", diff --git a/src/actions/users.js b/src/actions/users.js index ba6e5a9c..08b123cc 100644 --- a/src/actions/users.js +++ b/src/actions/users.js @@ -28,7 +28,7 @@ export const USER_LIST_FAILURE = 'USER_LIST_FAILURE' */ const refreshUsersDelayed = (dispatch, getState) => { const page = getState().settings.users.list.data.pagination.current - return dispatch(delay(loadUsers({ page }), 3000)) + return dispatch(delay(loadUsers(page), 3000)) } /** diff --git a/src/components/generics/ConfirmationBar.jsx b/src/components/generics/ConfirmationBar.jsx index 71f53326..ce3f6fcc 100644 --- a/src/components/generics/ConfirmationBar.jsx +++ b/src/components/generics/ConfirmationBar.jsx @@ -1,43 +1,65 @@ import PropTypes from 'prop-types' +import Slide from 'components/transitions/Slide' + export default function ConfirmationBar({ message = 'Are you sure?', + show, + setShow, onCancel, onConfirm, + hideOnCancel = true, + hideOnConfirm = false, }) { return ( -
-
-
{message}
-
- - + +
+
+
{message}
+
+ + +
-
+ ) } ConfirmationBar.propTypes = { message: PropTypes.string, - onCancel: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, + show: PropTypes.bool, + setShow: PropTypes.func.isRequired, + onCancel: PropTypes.func, + onConfirm: PropTypes.func, + hideOnCancel: PropTypes.bool, + hideOnConfirm: PropTypes.bool, } diff --git a/src/components/generics/Details.jsx b/src/components/generics/Details.jsx index f6d9e908..c9fb79dd 100644 --- a/src/components/generics/Details.jsx +++ b/src/components/generics/Details.jsx @@ -1,8 +1,8 @@ import classNames from 'classnames' import PropTypes from 'prop-types' import { useState } from 'react' -import { CSSTransition } from 'react-transition-group' +import Collapse from 'components/transitions/Collapse' import { isDisplayable } from 'utils' export function Details({ children }) { @@ -69,17 +69,11 @@ export function DetailLongText({ notifiable: isDisplayable(notifications), })} > - -
+ +

{children}

- +
{!revealed && (
) diff --git a/src/components/generics/Notification.jsx b/src/components/generics/NotificationBar.jsx similarity index 61% rename from src/components/generics/Notification.jsx rename to src/components/generics/NotificationBar.jsx index 163a23db..875d3e91 100644 --- a/src/components/generics/Notification.jsx +++ b/src/components/generics/NotificationBar.jsx @@ -1,21 +1,21 @@ import classNames from 'classnames' import PropTypes from 'prop-types' -import { useEffect, useMemo, useRef, useState } from 'react' -import { CSSTransition, TransitionGroup } from 'react-transition-group' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import Slide from 'components/transitions/Slide' import { alterationResponsePropType, Status, } from 'reducers/alterationsResponse' -const notificationTypes = { +const types = { [Status.pending]: 'success', [Status.successful]: 'success', [Status.failed]: 'danger', } -export default function Notification({ - alterationResponse, +export default function NotificationBar({ + alterationResponse = {}, failedDuration = 5000, failedMessage = 'Failure', pendingMessage = 'Pending…', @@ -23,7 +23,7 @@ export default function Notification({ successfulMessage = 'Success', noDisplayOnMount = false, }) { - const [display, setDisplay] = useState(!noDisplayOnMount) + const [show, setShow] = useState(false) const durations = useMemo( () => ({ @@ -41,12 +41,27 @@ export default function Notification({ [pendingMessage, successfulMessage, failedMessage] ) - const { - status, - date, - message: messageInState, - fields: fieldsInState, - } = alterationResponse || {} + const { status, date } = alterationResponse + + const getMessage = useCallback( + (alterationResponse) => { + const { status, fields, message } = alterationResponse + + // specific case if the alteration response contains field errors + if (fields && Object.keys(fields).length) { + return 'There are field errors' + } + + // specific case if the alteration contains a message + if (message) { + return message + } + + // default to specified messages + return messages[status] + }, + [messages] + ) useEffect(() => { if (!status || !date) { @@ -54,13 +69,13 @@ export default function Notification({ } // display if status and date changed and have a valid value - setDisplay(true) + setShow(true) // request to hide success or failure message only after a certain time let timeout if (status !== Status.pending && durations[status]) { timeout = setTimeout(() => { - setDisplay(false) + setShow(false) }, durations[status]) } @@ -72,53 +87,24 @@ export default function Notification({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [status, date]) - let notification - if (display && alterationResponse) { - // if there is a message in the state, keep it - // if there is a field in the state, consider there was an error with fields - // otherwise, use the messages passed to the compenent - let message - if (messageInState) { - message = messageInState - } else if (fieldsInState && Object.keys(fieldsInState).length > 0) { - message = 'There are field errors.' - } else { - message = messages[status] - } + const notificationMessage = getMessage(alterationResponse) - // if there is no message to display, do not show any notification - if (message) { - notification = ( - -
-
-
{message}
-
-
-
- ) - } + if (!notificationMessage) { + return null } return ( - - {notification} - + +
+
+
{notificationMessage}
+
+
+
) } -Notification.propTypes = { +NotificationBar.propTypes = { alterationResponse: alterationResponsePropType, failedDuration: PropTypes.number, failedMessage: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), diff --git a/src/components/generics/SearchBox.jsx b/src/components/generics/SearchBox.jsx index 5b420b42..70a51d63 100644 --- a/src/components/generics/SearchBox.jsx +++ b/src/components/generics/SearchBox.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types' import { useEffect, useState } from 'react' import { useSearchParams } from 'react-router' -import { CSSTransitionLazy } from 'thirdpartyExtensions/ReactTransitionGroup' +import Collapse from 'components/transitions/Collapse' function SearchBoxHelp({ example, fields, withHash }) { return ( @@ -53,12 +53,12 @@ SearchBoxHelp.propTypes = { * Note that the query and its setter are owned by a parent component. */ export default function SearchBox({ help, placeholder, query, setQuery }) { - const [displayHelp, setDisplayHelp] = useState(false) - const [searchParams, setSearchParams] = useSearchParams() const queryFromParams = searchParams.get('query') + const [displayed, setDisplayed] = useState(false) + useEffect( () => { // update query from URL immediately @@ -79,7 +79,7 @@ export default function SearchBox({ help, placeholder, query, setQuery }) { className="control square transparent" type="button" onClick={() => { - setDisplayHelp(!displayHelp) + setDisplayed(!displayed) }} > @@ -89,16 +89,11 @@ export default function SearchBox({ help, placeholder, query, setQuery }) { ) helpBox = ( - - - + +
+ +
+
) } diff --git a/src/components/generics/TokenWidget.jsx b/src/components/generics/TokenWidget.jsx index 85356d90..4db79790 100644 --- a/src/components/generics/TokenWidget.jsx +++ b/src/components/generics/TokenWidget.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import { useState } from 'react' -import Notification from 'components/generics/Notification' +import NotificationBar from 'components/generics/NotificationBar' import { Status } from 'reducers/alterationsResponse' export default function TokenWidget({ token }) { @@ -32,7 +32,7 @@ export default function TokenWidget({ token }) {
- { - if (onToggle) { + // manage on toggle callback if any + if (typeof onToggle === 'function') { onToggle(expanded) } }, @@ -44,30 +49,15 @@ export function ListingEntry({ setSearchParams(searchParams) } - /** - * Add transition to extra - */ - - let extraTransition - if (extra) { - extraTransition = [extra].flat().map((item, index) => ( - - {item} - - )) - } - return ( -
  • +
  • {children}
    )} {!expanded && ( - - {extraTransition} - +
      + + {[extra].flat().map((item, index) => ( + + {item} + + ))} + +
    )}
  • {!expanded && isDisplayable(controls) && ( @@ -93,22 +89,16 @@ export function ListingEntry({
    {notifications}
    )}
    - - <>{entryExpanded} - + +
    {entryExpanded}
    +
    ) } ListingEntry.propTypes = { children: PropTypes.node, + className: PropTypes.string, entryExpanded: PropTypes.element, extra: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.element), @@ -159,7 +149,7 @@ export function ListingEntryExpanded({ notifications, }) { return ( -
    +
    {isDisplayable(extra) && }
    {children}
    {(isDisplayable(controls) || isDisplayable(notifications)) && ( diff --git a/src/components/generics/listing/FetchWrapper.jsx b/src/components/generics/listing/FetchWrapper.jsx index 1ce8e5e3..e5dfa34a 100644 --- a/src/components/generics/listing/FetchWrapper.jsx +++ b/src/components/generics/listing/FetchWrapper.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import Delayer from 'components/generics/Delayer' -import Notification from 'components/generics/Notification' +import NotificationBar from 'components/generics/NotificationBar' import { Status } from 'reducers/alterationsResponse' export default function FetchWrapper({ status, children }) { @@ -19,7 +19,7 @@ export default function FetchWrapper({ status, children }) { return (
    {children} - {children} - } else { - content = ( - - {children.map((item, index) => ( - - {item} - - ))} - - ) - } + const content = ( + +
      + {noTransition ? ( + entries + ) : ( + + {entries.map((item) => ( + {item} + ))} + + )} +
    +
    + ) if (fetchStatus) { return ( @@ -71,7 +62,7 @@ export default function ListingList({ } ListingList.propTypes = { - children: PropTypes.node, + entries: PropTypes.arrayOf(PropTypes.node).isRequired, fetchStatus: PropTypes.symbol, free: PropTypes.bool, mini: PropTypes.bool, diff --git a/src/components/karaoke/player/ManageButton.jsx b/src/components/karaoke/player/ManageButton.jsx index 9c088a08..adfa529d 100644 --- a/src/components/karaoke/player/ManageButton.jsx +++ b/src/components/karaoke/player/ManageButton.jsx @@ -2,11 +2,11 @@ import classNames from 'classnames' import PropTypes from 'prop-types' import { Component } from 'react' +import Swipe from 'components/transitions/Swipe' import { alterationResponsePropType, Status, } from 'reducers/alterationsResponse' -import { CSSTransitionLazy } from 'thirdpartyExtensions/ReactTransitionGroup' /** * ManageButton class for a button connected to a player manage command @@ -121,8 +121,7 @@ export default class ManageButton extends Component { } render() { - const { onClick, disabled, className, timeout, iconDisabled, error } = - this.props + const { onClick, disabled, className, iconDisabled, error } = this.props const onClickControlled = (e) => { // do not manage any other click during the transition @@ -150,15 +149,11 @@ export default class ManageButton extends Component { onClick={onClickControlled} disabled={disabled} > - -
    + +
    - +
    ) } diff --git a/src/components/karaoke/player/Notification.jsx b/src/components/karaoke/player/Notification.jsx index 44e67a57..bbeeb0c4 100644 --- a/src/components/karaoke/player/Notification.jsx +++ b/src/components/karaoke/player/Notification.jsx @@ -1,7 +1,8 @@ import PropTypes from 'prop-types' import { Component } from 'react' -import { CSSTransition, TransitionGroup } from 'react-transition-group' +import { TransitionGroup } from 'react-transitioning' +import Collapse from 'components/transitions/Collapse' import { alterationResponsePropType, Status, @@ -128,25 +129,20 @@ export default class PlayerNotification extends Component { let notification if (message) { notification = ( - -
    + +
    {message}
    - +
    ) } return ( - - {notification} - +
    + + {notification} + +
    ) } } diff --git a/src/components/karaoke/player/Player.jsx b/src/components/karaoke/player/Player.jsx index 0a9e05df..80dc8dee 100644 --- a/src/components/karaoke/player/Player.jsx +++ b/src/components/karaoke/player/Player.jsx @@ -1,5 +1,4 @@ import classNames from 'classnames' -import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { sendPlayerCommand } from 'actions/playlist' @@ -11,9 +10,9 @@ import { } from 'components/karaoke/player/Carousel' import ManageButton from 'components/karaoke/player/ManageButton' import PlayerNotification from 'components/karaoke/player/Notification' +import Collapse from 'components/transitions/Collapse' import { isPlaylistManagerOrOwner } from 'permissions/playlist' import { Status } from 'reducers/alterationsResponse' -import { CSSTransitionLazy } from 'thirdpartyExtensions/ReactTransitionGroup' export default function Player() { const user = useSelector((state) => state.authenticatedUser) @@ -25,22 +24,14 @@ export default function Player() { (state) => state.alterationsResponse.multiple.sendPlayerCommands ) - const [animationsEnabled, setAnimationsEnabled] = useState(false) - const dispatch = useDispatch() const { data: playerStatus } = playerStatusState - const withControls = isPlaylistManagerOrOwner( + const hasControls = isPlaylistManagerOrOwner( user, playerStatus.playlist_entry ) - useEffect(() => { - // enable animations if the user, the player status, or the possibility to - // use controls changes - setAnimationsEnabled(true) - }, [user, playerStatus, withControls]) - const { playerErrors } = playerErrorsDigestState.data const fetchError = playerStatusState.status === Status.failed const isPlaying = !!playerStatus.playlist_entry @@ -83,74 +74,69 @@ export default function Player() { )} - -
    - { - dispatch(sendPlayerCommand('restart')) - }} - disabled={controlDisabled} - error={fetchError} - icon="step-backward" - /> - { - dispatch(sendPlayerCommand('rewind')) - }} - disabled={controlDisabled} - error={fetchError} - icon="backward" - /> - { - if (!isPlaying) return + +
    +
    + { + dispatch(sendPlayerCommand('restart')) + }} + disabled={controlDisabled} + error={fetchError} + icon="step-backward" + /> + { + dispatch(sendPlayerCommand('rewind')) + }} + disabled={controlDisabled} + error={fetchError} + icon="backward" + /> + { + if (!isPlaying) return - if (playerStatus.paused) { - dispatch(sendPlayerCommand('resume')) - } else { - dispatch(sendPlayerCommand('pause')) + if (playerStatus.paused) { + dispatch(sendPlayerCommand('resume')) + } else { + dispatch(sendPlayerCommand('pause')) + } + }} + disabled={controlDisabled} + error={fetchError} + icon={ + isPlaying ? (playerStatus.paused ? 'play' : 'pause') : 'stop' } - }} - disabled={controlDisabled} - error={fetchError} - icon={isPlaying ? (playerStatus.paused ? 'play' : 'pause') : 'stop'} - /> - { - dispatch(sendPlayerCommand('fast_forward')) - }} - disabled={controlDisabled} - error={fetchError} - icon="forward" - /> - { - dispatch(sendPlayerCommand('skip', true)) - }} - disabled={controlDisabled} - error={fetchError} - icon="step-forward" - /> + /> + { + dispatch(sendPlayerCommand('fast_forward')) + }} + disabled={controlDisabled} + error={fetchError} + icon="forward" + /> + { + dispatch(sendPlayerCommand('skip', true)) + }} + disabled={controlDisabled} + error={fetchError} + icon="step-forward" + /> +
    - +
    + ) diff --git a/src/components/library/artist/List.jsx b/src/components/library/artist/List.jsx index 32b2f16e..b8739367 100644 --- a/src/components/library/artist/List.jsx +++ b/src/components/library/artist/List.jsx @@ -46,9 +46,11 @@ export default function ArtistList() { example: 'artist', }} /> - - {libraryEntryArtistList} - + state.library.song.data.query) const responseOfAddSong = useSelector( (state) => state.alterationsResponse.multiple.addSongToPlaylist?.[song.id] @@ -169,7 +169,7 @@ export default function SongEntry({ song, karaokeRemainingSeconds }) { ) const notifications = [ - , - {isExpanded ? ( diff --git a/src/components/library/song/List.jsx b/src/components/library/song/List.jsx index 320149b8..f7e2fad5 100644 --- a/src/components/library/song/List.jsx +++ b/src/components/library/song/List.jsx @@ -66,9 +66,11 @@ export default function SongList() { withHash: true, }} /> - - {libraryEntrySongList} - + @@ -26,4 +30,5 @@ export default function ExceedsKaraStopTime({ expanded }) { ExceedsKaraStopTime.propTypes = { expanded: PropTypes.bool, + className: PropTypes.string, } diff --git a/src/components/library/status/InPlaylist.jsx b/src/components/library/status/InPlaylist.jsx index 73fa260e..4b875d43 100644 --- a/src/components/library/status/InPlaylist.jsx +++ b/src/components/library/status/InPlaylist.jsx @@ -64,6 +64,7 @@ export default function InPlaylist({ playingEntries, queuingEntries, expanded, + className, }) { const playerStatus = useSelector((state) => state.playlist.playerStatus.data) const { entry, position } = getMostPertinentEntry( @@ -144,7 +145,7 @@ export default function InPlaylist({ return (
  • @@ -159,4 +160,5 @@ InPlaylist.propTypes = { playingEntries: PropTypes.arrayOf(playlistEntryPropType), queuingEntries: PropTypes.arrayOf(playlistEntryPropType), expanded: PropTypes.bool, + className: PropTypes.string, } diff --git a/src/components/library/status/MaskedByTag.jsx b/src/components/library/status/MaskedByTag.jsx index 3c18d82d..158ae48d 100644 --- a/src/components/library/status/MaskedByTag.jsx +++ b/src/components/library/status/MaskedByTag.jsx @@ -1,7 +1,7 @@ import classNames from 'classnames' import PropTypes from 'prop-types' -export default function MaskedByTag({ expanded }) { +export default function MaskedByTag({ expanded, className }) { let message if (expanded) { message = ( @@ -12,9 +12,13 @@ export default function MaskedByTag({ expanded }) { } return (
  • @@ -26,4 +30,5 @@ export default function MaskedByTag({ expanded }) { MaskedByTag.propTypes = { expanded: PropTypes.bool, + className: PropTypes.string, } diff --git a/src/components/library/widgets/SongExpanded.jsx b/src/components/library/widgets/SongExpanded.jsx index 209b85f4..6c59e926 100644 --- a/src/components/library/widgets/SongExpanded.jsx +++ b/src/components/library/widgets/SongExpanded.jsx @@ -13,7 +13,7 @@ import { import HighlighterQuery from 'components/generics/HighlighterQuery' import { ListingEntry } from 'components/generics/listing/Entry' import ListingList from 'components/generics/listing/List' -import Notification from 'components/generics/Notification' +import NotificationBar from 'components/generics/NotificationBar' import SongTagList from 'components/library/SongTagList' import ArtistWidget from 'components/library/widgets/Artist' import WorkLinkWidget from 'components/library/widgets/WorkLink' @@ -93,9 +93,7 @@ export default function SongExpanded({ query, song }) { name={worksList.length > 1 ? workType.name_plural : workType.name} key={workTypeKey} > - - {worksList} - + ) }) @@ -129,9 +127,7 @@ export default function SongExpanded({ query, song }) { icon="la-microphone-alt" name={song.artists.length > 1 ? 'Artists' : 'Artist'} > - - {artistsList} - + ) } @@ -167,7 +163,7 @@ export default function SongExpanded({ query, song }) { let lyrics if (song.lyrics_preview) { const lyricsNotification = ( - + ) diff --git a/src/components/library/work/List.jsx b/src/components/library/work/List.jsx index 65dc5c1e..adacd199 100644 --- a/src/components/library/work/List.jsx +++ b/src/components/library/work/List.jsx @@ -84,9 +84,11 @@ export default function WorkList() { example: workType.name.toLowerCase(), }} /> - - {libraryEntryWorkList} - + state.playlist.played.data.query) const playerErrorsDigestState = useSelector( (state) => state.playlist.digest.playerErrors @@ -80,6 +80,7 @@ export default function PlayedEntry({ entry }) { controls={controls} extra={extra} entryExpanded={entryExpanded} + {...rest} > {expanded ? ( - {playedComponent} - + /> state.playlist.playerErrors.data.query) const { @@ -92,6 +92,7 @@ export default function PlayerErrorsEntry({ playerError }) { id={playerError.id} controls={controls} entryExpanded={entryExpanded} + {...rest} > diff --git a/src/components/playlist/playerErrors/List.jsx b/src/components/playlist/playerErrors/List.jsx index c859cdde..6bdf388e 100644 --- a/src/components/playlist/playerErrors/List.jsx +++ b/src/components/playlist/playerErrors/List.jsx @@ -55,11 +55,10 @@ export default function PlayerErrorsList() { }} /> - {errorsList} - + /> state.playlist.queuing.data.query) const queuingEntriesDigest = useSelector( (state) => state.playlist.digest.entries.data.queuingEntries @@ -212,15 +212,8 @@ export default function QueuingEntry({ entry, positions }) { const controlsExpanded = [ - -
    + +
    {!positions.isFirstPage && ( )}
    - +
    - -
    + +
    {reorderIndex > positions.position ? ( )}
    - +
    , controlSearch, ] const notifications = [ - { + dispatch(removeEntryFromPlaylist(entry.id)) }} - > - { - dispatch(removeEntryFromPlaylist(entry.id)) - }} - onCancel={() => { - setConfirmDisplayed(false) - }} - /> - , - , + , - {expanded ? ( - {queuingComponents} - + /> @@ -42,4 +42,5 @@ export default function HasError({ playerError, expanded }) { HasError.propTypes = { expanded: PropTypes.bool, playerError: playerErrorPropType.isRequired, + className: PropTypes.string, } diff --git a/src/components/settings/Tokens.jsx b/src/components/settings/Tokens.jsx index 1fa1ee08..470ee888 100644 --- a/src/components/settings/Tokens.jsx +++ b/src/components/settings/Tokens.jsx @@ -1,6 +1,5 @@ -import { useCallback, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { CSSTransition } from 'react-transition-group' import { createPlayerToken, @@ -9,13 +8,13 @@ import { } from 'actions/playlist' import { revokeToken } from 'actions/token' import ConfirmationBar from 'components/generics/ConfirmationBar' -import Notification from 'components/generics/Notification' +import NotificationBar from 'components/generics/NotificationBar' import TokenWidget from 'components/generics/TokenWidget' +import Collapse from 'components/transitions/Collapse' import { IsLibraryManager } from 'permissions/components/Library' import { IsPlaylistManager } from 'permissions/components/Playlist' import { Status } from 'reducers/alterationsResponse' import { karaokePropType, playerTokenPropType } from 'serverPropTypes/playlist' -import { CSSTransitionLazy } from 'thirdpartyExtensions/ReactTransitionGroup' function PlayerTokenBoxDisplay({ playerToken, karaoke }) { const responseOfRevokePlayerToken = useSelector( @@ -26,14 +25,6 @@ function PlayerTokenBoxDisplay({ playerToken, karaoke }) { const [confirmDisplayed, setConfirmDisplayed] = useState(false) - const displayConfirm = useCallback(() => { - setConfirmDisplayed(true) - }, []) - - const clearConfirm = useCallback(() => { - setConfirmDisplayed(false) - }, []) - return (
    @@ -43,28 +34,25 @@ function PlayerTokenBoxDisplay({ playerToken, karaoke }) {

    - { + dispatch(revokePlayerToken(karaoke.id)) }} - > - { - dispatch(revokePlayerToken(karaoke.id)) - }} - onCancel={clearConfirm} - /> - - + -
    @@ -88,7 +76,7 @@ function PlayerTokenBoxCreate({ karaoke }) {

    Create a token that can be used to authenticate the player.

    - - {keyExists ? ( - - ) : ( - - )} - + +
    + {keyExists ? ( + + ) : ( + + )} +
    +
    ) } else if (playerTokenStatus === Status.failed) { playerTokenBox = ( @@ -188,14 +174,6 @@ export default function Tokens() { const dispatch = useDispatch() - const displayConfirm = useCallback(() => { - setConfirmDisplayed(true) - }, []) - - const clearConfirm = useCallback(() => { - setConfirmDisplayed(false) - }, []) - return (
    @@ -209,30 +187,27 @@ export default function Tokens() {
    - { + dispatch(revokeToken()) }} - > - { - dispatch(revokeToken()) - }} - onCancel={clearConfirm} - /> - - + -
    diff --git a/src/components/settings/songTags/Entry.jsx b/src/components/settings/songTags/Entry.jsx index c6e75c0c..0c4f8ffc 100644 --- a/src/components/settings/songTags/Entry.jsx +++ b/src/components/settings/songTags/Entry.jsx @@ -7,13 +7,13 @@ import { clearAlteration } from 'actions/alterations' import { editSongTag } from 'actions/songTags' import { CheckboxField, FormInline, HueField } from 'components/generics/Form' import HighlighterQuery from 'components/generics/HighlighterQuery' -import Notification, { +import NotificationBar, { NotifiableForTable, -} from 'components/generics/Notification' +} from 'components/generics/NotificationBar' import { Checkmark } from 'components/generics/Shapes' +import Slide from 'components/transitions/Slide' import { Status } from 'reducers/alterationsResponse' import { songTagPropType } from 'serverPropTypes/library' -import { CSSTransitionLazy } from 'thirdpartyExtensions/ReactTransitionGroup' export default function SongTagsEntry({ tag, editable }) { const query = useSelector((state) => state.settings.songTags.data.query) @@ -124,7 +124,7 @@ export default function SongTagsEntry({ tag, editable }) { ) const colorForm = ( -
    +
    - - - - {colorForm} - + {colorForm} diff --git a/src/components/settings/users/Entry.jsx b/src/components/settings/users/Entry.jsx index f3362f11..33ce8008 100644 --- a/src/components/settings/users/Entry.jsx +++ b/src/components/settings/users/Entry.jsx @@ -6,14 +6,13 @@ import { clearAlteration } from 'actions/alterations' import { deleteUser } from 'actions/users' import ConfirmationBar from 'components/generics/ConfirmationBar' import HighlighterQuery from 'components/generics/HighlighterQuery' -import Notification, { +import NotificationBar, { NotifiableForTable, -} from 'components/generics/Notification' +} from 'components/generics/NotificationBar' import PermissionText from 'components/generics/PermissionText' import { Checkmark } from 'components/generics/Shapes' import { IsNotSelf, IsUsersManager } from 'permissions/components/Users' import { userPropType } from 'serverPropTypes/users' -import { CSSTransitionLazy } from 'thirdpartyExtensions/ReactTransitionGroup' export default function UsersEntry({ user }) { const query = useSelector((state) => state.settings.users.list.data.query) @@ -39,24 +38,14 @@ export default function UsersEntry({ user }) { - { + dispatch(deleteUser(user.id)) }} - > - { - dispatch(deleteUser(user.id)) - }} - onCancel={() => { - setConfirmDisplayed(false) - }} - /> - - + + {children} + + ) +} + +Collapse.propTypes = { + in: PropTypes.bool, + duration: PropTypes.number, + horizontal: PropTypes.bool, + force: PropTypes.bool, + children: PropTypes.node, +} diff --git a/src/components/transitions/Slide.jsx b/src/components/transitions/Slide.jsx new file mode 100644 index 00000000..e7074f0b --- /dev/null +++ b/src/components/transitions/Slide.jsx @@ -0,0 +1,21 @@ +import PropTypes from 'prop-types' +import { CSSTransition } from 'react-transitioning' + +export default function Slide({ + in: inProp, + duration = 300, + children, + ...rest +}) { + return ( + + {children} + + ) +} + +Slide.propTypes = { + in: PropTypes.bool, + duration: PropTypes.number, + children: PropTypes.node, +} diff --git a/src/components/transitions/Swipe.jsx b/src/components/transitions/Swipe.jsx new file mode 100644 index 00000000..e7ce2163 --- /dev/null +++ b/src/components/transitions/Swipe.jsx @@ -0,0 +1,21 @@ +import PropTypes from 'prop-types' +import { CSSTransition } from 'react-transitioning' + +export default function Swipe({ + in: inProp, + duration = 300, + children, + ...rest +}) { + return ( + + {children} + + ) +} + +Swipe.propTypes = { + in: PropTypes.bool, + duration: PropTypes.number, + children: PropTypes.node, +} diff --git a/src/contexts/listing.js b/src/contexts/listing.js new file mode 100644 index 00000000..25dc1ccd --- /dev/null +++ b/src/contexts/listing.js @@ -0,0 +1,3 @@ +import { createContext } from 'react' + +export const ListingNoTransitionContext = createContext(false) diff --git a/src/style/abstracts/_shapes.scss b/src/style/abstracts/_shapes.scss index 3f71fdc4..1532b488 100644 --- a/src/style/abstracts/_shapes.scss +++ b/src/style/abstracts/_shapes.scss @@ -10,6 +10,7 @@ @include preboot.square($width); transform: translateX(-$width * 0.5) translateY(-$width * 0.5); + z-index: 100; } @mixin make-checkmark($background) { diff --git a/src/style/abstracts/_transitions.scss b/src/style/abstracts/_transitions.scss new file mode 100644 index 00000000..ac193bd5 --- /dev/null +++ b/src/style/abstracts/_transitions.scss @@ -0,0 +1,86 @@ +// +// Dakara Project +// +// Transitions style file +// + +// helper for creating transition styles +// @param $name name of the transitiion +// @param $property property to transition +// @param $from value of the property before entered, and after exiting if $away is not set +// @param $to value of the property after entered and before exiting +// @param $away value of the property after exiting +// @param $duration-enter timing of the entering step +// @param $duration-exit timing of the exiting step +// @param $enter enable enter step +// @param $enter-active enable enterActive step +// @param $enter-done enable enterDone step +// @param $exit enable exit step +// @param $exit-active enable exitActive step +// @param $exit-done enable exitDone step +@mixin make-transition( + $name, + $property, + $from, + $to, + $away: false, + $duration-enter: 300ms, + $duration-exit: 150ms, + $enter: true, + $enter-active: true, + $enter-done: true, + $exit: true, + $exit-active: true, + $exit-done: true +) { + .transition { + @if ($enter) { + &.#{$name}-appear, + &.#{$name}-enter { + #{$property}: $from; + + @if ($enter-active) { + &.#{$name}-appear-active, + &.#{$name}-enter-active { + #{$property}: $to; + transition: $property $duration-enter ease-out; + } + } + } + } + + @if ($enter-done) { + &.#{$name}-appear-done, + &.#{$name}-enter-done { + #{$property}: $to; + } + } + + @if ($exit) { + &.#{$name}-exit { + #{$property}: $to; + + @if ($exit-active) { + &.#{$name}-exit-active { + @if ($away) { + #{$property}: $away; + } @else { + #{$property}: $from; + } + transition: $property $duration-exit ease-in; + } + } + } + } + + @if ($exit-done) { + &.#{$name}-exit-done { + @if ($away) { + #{$property}: $away; + } @else { + #{$property}: $from; + } + } + } + } +} diff --git a/src/style/components/_index.scss b/src/style/components/_index.scss index 2e383a42..b33a1824 100644 --- a/src/style/components/_index.scss +++ b/src/style/components/_index.scss @@ -19,4 +19,5 @@ @use 'navigation'; @use 'playlist'; @use 'settings'; +@use 'transitions'; @use 'user'; diff --git a/src/style/components/generics/_details.scss b/src/style/components/generics/_details.scss index 9f736884..d55db4c5 100644 --- a/src/style/components/generics/_details.scss +++ b/src/style/components/generics/_details.scss @@ -108,24 +108,23 @@ > .border { flex-grow: 1; - // hide after about 4 lines - max-height: 4.6em; - overflow: hidden; - - &.reveal-enter-active { - max-height: 10 * $height; - transition: max-height 600ms ease-out; - .paragraph { - -webkit-line-clamp: none; + &.transition { + &.collapse-vertical-enter:not(.collapse-vertical-enter-active), + &.collapse-vertical-exit-done { + // hide after about 4 lines + max-height: 4.6em; } - } - &.reveal-enter-done { - max-height: unset; + &.collapse-vertical-enter-active { + max-height: 15 * $height; + } - .paragraph { - -webkit-line-clamp: none; + &.collapse-vertical-enter-active, + &.collapse-vertical-enter-done { + .paragraph { + -webkit-line-clamp: none; + } } } } diff --git a/src/style/components/generics/_form.scss b/src/style/components/generics/_form.scss index b76918a8..ee08c5cb 100644 --- a/src/style/components/generics/_form.scss +++ b/src/style/components/generics/_form.scss @@ -158,24 +158,10 @@ $form-field-height: sizes.$row-height; @include sizes.make-vertical-row-padding(horizontal); } - &.error-container-enter { - max-height: 0; - overflow-y: hidden; - - &.error-container-enter-active { + &.transitiion { + &.collapse-vertical-enter-active, + &.collapse-vertical-exit:not(.collapse-vertical-exit-active) { max-height: calc(1em + #{sizes.$gap-vertical} * 2); - transition: max-height 300ms ease-out; - } - } - - // notification transition disappearance - &.error-container-exit { - max-height: calc(1em + #{sizes.$gap-vertical} * 2); - - &.error-container-exit-active { - max-height: 0; - overflow-y: hidden; - transition: max-height 100ms ease-out; } } } diff --git a/src/style/components/generics/_index.scss b/src/style/components/generics/_index.scss index 9d8b69e2..0c783967 100644 --- a/src/style/components/generics/_index.scss +++ b/src/style/components/generics/_index.scss @@ -9,7 +9,7 @@ @use 'form'; @use 'listing'; @use 'navigator'; -@use 'notification'; +@use 'notification_bar'; @use 'permission_text'; @use 'searchbox'; @use 'shapes'; diff --git a/src/style/components/generics/_notification.scss b/src/style/components/generics/_notification_bar.scss similarity index 78% rename from src/style/components/generics/_notification.scss rename to src/style/components/generics/_notification_bar.scss index e7e3e3a9..2b41f628 100644 --- a/src/style/components/generics/_notification.scss +++ b/src/style/components/generics/_notification_bar.scss @@ -36,28 +36,7 @@ position: absolute; top: 0; width: 100%; - - // notification transition appearance - &.notified-appear, - &.notified-enter { - left: 100%; - - &.notified-appear-active, - &.notified-enter-active { - left: 0; - transition: left 300ms ease-out; - } - } - - // notification transition disappearance - &.notified-exit { - left: 0; - - &.notified-exit-active { - left: 100%; - transition: left 150ms ease-out; - } - } + z-index: 1000; .notification { display: flex; diff --git a/src/style/components/generics/_searchbox.scss b/src/style/components/generics/_searchbox.scss index c7a4a35e..1d2f1603 100644 --- a/src/style/components/generics/_searchbox.scss +++ b/src/style/components/generics/_searchbox.scss @@ -27,26 +27,17 @@ } } - .help { - &.help-enter { - max-height: 0; - overflow: hidden; + .transition { + margin: 0; - &.help-enter-active { - max-height: (3 * sizes.$row-height); - transition: max-height 300ms ease-out; - } + &.collapse-vertical-enter.collapse-vertical-enter-active, + &.collapse-vertical-exit:not(.collapse-vertical-exit-active) { + max-height: 4 * sizes.$row-height; } + } - &.help-exit { - max-height: (3 * sizes.$row-height); - overflow: hidden; - - &.help-exit-active { - max-height: 0; - transition: max-height 150ms ease-out; - } - } + .help { + @include sizes.make-gap(padding, top); q { &::before, diff --git a/src/style/components/generics/listing/_listing.scss b/src/style/components/generics/listing/_listing.scss index 021c19dc..16aea4f6 100644 --- a/src/style/components/generics/listing/_listing.scss +++ b/src/style/components/generics/listing/_listing.scss @@ -88,48 +88,12 @@ $listing-entry-control-icon-size: sizes.$row-icon-font-size; } } - // appearance transition - &.add-remove-enter { - max-height: 0; - min-height: initial; - overflow: hidden; - - &.add-remove-enter-active { - max-height: $listing-entry-height; - transition: max-height 300ms ease-out; - } - } - - // disappearance transition - &.add-remove-exit { - max-height: $listing-entry-height; - min-height: initial; - overflow: hidden; - - &.expanded { - max-height: 15 * sizes.$subrow-height; - } - - &.add-remove-exit-active { - max-height: 0; - transition: max-height 150ms ease-in; - - &.expanded { - transition: max-height 300ms ease-in; - } - - &.delayed { - transition-delay: 500ms; - } - } - } - .extra { // remove list left padding padding: 0; } - > .one-line { + .listing-entry-compact { display: flex; .expander { @@ -176,53 +140,34 @@ $listing-entry-control-icon-size: sizes.$row-icon-font-size; position: absolute; right: 0; } - - // appearance transition - .add-remove-enter { - max-width: 0; - overflow: hidden; - - &.add-remove-enter-active { - max-width: $listing-entry-height; - transition: max-width 300ms ease-out; - } - } - - // disappearance transition - .add-remove-exit { - max-width: $listing-entry-height; - overflow: hidden; - - &.add-remove-exit-active { - max-width: 0; - transition: max-width 150ms ease-in; - } - } } } - > .expanded { - @include sizes.make-gap(padding, vertical); - - &.expand-collapse-enter { - max-height: 0; - overflow: hidden; - - &.expand-collapse-enter-active { - max-height: 15 * sizes.$subrow-height; - transition: max-height 600ms ease-out; + // transition for the entry + &.transition { + &.expanded { + &.collapse-vertical-exit:not(.collapse-vertical-exit-active) { + max-height: 10 * sizes.$subrow-height; } } - &.expand-collapse-exit { - max-height: 15 * sizes.$subrow-height; - overflow: hidden; + &.collapse-vertical-appear, + &.collapse-vertical-enter, + &.collapse-vertical-exit { + min-height: unset; + } + } - &.expand-collapse-exit-active { - max-height: 0; - transition: max-height 300ms ease-in; - } + // transition for the expanded entry + .transition { + &.collapse-vertical-enter.collapse-vertical-enter-active, + &.collapse-vertical-exit:not(.collapse-vertical-exit-active) { + max-height: 10 * sizes.$subrow-height; } + } + + .listing-entry-expanded { + @include sizes.make-gap(padding, vertical); // space between every element & > * + * { diff --git a/src/style/components/karaoke/player/_manage_button.scss b/src/style/components/karaoke/player/_manage_button.scss index 1768092f..6067cf60 100644 --- a/src/style/components/karaoke/player/_manage_button.scss +++ b/src/style/components/karaoke/player/_manage_button.scss @@ -2,8 +2,9 @@ overflow: hidden; position: relative; - &.managed-error .managed.managed-enter { - left: -100%; + // transition for error + &.managed-error .transition.swipe-enter { + left: 100%; } .managed { @@ -15,25 +16,5 @@ position: absolute; top: 0; width: 100%; - - // appearance transition - &.managed-enter { - left: 100%; - - &.managed-enter-active { - left: 0; - transition: left 150ms ease-out; - } - } - - // disappearance transition - &.managed-exit { - left: 0; - - &.managed-exit-active { - left: -100%; - transition: left 150ms ease-out; - } - } } } diff --git a/src/style/components/karaoke/player/_notification.scss b/src/style/components/karaoke/player/_notification.scss index 2ebc836b..5c036133 100644 --- a/src/style/components/karaoke/player/_notification.scss +++ b/src/style/components/karaoke/player/_notification.scss @@ -8,23 +8,10 @@ .player-notification { .player-notified { - &.player-notified-enter { - max-height: 0; - overflow-y: hidden; - - &.player-notified-enter-active { + &.transition { + &.collapse-vertical-enter-active, + &.collapse-vertical-exit:not(.collapse-vertical-exit-active) { max-height: 3em; - transition: max-height 300ms ease-out; - } - } - - &.player-notified-exit { - max-height: 3em; - - &.player-notified-exit-active { - max-height: 0; - overflow-y: hidden; - transition: max-height 150ms ease-in; } } } diff --git a/src/style/components/karaoke/player/_player.scss b/src/style/components/karaoke/player/_player.scss index c723fc47..b2cc446b 100644 --- a/src/style/components/karaoke/player/_player.scss +++ b/src/style/components/karaoke/player/_player.scss @@ -36,6 +36,18 @@ $player-progressbar-height: 0.4rem; } } + .transition { + &.collapse-vertical-enter.collapse-vertical-enter-active, + &.collapse-vertical-exit:not(.collapse-vertical-exit-active) { + max-height: $player-controls-height + sizes.$gap-vertical; + + @include support.make-smartphone { + max-height: $player-controls-height-smartphone + + sizes.$gap-vertical-smartphone; + } + } + } + // `controls` class: buttons to send commands to the player. .controls { @include sizes.make-gap(margin, bottom); @@ -52,36 +64,6 @@ $player-progressbar-height: 0.4rem; display: flex; justify-content: flex-start; } - - &.expand-enter { - max-height: 0; - overflow-y: hidden; - - &.expand-enter-active { - max-height: $player-controls-height + sizes.$gap-vertical; - transition: max-height 300ms ease-out; - - @include support.make-smartphone { - max-height: $player-controls-height-smartphone + - sizes.$gap-vertical-smartphone; - } - } - } - - &.expand-exit { - max-height: $player-controls-height + sizes.$gap-vertical; - overflow-y: hidden; - - @include support.make-smartphone { - max-height: $player-controls-height-smartphone + - sizes.$gap-vertical-smartphone; - } - - &.expand-exit-active { - max-height: 0; - transition: max-height 150ms ease-in; - } - } } // `progressbar` class: visual indicator of the song progression. diff --git a/src/style/components/library/_artist.scss b/src/style/components/library/_artist.scss index bfaa665b..4dc73cbb 100644 --- a/src/style/components/library/_artist.scss +++ b/src/style/components/library/_artist.scss @@ -7,7 +7,7 @@ @use '~/abstracts/sizes'; #artist-library { - .one-line { + .listing-entry-compact { .main { .artist-widget { flex-grow: 1; diff --git a/src/style/components/library/_song.scss b/src/style/components/library/_song.scss index f171b6f1..e0e920a4 100644 --- a/src/style/components/library/_song.scss +++ b/src/style/components/library/_song.scss @@ -20,7 +20,7 @@ // This list entry is not hoverizable, in order to make only the compact view // react to hover. #song-library { - .one-line { + .listing-entry-compact { .main { position: relative; diff --git a/src/style/components/library/_work.scss b/src/style/components/library/_work.scss index f0dbbd8b..519f90a5 100644 --- a/src/style/components/library/_work.scss +++ b/src/style/components/library/_work.scss @@ -7,7 +7,7 @@ @use '~/abstracts/sizes'; #work-library { - .one-line { + .listing-entry-compact { .main { .work-widget { flex-grow: 1; diff --git a/src/style/components/playlist/_queuing.scss b/src/style/components/playlist/_queuing.scss index 617f5593..1389b395 100644 --- a/src/style/components/playlist/_queuing.scss +++ b/src/style/components/playlist/_queuing.scss @@ -13,25 +13,10 @@ gap: inherit; overflow: hidden; - // display transition appearance - &.show-hide-appear, - &.show-hide-enter { - max-width: 0; - - &.show-hide-appear-active, - &.show-hide-enter-active { - max-width: 3 * sizes.$row-height; - transition: max-width 300ms ease-out; - } - } - - // display transition disappearance - &.show-hide-exit { - max-width: 3 * sizes.$row-height; - - &.show-hide-exit-active { - max-width: 0; - transition: max-width 150ms ease-in; + &.transition { + &.collapse-horizontal-enter.collapse-horizontal-enter-active, + &.collapse-horizontal-exit:not(.collapse-horizontal-exit-active) { + max-width: calc(2 * sizes.$row-height + sizes.$gap-horizontal); } } } diff --git a/src/style/components/settings/_tokens.scss b/src/style/components/settings/_tokens.scss index 6e60cac6..569e8bfb 100644 --- a/src/style/components/settings/_tokens.scss +++ b/src/style/components/settings/_tokens.scss @@ -9,31 +9,24 @@ #tokens { .token-box { - // approximate min and max height - $token-player-min-height: calc( - 1.25em + sizes.$row-height + sizes.$gap-vertical - ); - $token-player-max-height: calc( - 3 * sizes.$row-height + 2 * sizes.$gap-vertical - ); - - .token-player-enter { - height: $token-player-min-height; - overflow-y: hidden; - - &.token-player-enter-active { - height: $token-player-max-height; - transition: height 300ms ease-out; + .transition { + // stylelint-disable @stylistic/indentation + &.collapse-force-vertical-enter:not( + .collapse-force-vertical-enter-active + ), + // stylelint-enable @stylistic/indentation + &.collapse-force-vertical-exit-active { + height: calc(1.4em + sizes.$row-height + sizes.$gap-vertical); } - } - .token-player-exit { - height: $token-player-max-height; - overflow-y: hidden; + &.collapse-force-vertical-enter-done, + &.collapse-force-vertical-exit-done { + height: unset; + } - &.token-player-exit-active { - height: $token-player-min-height; - transition: height 150ms ease-out; + &.collapse-force-vertical-enter-active, + &.collapse-force-vertical-exit:not(.collapse-force-vertical-exit-active) { + height: calc(0.15em + 3 * sizes.$row-height + 2 * sizes.$gap-vertical); } } } diff --git a/src/style/components/transitions/_collapse.scss b/src/style/components/transitions/_collapse.scss new file mode 100644 index 00000000..21e6ee5a --- /dev/null +++ b/src/style/components/transitions/_collapse.scss @@ -0,0 +1,45 @@ +@use '~/abstracts/transitions'; +@use '~/abstracts/sizes'; + +@include transitions.make-transition( + 'collapse-vertical', + max-height, + $from: 0, + $to: calc(sizes.$row-height), + $enter-done: false +); + +@include transitions.make-transition( + 'collapse-force-vertical', + height, + $from: 0, + $to: calc(sizes.$row-height), + $enter-done: false +); + +.transition { + &.collapse-vertical-appear, + &.collapse-vertical-enter, + &.collapse-vertical-exit, + &.collapse-force-vertical-appear, + &.collapse-force-vertical-enter, + &.collapse-force-vertical-exit { + overflow-y: hidden; + } +} + +@include transitions.make-transition( + 'collapse-horizontal', + max-width, + $from: 0, + $to: calc(sizes.$row-height), + $enter-done: false +); + +.transition { + &.collapse-horizontal-appear, + &.collapse-horizontal-enter, + &.collapse-horizontal-exit { + overflow-x: hidden; + } +} diff --git a/src/style/components/transitions/_index.scss b/src/style/components/transitions/_index.scss new file mode 100644 index 00000000..ec63ca74 --- /dev/null +++ b/src/style/components/transitions/_index.scss @@ -0,0 +1,3 @@ +@use 'collapse'; +@use 'slide'; +@use 'swipe'; diff --git a/src/style/components/transitions/_slide.scss b/src/style/components/transitions/_slide.scss new file mode 100644 index 00000000..c685cdc6 --- /dev/null +++ b/src/style/components/transitions/_slide.scss @@ -0,0 +1,3 @@ +@use '~/abstracts/transitions'; + +@include transitions.make-transition('slide', left, $from: 100%, $to: 0); diff --git a/src/style/components/transitions/_swipe.scss b/src/style/components/transitions/_swipe.scss new file mode 100644 index 00000000..21e64f6d --- /dev/null +++ b/src/style/components/transitions/_swipe.scss @@ -0,0 +1,11 @@ +@use '~/abstracts/transitions'; + +@include transitions.make-transition( + 'swipe', + left, + $from: -100%, + $to: 0, + $away: 100%, + $duration-enter: 150ms, + $duration-exit: 150ms +); diff --git a/src/thirdpartyExtensions/ReactTransitionGroup.jsx b/src/thirdpartyExtensions/ReactTransitionGroup.jsx deleted file mode 100644 index 5df252ff..00000000 --- a/src/thirdpartyExtensions/ReactTransitionGroup.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types' -import { CSSTransition } from 'react-transition-group' - -/** - * The CSSTransitionLazy component is a sanitized version of CSSTransition - * - * It is aimed to fix its dysfunctionnal behavior related in - * https://github.com/reactjs/react-transition-group/issues/156 - * The problem is that the child component is always visible, either before the - * enter transition starts or after the exit transition finishes. The solution - * is to force the mounting of the children component only when the enter - * transition is bound to start and its unmounting when the exit transition has - * finished, hence resulting in a "lazy" mounting. - * - * The problem is present in the example of the OFFICIAL DOCUMENTATION, which - * makes it plainly NOT WORKING. This demonstrates how STUPID devoloppers are in - * the FUCKING JS COMMUNITY. - */ - -export const CSSTransitionLazy = ({ children, ...props }) => ( - - {children} - -) -CSSTransitionLazy.propTypes = { - children: PropTypes.node, -}