cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - Activehtmlicon-personicon-teamoctocatspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              //- Only truly works in Chrome, but that's
//- the use case I need. Firefox has a rerender
//- that throws off the positioning.
div#app
            
          
!
            
              $color-neutral-light: #b0b5b8
$color-neutral-medium: #545e61


html
  box-sizing: border-box
  background-color: #2d3437
  font-size: 62.5%
  font-family: Roboto, Helvetica, sans-serif
  overflow: hidden
  -webkit-font-smoothing: antialiased
  -moz-osx-font-smoothing: grayscale


  
*, *::before, *::after
  box-sizing: inherit


html, body
  height: 100%

  
.sr-only
  position: absolute
  width: 1px
  height: 1px
  padding: 0
  margin: -1px
  overflow: hidden
  clip: rect(0, 0, 0, 0)
  border: 0
  
  
#app
  display: flex
  flex: 1 1 auto
  flex-direction: row
  flex-wrap: wrap
  justify-content: space-between
  align-items: center
  
  
.morph-button-to-modal-wrapper
  position: relative
  overflow: visible
  top: 80vh
  left: 10vw
  width: 3.2rem
  height: 3.2rem
  

.morph-button-to-modal
  position: relative
  top: 0
  bottom: 0
  overflow: hidden
  min-width: 3.2rem
  min-height: 3.2rem
  width: 3.2rem
  //max-width: 3.2rem
  max-height: 3.2rem
  border: 0.2rem solid #fff
  border-radius: 50%
  background: transparent
  color: #fff
  will-change: content, width, max-width, max-height, transform
  transition-property: width, max-width, max-height, border-color, border-radius, background-color
  transition-duration: 350ms, 350ms, 350ms, 200ms, 200ms, 400ms
  transition-timing-function: cubic-bezier(0.46, 0.03, 0.52, 0.96)
  transition-delay: 100ms, 100ms, 200ms, 400ms, 400ms, 200ms
  //transition-delay: 200ms
    
  &.open
    z-index: 1000
    width: 90rem
    max-width: calc(100vw - 2rem)
    max-height: calc(100vh - 2rem)
    border-color: transparent
    border-radius: 1rem
    background-color: #394144
    transition-delay: 200ms, 200ms, 200ms, 200ms, 200ms, 200ms
    
  button
    appearence: none
    background: none
    border: none
    padding: 0
    
  .morph-button-enter
    opacity: 0.01
    
    &.morph-button-enter-active
      opacity: 1
      transition-property: opacity
      transition-duration: 300ms
      transition-timing-function: ease-out
      transition-delay: 400ms
      
  .morph-button-leave
    opacity: 1
  
    &.morph-button-leave-active
      opacity: 0
      transition: opacity 200ms ease-out
     
  .morph-modal-enter
    opacity: 0.01
    
    &.morph-modal-enter-active
      opacity: 1
      transition-property: opacity 320ms ease-out 500ms
      
  .morph-modal-leave
    opacity: 1
  
    &.morph-modal-leave-active
      opacity: 0
      transition: opacity 200ms ease-out
    
  .morph-button
    position: absolute
    width: 100%
    height: 100%

  .morph-modal-container
    display: flex
    flex-flow: column nowrap
    align-items: stretch
    position: relative
    padding: 2rem
    
  .morph-modal-title
    flex: 0 0 auto
    overflow: hidden
    padding-bottom: 2rem
    border-bottom: 0.1rem solid $color-neutral-medium
    font-size: 2rem
    font-weight: 500
    white-space: nowrap
    text-overflow: ellipsis
    
  .btn-close
    position: absolute
    top: 1.4rem
    right: 2rem
    color: $color-neutral-light
    font-size: 2.4rem
    
  .morph-modal-body
    flex: 0 1 auto
    overflow-y: auto
    max-height: calc(100vh - 14rem)
    font-size: 1.5rem
    
    
    
    
.device-entry-info-modal
  .morph-modal-enter
    .device-details-info
      > *
        border-color: transparent
      
        > *
          opacity: 0.01
    
    &.morph-modal-enter-active
      .device-details-info
        > *
          border-left-color: $color-neutral-medium
          transition: border-color 250ms ease-in 500ms
        
          > *
            opacity: 1
            transition: opacity 300ms ease-in 420ms
            
            @for $i from 1 through 24
              &:nth-child(#{$i})
                transition-delay: ($i * 32) + 420ms
        
    //.morph-modal-leave
    //  &.morph-leave-active
      
  .modal-button
    font-size: 3.2rem
  
  .info-key-value
    display: flex
    justify-content: space-between
    white-space: nowrap
    line-height: 2.5
    
    &, .info-key, .info-value
      margin: 0
    
    .info-key
      flex: 0 1 auto
      overflow: hidden
      padding-right: 1em
      color: #fff
      font-weight: 600
      text-overflow: ellipsis
    
    .info-value
      flex: 0 0 auto
      text-align: right
      color: $color-neutral-light
      
  .device-details-info
    display: flex
    flex-flow: row nowrap
    justify-content: space-between
    width: 100%
    max-width: 100%
    margin: 3rem 0
    
    > *
      flex: 0 1 33.33333%
      max-width: 33.33333%
      padding: 0 3rem
      border-left: 0.1rem solid $color-neutral-medium
    
      &:first-of-type
        border: none
        padding-left: 0
    
      &:last-of-type
        padding-right: 0
    
  .sub.info-key::before
    content: "\21B3"
    margin-right: 0.5em
    font-family: sans-serif
    color: $color-neutral-light
    
  .device-details-info-header
    display: flex
    flex-flow: row nowrap
    align-content: center
    margin-bottom: 4rem
    padding: 2.4rem 0 1.8rem
    border-bottom: 0.1rem solid $color-neutral-medium
    
    .device-image
      margin-right: 2.8rem
      color: #36d3b4
      font-size: 6.4rem
      
    .device-name, .device-online-status
      display: block
      
    .device-name
      font-size: 2.4rem
      font-weight: 500
      
    .device-online-status
      font-style: italic
      
      &::before
        content: "("
      
      &::after
        content: ")"
  
  .device-volume
    display: inline-block
    width: 100%
    break-inside: avoid
      
  // to save time color these
  .device-os-and-connection
    .fa-check
      color: #24E873
      
    .fa-times
      color: #FF005A
      
      
            
          
!
            
              {PropTypes} = React
{button, dd, div, dt, dl, i, li, span, ul} = React.DOM
{CSSTransitionGroup} = React.addons


# Loosely Definining Types:
#
# type alias Volume =
#   { name : String
#   , subdirs : Maybe (List String)  // but not actually wrapped in Maybe
#   }
#
# type alias Coord =
#   { x : Number
#   , y : Number
#   }

# availableVolums : List Volume
availableVolumes = 
  [
    {name: "iPhoto"}
    {name: "LightRoom"}
    {name: "Macintosh HD", subdirs: ["Users/lucius"]}
    {name: "Lucius's Biig Hard Drive Name"}
    {name: "MOVIES"}
    {name: "Music"}
    {name: "OnePlus Two"}
    {name: "NSFW", subdirs: ["latina", "teen", "twins", "snakes"]}
    {name: "Lucius Thumb"}
    {name: "Snakes"}
    {name: "Animu"}
  ]
  

# Button & Title are passed in as a prop, children ar the modal
#
# MorphButtonToModal : ReactClass
MorphButtonToModal = React.createClass {
  name: "MorphButtonToModal"
  
  propTypes: {
    buttonDom: PropTypes.node
    titleDom: PropTypes.node
  }
  
  getInitialState: :getInitialState ->
    {
      isOpen: false
      mainTranslateCoord: {x: 0, y: 0}
    }
    
  #componentWillMount: :componentWillMount ->
  #  return
    
  componentWillUnmount: :componentWillUnmount ->
    window.cancelAnimationFrame @mainTranslateAnim
    window.removeEventListener "resize", @boundResizeHandler, false
    document.removeEventListener "click", @clickOutClose, false
    return
  
  #mainTranslateDuration : Int
  mainTranslateDuration: 600
  
  # coordToTranslate : Coord -> String
  coordToTranslate: :coordToTranslate({x, y}) ->
    "translate(" + x + "px, " + y + "px)"
  
  # quadInOut : Number -> Number -> Number -> Number -> Number
  quadInOut: :quadInOut(time, begin, end, duration) ->
    if (time = time / (duration / 2)) < 1
      (end - begin) / 2 * time * time + begin
    else
      (end - begin) * -1 / 2 * ((time -= 1) * (time - 2) - 1) + begin
     
  mainTranslateBack: :mainTranslateBack ->
    startTime = window.performance.now!
    startX = @state.mainTranslateCoord.x
    startY = @state.mainTranslateCoord.y
    duration = @mainTranslateDuration
    translate = (t) ~>
      deltaTime = window.performance.now! - startTime
      newCoord = {
        x: @quadInOut deltaTime, startX, 0, duration
        y: @quadInOut deltaTime, startY, 0, duration
      }
      @setState {mainTranslateCoord: newCoord}
      if deltaTime < duration
        @mainTranslateAnim = window.requestAnimationFrame translate
    translate!
    return
        
  mainTranslateToCenter: :mainTranslateToCenter ->
    {morphMain} = @refs
    startTime = window.performance.now!
    rect = morphMain.getBoundingClientRect!
    startX = rect.left
    startY = rect.top
    duration = @mainTranslateDuration
    translate = (t) ~>
      deltaTime = window.performance.now! - startTime
      {width, height} = morphMain.getBoundingClientRect!
      newCoord = {
        x: @quadInOut deltaTime, 0, ((window.innerWidth - width) * 0.5) - startX, duration
        y: @quadInOut deltaTime, 0, ((window.innerHeight - height) * 0.5) - startY, duration
      }
      @setState {mainTranslateCoord: newCoord}
      if deltaTime < duration
         @mainTranslateAnim = window.requestAnimationFrame translate
    translate!
    return
    
  resizeHandler: :resizeHandler(event) ->
    {top, left} = @refs.morphWrapper.getBoundingClientRect!
    {width, height} = @refs.morphMain.getBoundingClientRect!
    newCoord = {
      x: ((window.innerWidth - width) * 0.5) - left
      y: ((window.innerHeight - height) * 0.5) - top
    }
    @setState {mainTranslateCoord: newCoord}
    return
    
  toggleClickOutClose: :toggleClickOutClose ->
    if @state.isOpen
      @boundResizeHandler = @resizeHandler.bind @
      @clickOutClose = (event) ~> 
        if not event.target.closest ".morph-modal-container"
          @closeModal!
      window.addEventListener "resize", @boundResizeHandler, false
      document.addEventListener "click", @clickOutClose, false
    else
      window.removeEventListener "resize", @boundResizeHandler, false
      document.removeEventListener "click", @clickOutClose, false
    return
    
  openModal: :openModal(event) ->
    @mainTranslateToCenter!
    @setState {isOpen: true}, @toggleClickOutClose
    return
    
  closeModal: :closeModal(event) ->
    @mainTranslateBack!
    @setState {isOpen: false}, @toggleClickOutClose
    return
    
  # renderButton : () -> React.Element
  renderButton: :renderButton ->
    div {ref: "morphButtonContainer", className: "morph-button-container"},
      button {ref: "morphButton", className: "morph-button", onClick: @openModal}, @props.buttonDom
  
  # renderModal : () -> React.Element
  renderModal: :renderModal ->
    div {ref: "morphModalContainer", className: "morph-modal-container"},
      div {ref: "morphModalTitle", className: "morph-modal-title"},
        @props.titleDom
        button {ref: "morphCloseButton", className: "btn-close", onClick: @closeModal},
          i {ref: "morphCloseButtonIcon", className: "fa fa-times"}
          span {ref: "morphCloseButtonLabel", className: "sr-only"}, "Close"
      div {ref: "morphModalBody", className: "morph-modal-body"},
        @props.children
  
  # render : () -> React.Element
  render: :render ->
    classNames = ["morph-button-to-modal", (if @state.isOpen then "open" else "closed"), @props.className].join " " .trim!
    wrapperClassNames = classNames.split " " .map ((s) -> s + "-wrapper") .join " "
    div {ref: "morphWrapper", className: wrapperClassNames},
      div {ref: "morphMain", key: "morphButtonToModal", className: classNames, style: {transform: @coordToTranslate @state.mainTranslateCoord}},
        React.createElement CSSTransitionGroup, 
          { key                    : "morphButtonTrans"
          , transitionName         : "morph-button"
          , transitionEnterTimeout : 700
          , transitionLeaveTimeout : 200
          },
          if @state.isOpen then null else @renderButton!,
        React.createElement CSSTransitionGroup, 
          { key                    : "morphModalTrans"
          , transitionName         : "morph-modal"
          , transitionEnterTimeout : 1200
          , transitionLeaveTimeout : 200
          },
          if @state.isOpen then @renderModal! else null
}


DeviceInfoButtonModal = React.createClass {
  name: "DeviceInfoButtonModal"
  
  propTypes: {
    volumes: PropTypes.arrayOf PropTypes.shape {
      name: PropTypes.string.isRequired
      subdirs: PropTypes.arrayOf PropTypes.string
    }
  }

  # renderVolume : Int -> Volume -> Int -> React.Element
  renderVolume: :renderVolume(colIdx, vol, idx=0) ->
    div {key: "vol" + colIdx + "." + idx, className: "device-volume"},
      [ dl {key: colIdx + "." + idx, className: "info-key-value"},
        dt {key: "key", className: "info-key"}, vol.name
        dd {key: "value", className: "info-value"}, "Available"
      ].concat if not vol.subdirs or not vol.subdirs.length then [] else vol.subdirs.map (sub, idx) ~>
        dl {key: idx, className: "info-key-value"}, [
          dt {key: "subkey" + idx, className: "sub info-key"}, sub
          dd {key: "subvalue" + idx, className: "sub info-value"}, "Available"
        ]

  # renderVolumes : List Volume -> Int -> React.Element
  renderVolumes: :renderVolumes(volumes, count) ->
    count = count or @this.props.volumes.reduce (acc, vol) ->
      acc + 1 + (if vol.subdirs then vol.subdirs.length else 0)
    , 0
    if count < 7
      div {key: "vol-list-0", className: "device-volume-list", style: {flexBasis: "50%", maxWidth: "50%", paddingLeft: "9rem"}},
        volumes.map @renderVolume.bind @, 0
    else
      splitAt = Math.ceil count * 0.5
      splitVolumes = ([head, ...tail], count_=0, acc=[[], []]) ->
        acc[if count_ < splitAt then 0 else 1].push head
        if tail.length === 0 then acc else splitVolumes tail, count_ + 1 + (if head.subdirs then head.subdirs.length else 0), acc
      splitVolumes(volumes).map (volColumn, idx) ~>
        div {key: "vol-list-" + idx, className: "device-volume-list"},
          volColumn.map @renderVolume.bind @, idx


  # renderDeviceDetailsInfo : () -> React.Element
  renderDeviceDetailsInfo: :renderDeviceDetailsInfo ->
    volCount = @props.volumes.reduce (acc, vol) ->
      acc + 1 + (if vol.subdirs then vol.subdirs.length else 0)
    , 0
    div {className: "device-details-info"}, [
      div {key: "os-conn-0", className: "device-os-and-connection", style: if volCount < 7 then {flexBasis: "50%", maxWidth: "50%", paddingRight: "9rem"} else {}},
        dl {className: "info-key-value"},
          dt {className: "info-key"}, "Operating System"
          dd {className: "info-value"}, "OS X"
        dl {className: "info-key-value"},
          dt {className: "info-key"},
            i {className: "fa fa-check"}, null
            span null, " LAN"
          dd {className: "info-value"}, "192.168.1.1:8111"
        dl {className: "info-key-value"},
          dt {className: "info-key"},
            i {className: "fa fa-times"}, null
            span null, " P2P"
          dd {className: "info-value"}, "none"
        dl {className: "info-key-value"},
          dt {className: "info-key"},
            i {className: "fa fa-check"}, null
            span null, " Relay"
          dd {className: "info-value"}, "toast.al"
      ].concat @renderVolumes availableVolumes, volCount
  
  # renderButtonDom : React.Element
  renderButtonDom: [
    i {key: "buttonIcon", className: "info-icon fa fa-info"}, null
    span {key: "buttonLabel", className: "info-label sr-only"}, "Device Info"
  ]
    
  # renderTitleDom : React.Element
  renderTitleDom: [
    i {key: "titleIcon", className: "title-icon fa fa-desktop", style: {paddingRight: "0.5em"}}, null
    span {key: "titleLabel", className: "title-label"}, "Devices Info"
  ]

  # deviceInfoButtonModal : () -> React.Element
  render: :render ->
    React.createElement MorphButtonToModal, {
      key: "deviceEntryInfoModal"
      className: "device-entry-info-modal"
      buttonDom: @renderButtonDom
      titleDom: @renderTitleDom
    }, div {key: "deviceDetailsInfoContainer", className: "device-details-info-container"},
      div {className: "device-details-info-header"},
        i {className: "device-image fa fa-desktop"}, null
        div {className: "device-label"},
          span {className: "device-name"}, "Lucius Desktop"
          span {className: "device-online-status"}, "Online"
      @renderDeviceDetailsInfo!
}
  
    

# render the guy
ReactDOM.render (React.createElement DeviceInfoButtonModal, {volumes: availableVolumes}), document.getElementById "app"
            
          
!
999px
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Loading ..................

Console