diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 473ae31..147e5c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: jobs: build: - name: Test, Bundle, and Dockerise + name: Test, and Build Storybook runs-on: ubuntu-20.04 steps: diff --git a/src/Yoga/Block/Atom/Segmented/Style.purs b/src/Yoga/Block/Atom/Segmented/Style.purs index b12a75b..cfd7a2d 100644 --- a/src/Yoga/Block/Atom/Segmented/Style.purs +++ b/src/Yoga/Block/Atom/Segmented/Style.purs @@ -18,12 +18,13 @@ segmented = styles , margin: _0 , padding: _0 , width: auto + , overflow: visible } activeElement ∷ Style activeElement = css - { position: fixed + { position: absolute , borderRadius: str "8px" , background: str colour.interfaceBackground , border: str $ i "1px solid " colour.interfaceBackgroundShadow @@ -32,7 +33,8 @@ activeElement = , boxShadow: str "0 0 1px rgba(0,0,0,0.55)" , margin: _0 , padding: _0 - , zIndex: str "0" + , zIndex: str "3" + , overflow: visible } container ∷ Style @@ -45,6 +47,7 @@ container = , border: str $ i "1px solid " colour.background15 , borderBottom: str $ i "1px solid " colour.background20 , padding: _0 + , overflow: visible } button ∷ { isFirst ∷ Boolean, isLast ∷ Boolean } -> Style @@ -55,15 +58,18 @@ button { isFirst, isLast } = , color: str colour.text , border: none , margin: _0 - , padding: _0 + , padding: str "var(--s-4)" + , paddingLeft: _0 + , paddingRight: _0 , fontSize: str "var(--s0)" + , overflow: visible , boxSizing: borderBox , zIndex: str "3" , "&:active": nest { outline: str "0" } -- ensures no outline on click in Chrome , "&:focus": nest { outline: none } , "&:focus > .ry-segmented-button__content": nest - { boxShadow: str $ i "0px 0px 0px var(--s-4) " colour.highlight + { border: str $ i "var(--s-4) solid " colour.highlight } } @@ -72,12 +78,16 @@ buttonContent { isFirst, isLast } = css { "&:active": nest { outline: str "0" } -- ensures no outline on click in Chrome , "&:focus": nest { outline: none } - , display: inlineBlock + , top: str "-3px" , padding: str "var(--s-4)" , paddingLeft: str if isFirst then "var(--s0)" else "calc(var(--s0)*0.9)" , paddingRight: str if isLast then "var(--s0)" else "calc(var(--s0)*0.9)" , borderRadius: str "8px" + , boxSizing: borderBox + , border: str $ i "var(--s-4) solid transparent" + , height: _100percent , margin: _0 + , overflow: visible } wrapper ∷ Style @@ -86,4 +96,5 @@ wrapper = { display: flex , alignItems: center , justifyContent: center + , overflow: visible } diff --git a/src/Yoga/Block/Atom/Segmented/View.purs b/src/Yoga/Block/Atom/Segmented/View.purs index a972d73..b5c7204 100644 --- a/src/Yoga/Block/Atom/Segmented/View.purs +++ b/src/Yoga/Block/Atom/Segmented/View.purs @@ -1,8 +1,7 @@ module Yoga.Block.Atom.Segmented.View (component, Props, MandatoryProps, PropsF, ComponentProps) where -import Prelude import Yoga.Prelude.View -import Control.Monad.Maybe.Trans (MaybeT(..), lift, runMaybeT) +import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT) import Control.Monad.Trans.Class (lift) import Data.Array as A import Data.FoldableWithIndex (foldMapWithIndex) @@ -11,7 +10,7 @@ import Data.Traversable (traverse) import Effect.Aff (Milliseconds(..), delay) import Effect.Unsafe (unsafePerformEffect) import Foreign.Object as Object -import Framer.Motion (VariantLabel) +import Framer.Motion (Animate, VariantLabel, Variants) import Framer.Motion as Motion import Hooks.Key as Key import Hooks.UseResize (useResize) @@ -36,7 +35,6 @@ type PropsF f = type MandatoryProps r = ( activeItemRefs ∷ Array (Ref (Nullable Node)) - , previousActiveItemIndex ∷ Int , activeItemIndex ∷ Int | r ) @@ -47,36 +45,45 @@ type Props = activeComponent ∷ ∀ p p_. Union (MandatoryProps p) p_ Props => ReactComponent { | MandatoryProps p } activeComponent = rawActiveComponent +indexToVariant ∷ Int -> VariantLabel +indexToVariant = show >>> unsafeCoerce + rawActiveComponent ∷ ∀ p. ReactComponent { | p } rawActiveComponent = mkForwardRefComponent "SegmentedActive" do \(props ∷ { | Props }) ref -> React.do - maybeVariants /\ setVariants <- useState' Nothing + animationVariants /\ setVariants <- useState' [] let - computeStyles = do - maybeSt <- runMaybeT $ traverse getStyle props.activeItemRefs - unless (maybeVariants == maybeSt) do - for_ maybeSt (setVariants <<< Just) + computeVariants = do + styles <- traverse getStyle props.activeItemRefs + unless (animationVariants == styles) do + setVariants styles # lift useLayoutEffectAlways do - computeStyles - mempty + runMaybeT computeVariants *> mempty let + variants ∷ Variants + variants = + Motion.variants $ animationVariants + # foldMapWithIndex (\i s -> Object.singleton (show i) (css s)) + # unsafeCoerce + animate ∷ Animate + animate = Motion.animate (indexToVariant props.activeItemIndex) clusterProps ∷ { | Cluster.Props } clusterProps = pick props activeElement = - styledLeaf Motion.div - { css: Style.activeElement - , className: "ry-active-segmented-element" - , initial: (unsafeCoerce <<< show) props.previousActiveItemIndex - , transition: Motion.transition { type: "tween", duration: 0.2, ease: "easeOut" } - , variants: - Motion.variants $ maybeVariants - # fromMaybe [] - # foldMapWithIndex (\i s -> Object.singleton (show i) (css s)) - # unsafeCoerce - , animate: Motion.animate ((unsafeCoerce <<< show $ props.activeItemIndex) ∷ VariantLabel) - , _aria: Object.fromHomogeneous { hidden: "true" } - } + guard (animationVariants /= []) + $ styledLeaf Motion.div + { css: Style.activeElement + , className: "ry-active-segmented-element" + , initial: Motion.initial (indexToVariant props.activeItemIndex) + , transition: + Motion.transition + { type: "tween", duration: 0.2, ease: "easeOut" } + , variants + , animate + , _aria: Object.fromHomogeneous { hidden: "true" } + , ref + } children = [ E.element R.div' { className: "ry-segmented" @@ -86,14 +93,12 @@ rawActiveComponent = } ] pure - $ E.element - Cluster.component - ( clusterProps - { children = children - , justify = "center" - , space = "var(--s5)" - } - ) + $ E.element Cluster.component + $ clusterProps + { children = children + , justify = "center" + , space = "var(--s5)" + } getStyle ∷ Ref (Nullable Node) -> @@ -139,7 +144,6 @@ component ∷ ReactComponent ComponentProps component = unsafePerformEffect $ reactComponent "Segmented" \({ buttonContents, activeIndex, updateActiveIndex } ∷ ComponentProps) -> React.do - previousActiveIndex /\ setPreviousActiveIndex <- useState' 0 ------------------------------------------- -- Store button refs for animation purposes itemRefs /\ setItemRefs <- useState' [] @@ -151,9 +155,11 @@ component = -- Support keyboard input let maxIndex = A.length buttonContents - 1 + updateIndex idx = do + updateActiveIndex idx updateTo toIndex = do itemRefs # blurAtIndex activeIndex - updateActiveIndex toIndex + updateIndex toIndex itemRefs # focusAtIndex toIndex useKeyDown case _ of Key.Right -> @@ -174,9 +180,6 @@ component = refs <- traverse (const createRef) buttonContents setItemRefs refs let - updateIndex idx = do - setPreviousActiveIndex activeIndex - updateActiveIndex idx children ∷ Array JSX children = A.mapWithIndex contentToChild refsAndContents refsAndContents ∷ Array ({ id ∷ String, value ∷ String } /\ Ref (Nullable Node)) @@ -215,7 +218,6 @@ component = [ styled activeComponent { activeItemRefs: itemRefs , activeItemIndex: activeIndex - , previousActiveItemIndex: previousActiveIndex , css: Style.container , className: "ry-segmented-container" } diff --git a/src/Yoga/Block/Container/Style.purs b/src/Yoga/Block/Container/Style.purs index 82de104..60151ca 100644 --- a/src/Yoga/Block/Container/Style.purs +++ b/src/Yoga/Block/Container/Style.purs @@ -16,6 +16,8 @@ global = $ css { minHeight: 100.0 # vh , minWidth: 100.0 # vw + , lineHeight: str "1.15" + , "-webkit-text-size-adjust": _100percent } , ":root": nested $ variables @@ -212,8 +214,8 @@ variables = fontVariables ∷ { main ∷ String, mono ∷ String } -> Style fontVariables { main, mono } = css - { "--mainFont": str $ main <> ", monospace" - , "--monoFont": str $ mono <> ", monospace" + { "--mainFont": str $ main <> """, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"""" + , "--monoFont": str $ mono <> ", monospace, monospace" } input ∷ StyleProperty