Giter VIP home page Giter VIP logo

purescript-lumi-components's Introduction

purescript-lumi-components Build Status

This is a component library focused on Lumi's specific UI and UX needs. Available components are found in src/components.

Goals and Roadmap

See ROADMAP.md for more info.

Installation

bower i -S purescript-lumi-components

To use the styles that come with these components the CSS needs to be injected into the page. The easiest way to do this is to run the attachGlobalComponentStyles effect available in Lumi.Components.Styles one time as your application initializes.

You will also need a few npm dependencies. These dependencies and their versions must be compatible with the ones listed here.

Local development

npm i; npx bower i; npx pulp build
npm start

You can run production builds (output minified, static files to build/) using npm run build.

Tagging a new release

pulp version 0.x.y
pulp publish
npm run deploy

Don't use npm version!

Contributing

See CONTRIBUTING.md for more info.

purescript-lumi-components's People

Contributors

megamaddu avatar kimispencer avatar arthurxavierx avatar spicydonuts avatar hdgarrood avatar dependabot[bot] avatar andhong avatar pete-murphy avatar bethanyjoy avatar codedmart avatar jxv avatar mjrussell avatar tippenein avatar jonathanlking avatar

Stargazers

🍃 Greg 'Krait' Hab 🍂 avatar Jacob Alford avatar Sureymi Tumah avatar Adam Harris avatar zhangweijun avatar  avatar Zac Z. avatar Cole Howard avatar Kamil Adam avatar Richard Allaway avatar Maciej Bielecki avatar Ricardo Trejos avatar Kiryl Valkovich avatar Fabien Bourgeois avatar Pavel Glushkov avatar MinhTu Thomas Hoang avatar lzl avatar  avatar Martin Allard avatar Andreas Thoelke avatar Carlos Gómez avatar Lihao Ye avatar Jonas Buntinx avatar Peter Szerzo avatar WJH avatar  avatar Juang Wiantoro avatar Bram Hoendervangers avatar Kurt Milam avatar Rodrigo Navarro avatar Alexey Kozlov avatar Mike McGirr avatar Lily Gilbert-Bland avatar Alex Gryzlov avatar Pascal Hartig avatar Andy White avatar Mesabloo avatar Vesa Hagström avatar Rajat Sharma avatar matoruru avatar  avatar Nick Saunders avatar Matthias Pronk avatar James Brock avatar Motohiro Yakura avatar Dejan Ranisavljevic avatar Brendan Zabarauskas avatar Adrian Rosian avatar dicioccio lucas avatar yangdao avatar Serhii Khoma avatar Josh Miller avatar paluh avatar e_ntyo avatar Vladislav Lagunov avatar  avatar Andy Wang avatar Mark Eibes avatar Jack Pooley avatar Jean-Baptiste Musso avatar Steven Shaw avatar Adelar da Silva Queiróz avatar Kevin L avatar  avatar Leonardo Farroco avatar Rajiv Abraham avatar Georgi Bojinov avatar Steven MacCoun avatar wangb avatar Akira Komamura avatar Simon avatar Elliot Davies avatar Dan Minshew avatar Moremi Vannak avatar Adam Recvlohe avatar Ali Hammad avatar cybai (Haku) avatar  avatar Wasif Baig avatar Paul Young avatar Kamil Shakirov avatar Florian Le Frioux avatar Josh Burgess avatar Petri Kola avatar Milia avatar Daniel Harvey avatar  avatar Heneli Kailahi avatar Simon Gardner avatar Hendrik Niemann avatar  avatar Rashad Gover avatar Sean Yu avatar Stéphane Le Dorze avatar Mattia Manzati avatar Anupam <|> अनुपम avatar Steph Ango avatar Tim Kersey avatar  avatar Vasiliy Yorkin avatar

Watchers

Abhik Khanra avatar  avatar Stéphane Le Dorze avatar  avatar  avatar James Cloos avatar  avatar  avatar  avatar  avatar  avatar

purescript-lumi-components's Issues

Border/container component

A few existing components (slat, ..) and some proposed ones (accordion) share the same basic rounded border. We should pull this out into a simple reusable container component.

(Possibly separate) components for containers with headers and footers might also be useful.

Distinguish between "simple" and "complex" forms

Currently there is no way of restricting the type of forms allowed inside certain compound FormBuilders (e.g. indent, array, editableTable see #1).

It would be interesting to not allow indented forms inside some specific form builders.

Improvements to when validation messages are displayed

At the moment, validation messages are displayed immediately, as soon as the form state for a validated form is modified:

Peek.2021-04-08.10-49.mp4

This is not ideal UX-wise - it feels a bit pushy of the form to start whining at you before you've even finished typing in the field. I think it would be preferable to wait until either the field loses focus or when the user attempts to submit the form to display these validation messages.

Another problem is that if I try to submit a form without filling in any required fields, validation errors are usually not shown. Again this is because we aren't setting validated form state fields to Modified when trying to submit the form, only when the form state for that field is changed.

These are separate issues but I think they might want tackling together; at least, we should probably ensure that trying to submit an invalid form cause any validation errors to be shown before we attempt to delay showing validation errors in form elements which have been modified until those elements lose focus, because in that case, if a "have I lost focus" flag doesn't get set properly for whatever reason then a validation error message might never appear at all.

Unfortunately there's probably no good non-breaking way of having attempted submission display validation errors - form submission is usually handled outside the form, via revalidate, which is pure and therefore doesn't provide the opportunity of modifying form state. Perhaps build could return not only a JSX, but a { trySubmit :: Effect (Maybe result), jsx :: JSX }, where trySubmit is defined as:

trySubmit = do
  props.onChange setModified
  pure (revalidate editor props props.value)

and then we might stop re-exporting revalidate from Lumi.Components.Form and make it only available from Form.Internal, and also add a warning in the docs that you should probably be using trySubmit instead.

We probably shouldn't use the "JSS" top level module name

... in a library which isn't called purescript-jss. Can we move it to Lumi.Components.JSS or something instead? It's necessarily a breaking change, which is a bit inconvenient, but I think there's a good chance this will collide with another library at some point if we leave it.

Default fixes

  • revert Box default of min-width: min-content (use min-width: auto instead)
  • Button loading has a loader with position: absolute causing bugs (without position: relative off the parent)

Image collection component

The image component is useful for visually summarizing one domain object, but summarizing a collection using a collection of images (user avatars, etc.) would also be very useful.

The ability to add to or remove from a collection using such a component might be useful too.

The hosted form example is not rendering.

The hosted form example is not rendering.

Render Error
Cannot read property 'constructor' of undefined
TypeError: Cannot read property 'constructor' of undefined
    at Object.isNothing (https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:332771)
    at Object.edit (https://lumihq.github.io/purescript-lumi-components/7.581cb57507c7c0eb4284.js:1:38065)
    at Object.edit (https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:319063)
    at Object.edit (https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:256328)
    at Object.edit (https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:836533)
    at Object.edit (https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:319305)
    at Object.edit (https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:319294)
    at Object.edit (https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:319294)
    at https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:814534
    at https://lumihq.github.io/purescript-lumi-components/main.a02721e2be46a1283be8.js:1:603242

    in UserFormExample
    in lumi-column
    in Column
    in lumi-column
    in Column
    in lumi-tab-content
    in lumi-column
    in Column
    in Tabs
    in Example
    in Example (Wrapper)
    in withRouter(Example (Wrapper))
    in lumi-column
    in Column
    in FormExample
    in Unknown
    in n
    in lumi-column
    in Column
    in t
    in t
    in e
    in lumi-column
    in Column
    in $
    in lumi-row
    in Row
    in div
    in X
    in lumi-column
    in Column
    in U
    in t
    in o
    in DragDropContext(o)
    in e
    in z
    in f
    in withRouter(f)
    in t
    in t

Newtype instance for ColorName defeats the purpose of hiding the constructor

Providing a Newtype instance for ColorName means that you're able to wrap and unwrap ColorName values, even though the constructor is not exported:

import Data.Newtype
import Lumi.Components.Color

huh :: String
huh = unwrap colorNames.black

We should probably export the constructor, or remove the Newtype instance.

TypeError for Wizard

Unsure if this has been reported or not, but I'm getting a TypeError when looking at the demo page for v0.22.0. Here's a link.

Remove global CSS

  • the CSS should generate unique class names to prevent possible conflicts with UI elements that aren't a part of this library
  • should be more type safe, both in style definition and the attaching of styles to components

[Pie in the Sky] React Native Compatibility

I'm opening this issue just to get thoughts, so please close it whenever you feel necessary.

I built a (pretty bad) library in a private repo using purescript-run with a Components DSL that allows you to compose components that will run in either React or React Native. It works pretty well, but I was just learning PureScript and React when I wrote it, and it needs to be rethought. Here's an example someone asked me to put together that gives a rough idea of how it works.

When you announced the release of lumi-components, you mentioned that React Native compatibility was something you were interested in pursuing. I'd love to hear your thoughts on how you may achieve this and maybe we can work to get there together? My company is building a consumer facing app. It's releasing as a PWA, but I'd really like to have a native version in the not too distant future, and there will be two of us working on it in a couple of weeks.

Thanks!

Form listen is not observably asynchronous

The listen function in the form code is not observably asynchronous from a UI perspective: https://github.com/lumihq/purescript-lumi-components/blob/1ebe3831bf44130fe007aab8c1e1fdb85fbb578b/src/Lumi/Components/Form/Internal.purs#L257-L268

The recent upgrade of PS and React included updates to this function's docs to indicate this.

Lumi built some internal variants of listen that may be worth upstreaming here. listenWith is observably asynchronous, though has a caveat in regards to its update behavior described in detail in the docs:

type Settings :: Type -> Type
type Settings a =
  { debouncer :: Debouncer (a -> a)
  , buildCallback :: a -> a -> Maybe (Aff (a -> a))
  }

-- | Listens for changes in a form's state and allows for performing debounced
-- | asynchronous effects and additional state changes.
-- |
-- | The asynchronous effect is fired on every state change. If a previous
-- | instance of the effect is already queued and the state changes, the
-- | previous instance of the effect is canceled and replaced with the latest
-- | instance.
-- |
-- | Note that this function requires care to be used safely. To keep the UI
-- | responsive to the user, the wrapped form's UI will be updated immediately
-- | regardless of whether or not the callback builder returns a callback. This
-- | keeps the input field the user is interacting with from feeling laggy. If
-- | the callback builder returns a callback, it will be scheduled for
-- | (debounced) asynchronous execution and when execution completes, the
-- | wrapped form's UI will be updated a second time. The callback builder is
-- | given the previous state and the current state, which enables computing a
-- | diff to determine whether or not an asynchronous response to the state
-- | change is warranted.
-- |
-- | It follows that the user-specified callback builder must only return an
-- | action when it detects a state change in which it would be safe for the
-- | form UI to be updated twice. Concretely, if the callback builder ignored
-- | the specific state change and always blindly returned an action, a change
-- | like adding a row to a table would be duplicated: one row would be added
-- | immediately, and a second row would be added after the asynchronous action
-- | completes.
-- |
-- | An example of a state change where it is generally safe for the form UI to
-- | be updated both immediately and after the asynchronous effect has executed
-- | is when the user is typing into an input field. Both UI-updating effects
-- | observe largely the same core state change that initiated the listener in
-- | the first place: the text in the input. While it is technically possible
-- | for the asynchronous action's returned updater function to change the state
-- | such that the second UI-updating effect does not observe the same text that
-- | was observed by the first UI-updating effect, this would be a useless
-- | listener as the user's input would be changed out from under them as they
-- | were typing. Considering it is exceedingly unlikely for a listener to be
-- | implemented in that way, we can reasonably assume both UI-updating effects
-- | observe roughly the same core state change, and the second UI-updating
-- | effect observes whatever additional changes were made by the asynchronous
-- | action's returned updater. Caveat emptor!
listenWith
  :: forall ui props unvalidated result
   . Settings unvalidated
  -> FormBuilder' ui props unvalidated result
  -> FormBuilder' ui props unvalidated result
listenWith { debouncer, buildCallback} (FormBuilder form) =
  FormBuilder \props state ->
    let { edit, validate } = form props state
     in { edit: \(onChange :: (unvalidated -> unvalidated) -> Effect Unit) ->
            edit \(update :: unvalidated -> unvalidated) -> do
              for_ (buildCallback state $ update state) \getUpdate -> do
                Debouncer.run debouncer
                  { action: do
                      updateFromCallback <- getUpdate
                      pure $ updateFromCallback <<< update
                  , callback: onChange
                  }
              -- N.B. This ensures the UI doesn't lock up while the asynchronous
              -- effect is running. We allow whatever changes the user is making
              -- to go through to the state/UI immediately here, and then whenever
              -- the debounced effect completes, it will also update the state.
              -- See the function's top-level comment for additional detail.
              onChange update
        , validate
        }

Lumi has also used variants of listen that more directly capture its (unintentionally) synchronous behavior. These variants don't use an Aff indirection and so are more easily understood at-a-glance that they are synchronous. interactWith in particular also supports diffing old state and new state to determine if a synchronous response is needed in the first place:

-- | Listens for changes in a form's state and performs additional state changes
-- | synchronously. The user-specified state update function is applied to every
-- | state change made by the wrapped form.
-- |
-- | If effectful behavior or the ability to diff the old state and current
-- | state is needed, see `interactWith`.
interact
  :: forall ui props unvalidated result
   . (unvalidated -> unvalidated)
  -> FormBuilder' ui props unvalidated result
  -> FormBuilder' ui props unvalidated result
interact update = interactWith \_oldState _newState -> pure update

-- | Listens for changes in a form's state and allows for performing synchronous
-- | effects and additional state changes.
-- |
-- | The synchronous effect is run on every state change and so it must return
-- | promptly. Using the effect to change the form's state, setting some
-- | separate React state, etc. are appropriate but making network calls should
-- | be avoided. The user-specified function to build the effect is given the
-- | previous state and the current state, which enables computing a diff to
-- | determine whether or not a synchronous response to the state change is
-- | warranted. If no response is necessary or if only the response's side
-- | effecting is meaningful, the effect should return `identity` so that the
-- | form's state goes unmodified.
-- |
-- | If no effectful/diffing behavior is needed (e.g. all that's needed is a
-- | pure state update), see the simpler `interact` function.
interactWith
  :: forall ui props unvalidated result
   . (unvalidated -> unvalidated -> Effect (unvalidated -> unvalidated))
  -> FormBuilder' ui props unvalidated result
  -> FormBuilder' ui props unvalidated result
interactWith buildSynchronousCallback (FormBuilder form) =
  FormBuilder \props state ->
    let { edit, validate } = form props state
     in { edit: \(onChange :: (unvalidated -> unvalidated) -> Effect Unit) ->
            edit \(update :: unvalidated -> unvalidated) -> do
              callback <- buildSynchronousCallback state $ update state
              onChange $ callback <<< update
        , validate
        }

While listenWith and interact* are general-purpose functions, it's not clear if they're so general-purpose that its warranted to include them in purescript-lumi-components. I'm including them here in full though in case there is interest from the community to push them into the repo. Additionally, a case could be made for listen either being removed or its implementation replaced to something much closer to interactWith (no Aff).

The debouncer code is available in a gist here, and should likely also be folded into this repo if there's interest in listenWith itself making it into the repo.

Unexpected token HashRouter.

I followed the steps for local development from the readme and saw the following error.

> npm i
> npx bower i
> npx pulp build
> npm start
...
* Building project in /.../purescript-lumi-components
ℹ 「wds」: Project is running at http://0.0.0.0:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /.../purescript-lumi-components
✖ 「wdm」:    33 modules

ERROR in ./docs/index.jsx 8:2
Module parse failed: Unexpected token (8:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.
See https://webpack.js.org/concepts#loaders
|
| ReactDOM.render(
>   <HashRouter>
|     <ScrollManager>
|       <App />
ℹ 「wdm」: Failed to compile.
           Src   Lib   All
Warnings   0     0     0
Errors     0     0     0
* Build successful.

Allow dropdowns to close themselves after certain interactions with their contents

Currently, dropdowns close themselves in a few situations, e.g. if you click outside of them. However, as far as I can tell, there doesn't currently appear to be a way for the parent to signal to a dropdown that it should close itself. If you have a dropdown menu of action buttons, for example, you usually want the dropdown to close itself after one of the action buttons has been clicked on.

Would it make sense to change the content prop of a DropdownButton from JSX to Effect Unit -> JSX, where the argument is an effect which can be invoked by the parent in order to tell the dropdown to close itself? We could also modify the dropdownMenu component to invoke this effect in order to automatically close itself after clicking on any of the menu items.

Reverse direction of PropModifier composition

Current:

lumiElement qrcode
  <<< Border.border
  >>> Border._round
  >>> S.styleModifier_
      ( S.css
          { padding: S.int 16
          , width: S.int 140
          }
      )
  $ identity

Proposed:

lumiElement qrcode
  $ Border.border
  $ Border._round
  $ S.styleModifier_
      ( S.css
          { padding: S.int 16
          , width: S.int 140
          }
      )
  $ identity

Image, cover not contain

Instead of using object-fit: contain, use object-fit: cover re img fitting inside parent Image container

Table inputs

Currently any non checkbox or select input inside of the table is losing it's inherent styles. Due to:

lumi-table table.lumi tbody input.lumi:not([type="checkbox"]), lumi-table table.lumi tbody select.lumi {
border: none;
padding-top: 0;
padding-left: 0;
padding-bottom: 0;
}

setModified is not "deep"

Clicking "submit" in the form example after adding an empty address we get:

deep-validation

Although the address fields fail to validate, no error messages are displayed beside them.

In the Submit case for send, if we replace user = F.setModified state.user with

user = F.setModified $ state.user { addresses = (map F.setModified) <$> state.user.addresses }

then it will also validate the addresses too.

Maybe this is already the desired behaviour? Either way it might be nice to have some variant of setModified that "deeply" traverses the form.

Allow "yes/no" copy for switch component

It looks like the "On"/"Off" text for the switch component is currently set in css. It would be nice to allow changing the copy of this component to be something other than "On/Off"

Remove String props where possible

  • props like variant should be full enums or removed and the component split into separate more specialized components
  • content, child, text, etc, props are generally better as JSX than strings because it's just about as opaque while being flexible enough to allow interesting component composition

Drawer Component

Hey, I like the looks of this library (as I like the looks of everything your team has built!), but the one component missing that I need is a Drawer component, like this one from Material UI. I'm happy to make a PR if this is something you'd all be interested in.

Table UI updates

Currently the Table filter dropdown ("column switcher") pulls in JSX from the table label. If a table column is right-aligned this will reflect in the dropdown. This also applies if we do not want to display a label in the table column header, but do want it to appear in the dropdown menu.

Additionally, the dropdown menu overflow is being cropped off if larger than the table itself.

TODO:

  • explore a visual only representation of the table (no dropdown menu or column sorting, etc actions)
  • explore a TableBuilder DSL
  • investigate a table with inline fields similar to specs
  • add right aligned prop for table header/label
  • add display prop for table header/label
  • resolve dropdown menu overflow within the table
  • add optional display of borders on table rows
  • add ability to have evenly divided column widths
  • improve responsive behavior
  • inputs within a table are losing their border behaviors (overridden by the Table css)

Usage with `spago`?

Is there a living example somewhere that includes this library via packages.dhall?

I saw #167 but lumihq/react-basic-starter referenced by @megamaddu seems to be dead.

Also, some of the dependencies such as generics-rep have been deprecated and I'm not really sure how to go about that.

Start publishing to Pursuit

Now that we have officially announced this library, I think we should start publishing it to Pursuit. I think this ought to be as simple as replacing the git push --follow-tags with pulp publish in the "tagging a new release" section of the readme.

spago package for purescript-lumi-components

Would it be possible to have an easy-to-use spago package for purescript-lumi-components (with no dependencies beyond that)?

I would like to try to use purescript-lumi-components in my project but the dependency management is too complex and I can't get it working currently.

Thanks!

Renaming a Table component doesn't reset its state (i.e. like `key`)

This is rarely a problem because tables tend to be contained by other unique component instances (like pages), but as we move from manual component creation to DSLs and helper functions it's likely we'll end up with two pages with the same component structure. This could prevent React from remounting these components leading to issues with stale component state. For coincidences like these it might be a good idea to have the Table component reset itself when its identifying name changes to prevent mixing up localStorage data.

Update Modal action button

Update the Modal component's action button to reflect these different button states (enabled, disabled & loading).

Add ButtonState

Update Button component to use data ButtonState = Enabled | Disabled | Loading.

keyValueList

Move keyValueList component into purescript-lumi-components

Ability to pass validation state to the input element

I'd like to add the aria invalid attribute to the input component when validation fails. However, when rendering and there's a possible validation error the input has turned into opaque JSX. Is there a way to do this with a custom render function or any other way?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.