├── .editorconfig
├── .envrc
├── .eslintignore
├── .eslintrc.yml
├── .gitattributes
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── ROADMAP.md
├── bower.json
├── docs
├── .eslintrc.yml
├── App.jsx
├── AppSetup.purs
├── Example.purs
├── Examples
│ ├── Badge.example.purs
│ ├── Border.example.purs
│ ├── Breadcrumb.example.purs
│ ├── Button.example.purs
│ ├── ButtonGroup.example.purs
│ ├── Card.example.purs
│ ├── CardGrid.example.purs
│ ├── Color.example.purs
│ ├── Column.example.purs
│ ├── Details.example.purs
│ ├── Divider.example.purs
│ ├── DropdownButton.example.purs
│ ├── EditableTable.example.purs
│ ├── FixedPrecisionInput.example.purs
│ ├── Form.example.purs
│ ├── Icon.example.purs
│ ├── Images.example.purs
│ ├── Input.example.purs
│ ├── InputGroup.example.purs
│ ├── LabeledField.example.purs
│ ├── Layouts.example.purs
│ ├── Link.example.purs
│ ├── List.example.purs
│ ├── Loader.example.purs
│ ├── Lockup.example.purs
│ ├── Modal.example.purs
│ ├── NativeSelect.example.purs
│ ├── Navigation.example.purs
│ ├── Pagination.example.purs
│ ├── Pill.example.purs
│ ├── Progress.example.purs
│ ├── Responsive.example.purs
│ ├── Row.example.purs
│ ├── ScrollManager.example.purs
│ ├── Select.example.purs
│ ├── Slider.example.purs
│ ├── Spacing.example.purs
│ ├── StatusSlat.example.purs
│ ├── Svg.example.purs
│ ├── Tab.example.purs
│ ├── Table.example.purs
│ ├── Text.example.purs
│ ├── Textarea.example.purs
│ ├── Toast.example.purs
│ ├── Tooltip.example.purs
│ ├── Upload.example.purs
│ └── Wizard.example.purs
├── Examples2
│ ├── Box.example.purs
│ ├── Button.example.purs
│ ├── ButtonGroup.example.purs
│ ├── Clip.example.purs
│ ├── Image.example.purs
│ ├── Link.example.purs
│ ├── QRCode.example.purs
│ ├── Slat.example.purs
│ └── Text.example.purs
├── MUIColorScheme.purs
├── ScrollManager.jsx
├── Utility
│ ├── ReactRouter.js
│ └── ReactRouter.purs
├── index.html
└── index.jsx
├── flake.lock
├── flake.nix
├── package-lock.json
├── package.json
├── packages.dhall
├── spago.dhall
├── src
├── JSS
│ ├── JSS.js
│ └── JSS.purs
└── Lumi
│ ├── Components.purs
│ ├── Components
│ ├── Badge.purs
│ ├── Border.purs
│ ├── Breadcrumb.purs
│ ├── Button.purs
│ ├── ButtonGroup.purs
│ ├── Card.purs
│ ├── CardGrid.purs
│ ├── Color.purs
│ ├── Column.purs
│ ├── Details.purs
│ ├── Divider.purs
│ ├── DropdownButton.js
│ ├── DropdownButton.purs
│ ├── EditableTable.purs
│ ├── FetchCache.purs
│ ├── FixedPrecisionInput.js
│ ├── FixedPrecisionInput.purs
│ ├── Form.purs
│ ├── Form
│ │ ├── Defaults.purs
│ │ ├── Internal.purs
│ │ ├── Table.purs
│ │ └── Validation.purs
│ ├── Icon.purs
│ ├── Images.purs
│ ├── Input.js
│ ├── Input.purs
│ ├── InputGroup.purs
│ ├── LabeledField.purs
│ ├── Layouts.purs
│ ├── Layouts
│ │ ├── Centered.purs
│ │ ├── OneColumnWithHeader.purs
│ │ └── Tabs.purs
│ ├── Link.purs
│ ├── List.purs
│ ├── Loader.purs
│ ├── Lockup.purs
│ ├── Modal.js
│ ├── Modal.purs
│ ├── NativeSelect.purs
│ ├── Navigation.js
│ ├── Navigation.purs
│ ├── Orientation.purs
│ ├── Pagination.purs
│ ├── Pill.purs
│ ├── Progress.purs
│ ├── Responsive.js
│ ├── Responsive.purs
│ ├── Row.purs
│ ├── Select.purs
│ ├── Select
│ │ └── SelectBackend.purs
│ ├── Size.purs
│ ├── Slider.purs
│ ├── Spacing.purs
│ ├── Status.purs
│ ├── StatusSlat.purs
│ ├── Styles.purs
│ ├── Svg.purs
│ ├── Tab.js
│ ├── Tab.purs
│ ├── Table.js
│ ├── Table.purs
│ ├── Table
│ │ └── FilterDropdown.purs
│ ├── Text.purs
│ ├── Textarea.purs
│ ├── Toast.purs
│ ├── Tooltip.purs
│ ├── Upload.js
│ ├── Upload.purs
│ ├── Wizard.purs
│ └── ZIndex.purs
│ ├── Components2
│ ├── Box.purs
│ ├── Button.purs
│ ├── ButtonGroup.purs
│ ├── Clip.js
│ ├── Clip.purs
│ ├── Image.purs
│ ├── Link.purs
│ ├── PasswordStrength.js
│ ├── PasswordStrength.purs
│ ├── QRCode.js
│ ├── QRCode.purs
│ ├── ScrollObserver.js
│ ├── ScrollObserver.purs
│ ├── Slat.purs
│ ├── Tabs.purs
│ └── Text.purs
│ ├── Styles.purs
│ └── Styles
│ ├── Border.purs
│ ├── Box.purs
│ ├── Clip.purs
│ ├── Link.purs
│ ├── Loader.purs
│ ├── Responsive.purs
│ ├── Slat.purs
│ └── Theme.purs
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # Unix-style newlines with a newline ending every file
4 | [*]
5 | end_of_line = lf
6 | insert_final_newline = true
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [Makefile]
12 | indent_style = tab
13 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | use flake
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | webpack.config.js
2 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | env:
2 | browser: true
3 | commonjs: true
4 | es6: true
5 | node: true
6 | extends:
7 | - eslint:recommended
8 | # - plugin:prettier/recommended
9 | globals:
10 | Atomics: readonly
11 | SharedArrayBuffer: readonly
12 | parserOptions:
13 | ecmaFeatures:
14 | jsx: false
15 | ecmaVersion: 2018
16 | sourceType: module
17 | rules:
18 | strict:
19 | - error
20 | - global
21 | no-unused-vars:
22 | - error
23 | - argsIgnorePattern: ^_
24 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | package-lock.json binary
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules/
5 | /bower_components/
6 | /.psc-package/
7 | .spago
8 |
9 | # testing
10 | /coverage
11 |
12 | # purescript
13 | /output/
14 | /.pulp-cache/
15 | /generated-docs/
16 | /.psc*
17 | /.purs*
18 | /.psa*
19 |
20 | # production build
21 | /build/
22 |
23 | # misc
24 | .DS_Store
25 | .vscode
26 | .env.local
27 | .env.development.local
28 | .env.test.local
29 | .env.production.local
30 | .direnv
31 |
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 | package-lock.json
3 | bower.json
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "trailingComma": "none"
4 | }
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: required
3 | language: node_js
4 | node_js: 10
5 |
6 | install:
7 | - npm install
8 |
9 | script:
10 | - npx bower install
11 | - npm run build
12 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are welcome, particularly for bug fixes or suggestions, but we want to be clear upfront that this library tool specific to Lumi's needs. We may not take contributions we don't have a use for, especially if they add third party dependencies or cause breaking API changes we'll need to account for. Please ask/propose in an issue if you're unsure.
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # purescript-lumi-components [](https://travis-ci.org/lumihq/purescript-lumi-components)
2 |
3 | - [lumihq.github.io/purescript-lumi-components](https://lumihq.github.io/purescript-lumi-components/)
4 | - [purescript-lumi-components on Pursuit](https://pursuit.purescript.org/packages/purescript-lumi-components/)
5 |
6 | This is a component library focused on Lumi's specific UI and UX needs. Available components are found in `src/components`.
7 |
8 | ## Goals and Roadmap
9 |
10 | See [ROADMAP.md](ROADMAP.md) for more info.
11 |
12 | ## Installation
13 |
14 | ```sh
15 | bower i -S purescript-lumi-components
16 | ```
17 |
18 | 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.
19 |
20 | You will also need a few `npm` dependencies. These dependencies and their versions must be compatible with the ones listed [here](https://github.com/lumihq/purescript-lumi-components/blob/master/package.json#L31).
21 |
22 | ## Local development
23 |
24 | ```sh
25 | npm i; npx bower i; npx pulp build
26 | npm start
27 | ```
28 |
29 | You can run production builds (output minified, static files to `build/`) using `npm run build`.
30 |
31 |
32 | ## Tagging a new release
33 |
34 | ```sh
35 | pulp version 0.x.y
36 | pulp publish
37 | npm run deploy
38 | ```
39 |
40 | _Don't use `npm version`!_
41 |
42 | ## Contributing
43 |
44 | See [CONTRIBUTING.md](CONTRIBUTING.md) for more info.
45 |
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Goals and Roadmap
2 |
3 | Lumi Components aims to encapsulate the low level design patterns and UI building blocks used at Lumi. This library can be thought of as a DSL for constructing business-specific components which follow Lumi's style guidelines. This idea will continue to influence the components we create and the way we structure their APIs.
4 |
5 | For example, an `Input` component which just wraps an HTML input and passes through props like `className` and `style` is too low-level. It requires too much knowledge of both CSS and our style rules (such as padding in multiples of 8 pixels) to use. This slows initial development of new pages, clutters up the code, and creates inconsistencies in the UI.
6 |
7 | "But I see those props on many of these components!"
8 |
9 | Yes, this is definitely a work in progress. While this is a rough outline of our goal for a 1.0 release, we're still working out the best ways to accomplish these goals. Short term plans include:
10 |
11 | - remove the use of `Nullable`
12 | - these APIs are a remnant of having ported some of these components from JavaScript
13 | - remove `String` props where possible
14 | - props like `variant` should be full enums or removed and the component split into separate more specialized components
15 | - `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
16 | - remove global CSS
17 | - the CSS should generate unique class names to prevent possible conflicts with UI elements that aren't a part of this library
18 | - should be more type safe, both in style definition and the attaching of styles to components
19 |
20 | Longer term plans may include more DSLs similar to `Form` for use cases such as layout, or the ability to import the component library into Sketch.
21 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "purescript-lumi-components",
3 | "license": [
4 | "Apache-2.0"
5 | ],
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/lumihq/purescript-lumi-components"
9 | },
10 | "ignore": [
11 | "**/.*",
12 | "node_modules",
13 | "bower_components",
14 | "output"
15 | ],
16 | "dependencies": {
17 | "purescript-aff": "^v7.1.0",
18 | "purescript-aff-coroutines": "^v9.0.0",
19 | "purescript-arrays": "^v7.2.1",
20 | "purescript-bifunctors": "^v6.0.0",
21 | "purescript-colors": "^v7.0.1",
22 | "purescript-console": "^v6.0.0",
23 | "purescript-control": "^v6.0.0",
24 | "purescript-coroutines": "^v7.0.0",
25 | "purescript-datetime": "^v6.1.0",
26 | "purescript-effect": "^v4.0.0",
27 | "purescript-either": "^v6.1.0",
28 | "purescript-enums": "^v6.0.1",
29 | "purescript-exceptions": "^v6.0.0",
30 | "purescript-fixed-precision": "^v5.0.0",
31 | "purescript-foldable-traversable": "^v6.0.0",
32 | "purescript-foreign": "^v7.0.0",
33 | "purescript-foreign-object": "^v4.1.0",
34 | "purescript-free": "^v7.0.0",
35 | "purescript-heterogeneous": "^v0.6.0",
36 | "purescript-integers": "^v6.0.0",
37 | "purescript-js-timers": "^v6.1.0",
38 | "purescript-js-uri": "https://github.com/purescript-contrib/purescript-js-uri.git#v3.1.0",
39 | "purescript-maybe": "^v6.0.0",
40 | "purescript-media-types": "^v6.0.0",
41 | "purescript-newtype": "^v5.0.0",
42 | "purescript-nonempty": "^v7.0.0",
43 | "purescript-nullable": "^v6.0.0",
44 | "purescript-numbers": "^v9.0.1",
45 | "purescript-ordered-collections": "^v3.0.0",
46 | "purescript-parallel": "^v6.0.0",
47 | "purescript-partial": "^v4.0.0",
48 | "purescript-prelude": "^v6.0.1",
49 | "purescript-profunctor-lenses": "^v8.0.0",
50 | "purescript-react-basic": "^v17.0.0",
51 | "purescript-react-basic-classic": "https://github.com/lumihq/purescript-react-basic-classic.git#v3.0.0",
52 | "purescript-react-basic-dnd": "https://github.com/lumihq/purescript-react-dnd-basic.git#v10.1.0",
53 | "purescript-react-basic-dom": "https://github.com/lumihq/purescript-react-basic-dom.git#v6.1.0",
54 | "purescript-react-basic-emotion": "^v7.1.0",
55 | "purescript-react-basic-hooks": "^v8.2.0",
56 | "purescript-record": "^v4.0.0",
57 | "purescript-refs": "^v6.0.0",
58 | "purescript-simple-json": "^v9.0.0",
59 | "purescript-st": "^v6.2.0",
60 | "purescript-strings": "^v6.0.1",
61 | "purescript-tailrec": "^v6.1.0",
62 | "purescript-transformers": "^v6.0.0",
63 | "purescript-tuples": "^v7.0.0",
64 | "purescript-unsafe-coerce": "^v6.0.0",
65 | "purescript-unsafe-reference": "^v5.0.0",
66 | "purescript-web-dom": "^v6.0.0",
67 | "purescript-web-events": "^v4.0.0",
68 | "purescript-web-file": "^v4.0.0",
69 | "purescript-web-html": "^v4.1.0",
70 | "purescript-web-storage": "^v5.0.0",
71 | "purescript-web-uievents": "^v4.0.0"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/docs/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | extends:
2 | - plugin:react/recommended
3 | parser: babel-eslint
4 | parserOptions:
5 | sourceType: module
6 | ecmaFeatures:
7 | jsx: true
8 | settings:
9 | react:
10 | version: detect
11 | rules:
12 | react/prop-types: 0
13 |
--------------------------------------------------------------------------------
/docs/AppSetup.purs:
--------------------------------------------------------------------------------
1 | module AppSetup where
2 |
3 | import React.Basic.Classic (JSX)
4 | import React.Basic.ReactDND (dndProvider)
5 | import React.Basic.ReactDND.Backends.HTML5Backend (html5Backend)
6 |
7 | dragDropContext :: JSX -> JSX
8 | dragDropContext = dndProvider html5Backend
9 |
--------------------------------------------------------------------------------
/docs/Examples/Badge.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Badge where
2 |
3 | import Lumi.Components.Badge (badge, badge_)
4 | import Lumi.Components.Color (colors)
5 | import Lumi.Components.Column (column_)
6 | import Lumi.Components.Spacing (Space(..), vspace)
7 | import Lumi.Components.Text (nbsp)
8 | import React.Basic.Classic (JSX)
9 | import React.Basic.DOM as R
10 |
11 | docs :: JSX
12 | docs =
13 | column_
14 | [ badge_ "1"
15 | , vspace S8
16 |
17 | , badge_ "2"
18 | , vspace S8
19 |
20 | , badge_ "3"
21 | , vspace S8
22 |
23 | , badge_ "Hello!"
24 | , vspace S8
25 |
26 | , badge
27 | { background: colors.primary
28 | , color: colors.white
29 | , style: R.css {}
30 | , text: "1"
31 | }
32 | , vspace S8
33 |
34 | , badge
35 | { background: colors.primary
36 | , color: colors.white
37 | , style: R.css {}
38 | , text: nbsp
39 | }
40 | ]
41 |
--------------------------------------------------------------------------------
/docs/Examples/Border.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Border where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Border (borderBottom, borderRound, borderSquare, borderSquareBottom, borderSquareTop, borderTop)
6 | import Lumi.Components.Column (column, column_)
7 | import Lumi.Components.Example (example)
8 | import Lumi.Components.Text (h2_)
9 | import React.Basic.Classic (JSX)
10 | import React.Basic.DOM as R
11 |
12 | docs :: JSX
13 | docs =
14 | column_
15 | [ h2_ "Rounded borders"
16 | , example
17 | $ borderRound $
18 | column
19 | { children: [ R.text "bordered element" ]
20 | , style: R.css {}
21 | }
22 | , h2_ "Square borders"
23 | , example
24 | $ borderSquare $
25 | column
26 | { children: [ R.text "bordered element" ]
27 | , style: R.css {}
28 | }
29 | , h2_ "Square Top borders"
30 | , example
31 | $ borderSquareTop $
32 | column
33 | { children: [ R.text "bordered element" ]
34 | , style: R.css {}
35 | }
36 | , h2_ "Square Bottom borders"
37 | , example
38 | $ borderSquareBottom $
39 | column
40 | { children: [ R.text "bordered element" ]
41 | , style: R.css {}
42 | }
43 | , h2_ "Top borders"
44 | , example
45 | $ borderTop $
46 | column
47 | { children: [ R.text "bordered element" ]
48 | , style: R.css {}
49 | }
50 | , h2_ "Bottom borders"
51 | , example
52 | $ borderBottom $
53 | column
54 | { children: [ R.text "bordered element" ]
55 | , style: R.css {}
56 | }
57 | ]
58 |
--------------------------------------------------------------------------------
/docs/Examples/Breadcrumb.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Breadcrumb where
2 |
3 | import Prelude
4 |
5 | import Data.Array.NonEmpty (cons')
6 | import Data.Maybe (Maybe(..))
7 | import Data.Nullable (toNullable)
8 | import Lumi.Components.Breadcrumb (breadcrumb, compactBreadcrumb)
9 | import Lumi.Components.Column (column_)
10 | import Lumi.Components.Text (h2_)
11 | import Lumi.Components.Example (example)
12 | import React.Basic.Classic (JSX)
13 | import Web.HTML.History (URL(..))
14 |
15 | docs :: JSX
16 | docs =
17 | column_
18 | [ example
19 | $ breadcrumb
20 | { onNavigate: toNullable Nothing
21 | , items: cons'
22 | { label: "Lorem", href: URL "#/input" }
23 | [ { label: "Ipsum", href: URL "#/link" }
24 | , { label: "Dolor", href: URL "" }
25 | ]
26 | }
27 |
28 | , h2_ "Compact variation"
29 | , example
30 | $ compactBreadcrumb
31 | { onNavigate: toNullable Nothing
32 | , items: cons'
33 | { label: "Lorem", href: URL "#/input" }
34 | [ { label: "Ipsum", href: URL "#/link" }
35 | ]
36 | }
37 |
38 | , example
39 | $ compactBreadcrumb
40 | { onNavigate: toNullable Nothing
41 | , items: cons'
42 | { label: "Lorem", href: URL "#/input" }
43 | []
44 | }
45 | ]
46 |
--------------------------------------------------------------------------------
/docs/Examples/ButtonGroup.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.ButtonGroup where
2 |
3 | import Prelude
4 |
5 | import Effect.Console (log)
6 | import Effect.Uncurried (mkEffectFn1)
7 | import Lumi.Components.Button (button, primary, secondary)
8 | import Lumi.Components.ButtonGroup (buttonGroup)
9 | import Lumi.Components.Column (column_)
10 | import Lumi.Components.NativeSelect (nativeSelect, defaults)
11 | import Lumi.Components.Text (h2_)
12 | import Lumi.Components.Example (example)
13 | import React.Basic.Classic (JSX)
14 | import React.Basic.DOM as R
15 |
16 | docs :: JSX
17 | docs =
18 | column_
19 | [ h2_ "Not Joined"
20 | , example
21 | $ buttonGroup
22 | { joined: false
23 | , style: R.css {}
24 | , children:
25 | [ button primary { title = "Button" }
26 | , button secondary { title = "Button" }
27 | ]
28 | }
29 | , h2_ "Not Joined"
30 | , example
31 | $ buttonGroup
32 | { joined: false
33 | , style: R.css {}
34 | , children:
35 | [ button primary { title = "Button" }
36 | , button secondary { title = "Button" }
37 | , button secondary { title = "Button" }
38 | ]
39 | }
40 | , h2_ "Not Joined"
41 | , example
42 | $ buttonGroup
43 | { joined: false
44 | , style: R.css {}
45 | , children:
46 | [ button primary { title = "Button" }
47 | , nativeSelect defaults
48 | { options = []
49 | , onChange = mkEffectFn1 \_ -> log "onChange"
50 | , value = "Foo bar"
51 | }
52 | , button secondary { title = "Button" }
53 | ]
54 | }
55 | , h2_ "Joined"
56 | , example
57 | $ buttonGroup
58 | { joined: true
59 | , style: R.css {}
60 | , children:
61 | [ button secondary { title = "Button" }
62 | , button secondary { title = "Button" }
63 | ]
64 | }
65 | , h2_ "Joined"
66 | , example
67 | $ buttonGroup
68 | { joined: true
69 | , style: R.css {}
70 | , children:
71 | [ button secondary { title = "Button" }
72 | , button secondary { title = "Button" }
73 | , button secondary { title = "Button" }
74 | ]
75 | }
76 | ]
77 |
--------------------------------------------------------------------------------
/docs/Examples/Card.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Card where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Data.Newtype (un)
7 | import Effect.Console (log)
8 | import Lumi.Components.Card (card, defaults)
9 | import Lumi.Components.Column (column_)
10 | import Lumi.Components.Row (row_)
11 | import Lumi.Components.Spacing (Space(..), hspace)
12 | import Lumi.Components.Text (h2_)
13 | import Lumi.Components.Example (example)
14 | import React.Basic.Classic (JSX)
15 | import React.Basic.DOM as R
16 | import Web.HTML.History (URL(..))
17 |
18 | docs :: JSX
19 | docs =
20 | row_
21 | [ column_
22 | [ h2_ "Default state"
23 | , example $
24 | card defaults
25 | { title = "Poly Mailers"
26 | , subtitle = "14.50\" × 19.00\""
27 | , href = Just $ URL "https://www.lumi.com/"
28 | , onNavigate = log <<< un URL
29 | , children =
30 | [ R.img
31 | { src: "https://s3.amazonaws.com/lumi-flapjack-production/mockups/4985252ac5ba4c79e2ecbc6d3438e4ca.jpg"
32 | }
33 | ]
34 | }
35 | ]
36 |
37 | , hspace S32
38 |
39 | , column_
40 | [ h2_ "Selected state"
41 | , example $
42 | card defaults
43 | { title = "Poly Mailers"
44 | , subtitle = "14.50\" × 19.00\""
45 | , selected = true
46 | , children =
47 | [ R.img
48 | { src: "https://s3.amazonaws.com/lumi-flapjack-production/mockups/4985252ac5ba4c79e2ecbc6d3438e4ca.jpg"
49 | }
50 | ]
51 | }
52 | ]
53 | ]
54 |
--------------------------------------------------------------------------------
/docs/Examples/Column.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Column where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Button (button, primary)
6 | import Lumi.Components.Column (column_, responsiveColumn_)
7 | import Lumi.Components.Text (h2_, p_)
8 | import Lumi.Components.Example (example)
9 | import React.Basic.Classic (JSX)
10 |
11 | docs :: JSX
12 | docs =
13 | column_
14 | [ example
15 | $ column_
16 | [ button primary { title = "Button" }
17 | , button primary { title = "Button" }
18 | , button primary { title = "Button" }
19 | ]
20 |
21 | , h2_ "Responsive column"
22 | , p_ "* Resize the window to see how the component responds."
23 | , example
24 | $ responsiveColumn_
25 | [ button primary { title = "Button" }
26 | , button primary { title = "Button" }
27 | , button primary { title = "Button" }
28 | ]
29 | ]
30 |
--------------------------------------------------------------------------------
/docs/Examples/Details.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Details where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Border (borderBottom, borderTop)
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Details (defaults, details)
8 | import Lumi.Components.Divider (divider_)
9 | import Lumi.Components.Example (example)
10 | import Lumi.Components.Icon as Icon
11 | import Lumi.Components.Row (row)
12 | import Lumi.Components.Spacing (Space(..), hspace, vspace)
13 | import Lumi.Components.Text (body_, h2_, subsectionHeader_)
14 | import React.Basic.Classic (JSX)
15 | import React.Basic.DOM as R
16 |
17 | docs :: JSX
18 | docs =
19 | column_
20 | [ h2_ "Bare/defaults"
21 | , example $
22 | details defaults
23 | { expanded = body_ "Here's all the info"
24 | }
25 |
26 | , h2_ "Custom arrow"
27 | , example $
28 | details defaults
29 | { summary = iconSummary $ subsectionHeader_ "Details"
30 | , expanded = body_ "Here's all the info"
31 | }
32 |
33 | , h2_ "Accordion"
34 | , example $
35 | borderTop $ borderBottom $
36 | details defaults
37 | { summary = iconSummary $ subsectionHeader_ "Details"
38 | , expanded =
39 | column_
40 | [ vspace S8
41 | , divider_
42 | , vspace S8
43 | , body_ "Here's all the info"
44 | ]
45 | }
46 | ]
47 | where
48 | iconSummary child =
49 | row
50 | { style: R.css { alignItems: "center" }
51 | , children: [ Icon.icon_ Icon.ArrowRight, hspace S8, child ]
52 | }
53 |
--------------------------------------------------------------------------------
/docs/Examples/Divider.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Divider where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Column (column, column_)
6 | import Lumi.Components.Divider (divider_, flexDivider_)
7 | import Lumi.Components.Example (example)
8 | import Lumi.Components.Row (row)
9 | import Lumi.Components.Text as T
10 | import React.Basic.Classic (Component, JSX, createComponent, makeStateless)
11 | import React.Basic.DOM as R
12 |
13 | component :: Component Unit
14 | component = createComponent "DividerExample"
15 |
16 | docs :: JSX
17 | docs = unit # makeStateless component render
18 | where
19 | item t =
20 | T.text T.body
21 | { style = R.css { margin: "12px" }
22 | , children = [ R.text t ]
23 | }
24 |
25 | render _ =
26 | column_
27 | [ T.text T.p
28 | { children =
29 | [ R.code_ [ R.text "hr" ]
30 | , R.text " based divider:"
31 | ]
32 | }
33 | , example $
34 | column
35 | { style: R.css
36 | { minWidth: 300
37 | , alignItems: "center"
38 | }
39 | , children:
40 | [ item "Lorem"
41 | , divider_
42 | , item "ipsum"
43 | ]
44 | }
45 |
46 | , T.p_ "Flexbox based divider:"
47 | , example $
48 | row
49 | { style: R.css
50 | { minHeight: 200
51 | , alignItems: "center"
52 | }
53 | , children:
54 | [ item "Lorem"
55 | , flexDivider_
56 | , column_
57 | [ item "ipsum"
58 | , flexDivider_
59 | , item "dolor"
60 | ]
61 | ]
62 | }
63 | ]
64 |
--------------------------------------------------------------------------------
/docs/Examples/FixedPrecisionInput.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.FixedPrecisionInput where
2 |
3 | import Prelude
4 |
5 | import Data.Either (Either(..))
6 | import Data.Fixed (Fixed, P100)
7 | import Effect.Uncurried (mkEffectFn2)
8 | import Lumi.Components.Spacing (vspace, Space(S12))
9 | import Lumi.Components.Column (column_)
10 | import Lumi.Components.Text (h3_, subtext_)
11 | import Lumi.Components.FixedPrecisionInput (defaults, fixedPrecisionInput)
12 | import Lumi.Components.Example (example)
13 | import React.Basic.Classic (Component, JSX, createComponent, make)
14 |
15 | component :: Component Unit
16 | component = createComponent "FixedPrecisionInputExample"
17 |
18 | docs :: JSX
19 | docs = unit # make component
20 | { initialState
21 | , render
22 | }
23 | where
24 | initialState =
25 | { example1: Left "" :: Either String (Fixed P100)
26 | , example2: Left "" :: Either String (Fixed P100)
27 | }
28 |
29 | render self =
30 | column_
31 | [ h3_ "Fixed P100"
32 | , example $
33 | fixedPrecisionInput defaults
34 | { onChange = mkEffectFn2 \value _ ->
35 | self.setState _ { example1 = value }
36 | , value = self.state.example1
37 | }
38 | , subtext_ (show self.state.example1)
39 |
40 | , vspace S12
41 | , h3_ "Fixed P100 (forced invalid)"
42 | , example $
43 | fixedPrecisionInput defaults
44 | { onChange = mkEffectFn2 \value _ ->
45 | self.setState _ { example2 = value }
46 | , value = self.state.example2
47 | }
48 | , subtext_ (show self.state.example2)
49 | ]
50 |
--------------------------------------------------------------------------------
/docs/Examples/Icon.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Icon where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Column (column_)
6 | import Lumi.Components.Icon (IconType(..), icon_)
7 | import Lumi.Components.Text (body, body_, text)
8 | import Lumi.Components.Example (example)
9 | import React.Basic.Classic (JSX)
10 |
11 | docs :: JSX
12 | docs =
13 | column_
14 | [ text body
15 | { children =
16 | [ body_ "Arrow Down"
17 | , example $ icon_ ArrowDown
18 | , body_ "Arrow Left"
19 | , example $ icon_ ArrowLeft
20 | , body_ "Arrow Right"
21 | , example $ icon_ ArrowRight
22 | , body_ "Arrow Up"
23 | , example $ icon_ ArrowUp
24 | , body_ "Bin"
25 | , example $ icon_ Bin
26 | , body_ "Cart"
27 | , example $ icon_ Cart
28 | , body_ "Check"
29 | , example $ icon_ Check
30 | , body_ "Close"
31 | , example $ icon_ Close
32 | , body_ "Currency"
33 | , example $ icon_ Currency
34 | , body_ "Grid"
35 | , example $ icon_ Grid
36 | , body_ "Hamburger"
37 | , example $ icon_ Hamburger
38 | , body_ "Indeterminate"
39 | , example $ icon_ Indeterminate
40 | , body_ "Info"
41 | , example $ icon_ Info
42 | , body_ "List"
43 | , example $ icon_ List
44 | , body_ "Minus"
45 | , example $ icon_ Minus
46 | , body_ "Overflow"
47 | , example $ icon_ Overflow
48 | , body_ "Percent"
49 | , example $ icon_ Percent
50 | , body_ "Plus"
51 | , example $ icon_ Plus
52 | , body_ "Rearrange"
53 | , example $ icon_ Rearrange
54 | , body_ "Remove"
55 | , example $ icon_ Remove
56 | , body_ "Search"
57 | , example $ icon_ Search
58 | ]
59 | }
60 | ]
61 |
--------------------------------------------------------------------------------
/docs/Examples/Images.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Images where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Column (column_)
6 | import Lumi.Components.Images (avatar_, productThumb_)
7 | import Lumi.Components.Size (extraLarge, large, medium, small)
8 | import Lumi.Components.Text (h2_)
9 | import Lumi.Components.Example (example)
10 | import React.Basic.Classic (JSX)
11 | import React.Basic.DOM as R
12 |
13 | docs :: JSX
14 | docs =
15 | column_
16 | [ h2_ "Avatar"
17 |
18 | , example $
19 | avatar_ { image: R.img { src: avatarImgSrc }, size: small }
20 | , example $
21 | avatar_ { image: R.img { src: avatarImgSrc }, size: medium }
22 | , example $
23 | avatar_ { image: R.img { src: avatarImgSrc }, size: large }
24 | , example $
25 | avatar_ { image: R.img { src: avatarImgSrc }, size: extraLarge }
26 |
27 | , h2_ "Product Thumb"
28 |
29 | , example $
30 | productThumb_ { image: R.img { src: productImgSrc }, size: small }
31 | , example $
32 | productThumb_ { image: R.img { src: productImgSrc }, size: small }
33 | , example $
34 | productThumb_ { image: R.img { src: productImgSrc }, size: medium }
35 | , example $
36 | productThumb_ { image: R.img { src: productImgSrc }, size: large }
37 | , example $
38 | productThumb_ { image: R.img { src: productImgSrc }, size: extraLarge }
39 | ]
40 | where
41 | avatarImgSrc = "https://s3.amazonaws.com/lumi-blog/avatars/_x600/flexo.jpg"
42 | productImgSrc = "https://s3.amazonaws.com/lumi-flapjack-staging/mockups/9e7f08b801e6bb3a428ef72e93c49fe5.jpg"
43 |
--------------------------------------------------------------------------------
/docs/Examples/Input.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Input where
2 |
3 | import Prelude
4 | import Data.Maybe (Maybe(Just))
5 | import Data.Nullable (toNullable)
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Example (example)
8 | import Lumi.Components.Input (CheckboxState(..), alignToInput, checkbox, input, radio, range, switch, text_)
9 | import Lumi.Components.Row (row_)
10 | import Lumi.Components.Size (Size(..))
11 | import Lumi.Components.Text (body, h2_, h4_, p_, text)
12 | import React.Basic.Classic (JSX)
13 | import React.Basic.DOM as R
14 |
15 | docs :: JSX
16 | docs =
17 | column_
18 | [ h4_ "Simple text input"
19 | , example
20 | $ input text_ { placeholder = "Placeholder..." }
21 | , h4_ "Text input with a placeholder"
22 | , example
23 | $ input
24 | text_
25 | { placeholder = "Placeholder..."
26 | , value = "This input contains a value"
27 | }
28 | , h4_ "Invalid input value"
29 | , example
30 | $ input
31 | text_
32 | { placeholder = "Placeholder..."
33 | , pattern = toNullable $ Just "d+"
34 | , value = "An invalid value"
35 | }
36 | , h4_ "Disabled input"
37 | , example
38 | $ input
39 | text_
40 | { placeholder = "Placeholder..."
41 | , disabled = true
42 | }
43 | , h4_ "Disabled input with a value"
44 | , example
45 | $ input
46 | text_
47 | { placeholder = "Placeholder..."
48 | , value = "Disabled with a value"
49 | , disabled = true
50 | }
51 | , h4_ "Aligning components with input text"
52 | , p_ "`AlignToInput` applies the padding values inputs use to any child component. Use this to align elements with inputs instead of manually specifying padding."
53 | , example
54 | $ row_
55 | [ alignToInput
56 | $ text
57 | body
58 | { children = [ R.text "Some aligned text" ]
59 | , style = R.css { whiteSpace: "nowrap" }
60 | }
61 | , input
62 | text_
63 | { placeholder = "Placeholder..."
64 | , value = "Input value"
65 | }
66 | ]
67 | , h2_ "Checkbox"
68 | , h4_ "Medium"
69 | , exampleRow $ input checkbox { checked = Off }
70 | , exampleRow $ input checkbox { checked = On }
71 | , exampleRow $ input checkbox { checked = Indeterminate }
72 | , h4_ "Small"
73 | , exampleRow
74 | $ input
75 | checkbox
76 | { size = Small
77 | , checked = Off
78 | }
79 | , exampleRow
80 | $ input
81 | checkbox
82 | { size = Small
83 | , checked = On
84 | }
85 | , exampleRow
86 | $ input
87 | checkbox
88 | { size = Small
89 | , checked = Indeterminate
90 | }
91 | , h2_ "Radio"
92 | , h4_ "Medium"
93 | , exampleRow $ input radio { checked = Off }
94 | , exampleRow $ input radio { checked = On }
95 | , h4_ "Small"
96 | , exampleRow
97 | $ input
98 | radio
99 | { size = Small
100 | , checked = Off
101 | }
102 | , exampleRow
103 | $ input
104 | radio
105 | { size = Small
106 | , checked = On
107 | }
108 | , h2_ "Switch"
109 | , exampleRow $ input switch { checked = Off }
110 | , exampleRow $ input switch { checked = On }
111 | , h2_ "Switch (Large)"
112 | , exampleRow $ input switch { checked = Off, size = Large }
113 | , exampleRow $ input switch { checked = On, size = Large }
114 | , h2_ "Range"
115 | , exampleRow $ input range
116 | ]
117 | where
118 | exampleRow child = example $ row_ [ child ]
119 |
--------------------------------------------------------------------------------
/docs/Examples/LabeledField.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.LabeledField where
2 |
3 | import Prelude
4 |
5 | import Data.Foldable (intercalate)
6 | import Data.Maybe (Maybe(..))
7 | import Lumi.Components.Column (column, column_)
8 | import Lumi.Components.Divider (divider)
9 | import Lumi.Components.Input as Input
10 | import Lumi.Components.LabeledField (RequiredField(..), ValidationMessage(..), defaults, labeledField)
11 | import Lumi.Components.Text (h2_)
12 | import Lumi.Components.Example (example)
13 | import React.Basic.Classic (JSX)
14 | import React.Basic.DOM (css)
15 | import React.Basic.DOM as R
16 |
17 | docs :: JSX
18 | docs =
19 | column_
20 | [ h2_ "Standard Form"
21 | , example
22 | $ column
23 | { style: css { alignSelf: "stretch" }
24 | , children: [ intercalate simpleDivider labeledFieldExamples ]
25 | }
26 |
27 | , h2_ "Forced Top Labels Form"
28 | , example
29 | $ column
30 | { style: css { alignSelf: "stretch" }
31 | , children: [ intercalate simpleDivider labeledFieldTopLabelExamples ]
32 | }
33 |
34 | , h2_ "Inline Table Form"
35 | , example
36 | $ column
37 | { style: css { alignSelf: "stretch" }
38 | , children:
39 | [ inlineTableDivider
40 | , intercalate inlineTableDivider labeledFieldExamples
41 | , inlineTableDivider
42 | ]
43 | }
44 |
45 | , h2_ "Inline Table Form (Nested)"
46 | , example
47 | $ column
48 | { style: css { alignSelf: "stretch" }
49 | , children:
50 | [ inlineTableDivider
51 | , intercalate inlineTableDivider inlineTableNestedExample
52 | , inlineTableDivider
53 | ]
54 | }
55 | ]
56 | where
57 |
58 | simpleDivider = divider { style: css { margin: "6px 0", visibility: "hidden" } }
59 |
60 | inlineTableDivider = divider { style: css { margin: "6px 0" } }
61 |
62 | makeLabeledFieldExamples forceTopLabel =
63 | [ labeledField defaults
64 | { label = R.text "First Name"
65 | , forceTopLabel = forceTopLabel
66 | , value = Input.input Input.text_ { value = "Asdf" }
67 | }
68 | , labeledField defaults
69 | { label = R.text "Last Name"
70 | , validationError = Just $ Error "Example validation error."
71 | , forceTopLabel = forceTopLabel
72 | }
73 | , labeledField defaults
74 | { label = R.text "Username"
75 | , forceTopLabel = forceTopLabel
76 | , required = Optional
77 | }
78 | , labeledField defaults
79 | { label = R.text "Password"
80 | , forceTopLabel = forceTopLabel
81 | , required = Required
82 | }
83 | , labeledField defaults
84 | { label = R.text "Admin?"
85 | , value = Input.input Input.switch
86 | , forceTopLabel = forceTopLabel
87 | }
88 | ]
89 |
90 | labeledFieldExamples = makeLabeledFieldExamples false
91 |
92 | labeledFieldTopLabelExamples = makeLabeledFieldExamples true
93 |
94 | inlineTableNestedExample =
95 | [ labeledField defaults
96 | { label = R.text "User Info"
97 | , value = intercalate inlineTableDivider labeledFieldExamples
98 | }
99 | , labeledField defaults
100 | { label = R.text "How many?"
101 | , value = Input.input Input.range
102 | }
103 | ]
104 |
--------------------------------------------------------------------------------
/docs/Examples/Link.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Link where
2 |
3 | import Prelude
4 | import Data.Maybe (Maybe(..))
5 | import Effect.Console (log)
6 | import Effect.Uncurried (mkEffectFn1)
7 | import Lumi.Components.Column (column_)
8 | import Lumi.Components.Link (link, defaults)
9 | import Lumi.Components.Text (body_)
10 | import Lumi.Components.Example (example)
11 | import React.Basic.Classic (JSX)
12 | import React.Basic.DOM as R
13 | import Web.HTML.History (URL(..))
14 |
15 | docs :: JSX
16 | docs =
17 | column_
18 | [ example
19 | $ R.div
20 | { onClick: mkEffectFn1 \_ -> log "Propagated"
21 | , style: R.css { display: "flex", flexDirection: "column" }
22 | , children:
23 | [ link
24 | defaults
25 | { href = URL "#/link"
26 | , navigate = pure $ log "link clicked"
27 | , text = body_ "Click here"
28 | }
29 | , link
30 | defaults
31 | { href = URL "#/input"
32 | , target = Just "_blank"
33 | , text = body_ "This should open in a new tab"
34 | }
35 | ]
36 | }
37 | ]
38 |
--------------------------------------------------------------------------------
/docs/Examples/Loader.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Loader where
2 |
3 | import Prelude
4 |
5 | import Data.Nullable (null)
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Loader (loader)
8 | import Lumi.Components.Example (example)
9 | import React.Basic.Classic (JSX)
10 | import React.Basic.DOM as R
11 |
12 | docs :: JSX
13 | docs =
14 | column_
15 | [ example $
16 | loader { style: R.css {}, testId: null }
17 | ]
18 |
--------------------------------------------------------------------------------
/docs/Examples/Lockup.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Lockup where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Images (productThumb_)
8 | import Lumi.Components.Lockup (pageLockup, productLockup, userLockup)
9 | import Lumi.Components.Size (Size(..))
10 | import Lumi.Components.Text (h4_)
11 | import Lumi.Components.Example (example)
12 | import React.Basic.Classic (JSX)
13 | import React.Basic.DOM as R
14 |
15 | docs :: JSX
16 | docs =
17 | column_
18 | [ h4_ "Product Lockup"
19 | , example $
20 | productLockup
21 | { name: "Item title"
22 | , description: Just "Foo Bar"
23 | , image: R.img { src: productImgSrc }
24 | }
25 |
26 | , h4_ "User Lockup"
27 | , example $
28 | userLockup
29 | { name: "Flexo Rodriguez"
30 | , description: Just "Lumi"
31 | , image: R.img { src: avatarImgSrc }
32 | }
33 |
34 | , h4_ "User Lockup (no image)"
35 | , example $
36 | userLockup
37 | { name: "Flexo Rodriguez"
38 | , description: Just "Lumi"
39 | , image: mempty
40 | }
41 |
42 | , h4_ "Page title Lockup"
43 | , example $
44 | pageLockup
45 | { title: "Small Pouch - Rebrand"
46 | , subtitle: Just "7\" × 11\""
47 | , image: Just $ productThumb_ { size: Medium, image: R.img { src: productImgSrc } }
48 | }
49 | ]
50 | where
51 | avatarImgSrc = "https://s3.amazonaws.com/lumi-flapjack-staging/avatars/vfhpnqairr/thumbnail_1517878176349.png"
52 | productImgSrc = "https://s3.amazonaws.com/lumi-flapjack-staging/mockups/9e7f08b801e6bb3a428ef72e93c49fe5.jpg"
53 |
--------------------------------------------------------------------------------
/docs/Examples/NativeSelect.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.NativeSelect where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (fromMaybe)
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Example (example)
8 | import Lumi.Components.NativeSelect (defaults, nativeSelect)
9 | import React.Basic.Classic (Component, JSX, createComponent, make)
10 | import React.Basic.DOM as R
11 | import React.Basic.DOM.Events (targetValue)
12 | import React.Basic.Events (handler)
13 |
14 | component :: Component Unit
15 | component = createComponent "SelectExample"
16 |
17 | docs :: JSX
18 | docs = unit # make component
19 | { initialState
20 | , render
21 | }
22 | where
23 | opts =
24 | [ { label: "Select your car...", value: "" }
25 | , { label: "Volvo", value: "volvo" }
26 | , { label: "Saab", value: "saab" }
27 | , { label: "Mercedes", value: "mercedes" }
28 | , { label: "Audi", value: "audi" }
29 | ]
30 |
31 | initialState = { selectedOption: "" }
32 |
33 | render self =
34 | column_
35 | [ example
36 | $ nativeSelect defaults
37 | { options = opts
38 | , onChange = handler targetValue \value ->
39 | self.setState _ { selectedOption = fromMaybe "" value }
40 | , value = self.state.selectedOption
41 | }
42 |
43 | , example
44 | $ nativeSelect defaults
45 | { options = opts
46 | , onChange = handler targetValue \value ->
47 | self.setState _ { selectedOption = fromMaybe "" value }
48 | , value = self.state.selectedOption
49 | , style = R.css { textAlignLast: "right" }
50 | }
51 | ]
52 |
--------------------------------------------------------------------------------
/docs/Examples/Navigation.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Navigation where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Data.NonEmpty ((:|))
7 | import Effect.Console (log)
8 | import Lumi.Components.Column (column_)
9 | import Lumi.Components.Example (example)
10 | import Lumi.Components.Navigation (navigation)
11 | import Lumi.Components.Text (h2_)
12 | import React.Basic.Classic (JSX)
13 | import Web.HTML.History (URL(..))
14 |
15 | docs :: JSX
16 | docs =
17 | column_
18 | [ h2_ "Compact navigation"
19 | , example $
20 | navigation
21 | { navLinks:
22 | { text: "Home"
23 | , href: URL "/"
24 | , subNavLinks: []
25 | } :|
26 | [ { text: "Customers"
27 | , href: URL "/customers"
28 | , subNavLinks: []
29 | }
30 | , { text: "Products"
31 | , href: URL "/products"
32 | , subNavLinks:
33 | [ { text: "Products"
34 | , href: URL "/products"
35 | }
36 | , { text: "Categories"
37 | , href: URL "/categories"
38 | }
39 | ]
40 | }
41 | , { text: "Orders"
42 | , href: URL "/orders"
43 | , subNavLinks: []
44 | }
45 | , { text: "Help"
46 | , href: URL "/help"
47 | , subNavLinks: []
48 | }
49 | ]
50 | , user:
51 | { src: "https://s3.amazonaws.com/lumi-flapjack-staging/avatars/vfhpnqairr/thumbnail_1517878176349.png"
52 | , name: Just "Flexo"
53 | , email: Just "flexo@lumi.com"
54 | , id: "flexo-1234"
55 | }
56 | , client:
57 | { name: Nothing
58 | }
59 | , compact: true
60 | , onCartClick: Nothing
61 | , cartAmount: Nothing
62 | , onLogoutClick: Just $ log "logout click"
63 | , logo: mempty
64 | }
65 |
66 | , h2_ "Responsive navigation"
67 | , example $
68 | navigation
69 | { navLinks:
70 | { text: "Items"
71 | , href: URL "/items"
72 | , subNavLinks: []
73 | } :|
74 | [ { text: "Orders"
75 | , href: URL "/orders"
76 | , subNavLinks: []
77 | }
78 | , { text: "Shipments"
79 | , href: URL "/shipments"
80 | , subNavLinks: []
81 | }
82 | , { text: "Billing"
83 | , href: URL "/billing"
84 | , subNavLinks: []
85 | }
86 | , { text: "Shop"
87 | , href: URL "/shop"
88 | , subNavLinks: []
89 | }
90 | ]
91 | , user:
92 | { src: "https://s3.amazonaws.com/lumi-flapjack-staging/avatars/vfhpnqairr/thumbnail_1517878176349.png"
93 | , name: Just $ "Flexo"
94 | , email: Just $ "flexo@lumi.com"
95 | , id: "flexo-1234"
96 | }
97 | , client:
98 | { name: Just "Lumi"
99 | }
100 | , compact: false
101 | , onCartClick: Just $ log "cart click"
102 | , cartAmount: Just 2
103 | , onLogoutClick: Just $ log "logout click"
104 | , logo: mempty
105 | }
106 | ]
107 |
--------------------------------------------------------------------------------
/docs/Examples/Pagination.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Pagination where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Pagination (defaults, pagination)
8 | import Lumi.Components.Example (example)
9 | import React.Basic.Classic (JSX, createComponent, make)
10 | import React.Basic.DOM as R
11 |
12 | docs :: JSX
13 | docs = unit # make (createComponent "PaginationExample")
14 | { initialState
15 | , render
16 | }
17 | where
18 | initialState =
19 | { currentPage: 0
20 | }
21 |
22 | render self =
23 | column_ $
24 | [ example
25 | $ pagination defaults
26 | { currentPage = self.state.currentPage
27 | , pages = 53
28 | , onChange = self.setState <<< flip _{ currentPage = _ }
29 | , details = Just $ R.text "531 results"
30 | }
31 |
32 | , example
33 | $ pagination defaults
34 | { currentPage = self.state.currentPage
35 | , pages = 4
36 | , onChange = self.setState <<< flip _{ currentPage = _ }
37 | , details = Just $ R.text "40 results"
38 | }
39 |
40 | , example
41 | $ pagination defaults
42 | { currentPage = self.state.currentPage
43 | , pages = 0
44 | , onChange = self.setState <<< flip _{ currentPage = _ }
45 | , details = Just $ R.text "0 results"
46 | }
47 |
48 | , example
49 | $ pagination defaults
50 | { currentPage = self.state.currentPage
51 | , pages = 40
52 | , onChange = self.setState <<< flip _{ currentPage = _ }
53 | , focusWindow = 4
54 | , marginPages = 3
55 | , details = Just $ R.text "408 results"
56 | }
57 | ]
58 |
--------------------------------------------------------------------------------
/docs/Examples/Pill.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Pill where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Column (column_)
6 | import Lumi.Components.Pill (pill_)
7 | import Lumi.Components.Example (example)
8 | import React.Basic.Classic (JSX)
9 | import Lumi.Components.Status (Status(..))
10 |
11 | docs :: JSX
12 | docs =
13 | column_
14 | [ example $
15 | pill_ Active "Active"
16 |
17 | , example $
18 | pill_ Warning "Warning"
19 |
20 | , example $
21 | pill_ Error "Error"
22 |
23 | , example $
24 | pill_ Unknown "Unknown"
25 | ]
26 |
--------------------------------------------------------------------------------
/docs/Examples/Progress.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Progress where
2 |
3 | import Prelude
4 |
5 | import Data.Int (fromString)
6 | import Data.Maybe (Maybe(..), fromMaybe)
7 | import Data.Nullable (toNullable)
8 | import Lumi.Components.Button as Button
9 | import Lumi.Components.Column (column, column_)
10 | import Lumi.Components.Input as Input
11 | import Lumi.Components.LabeledField (labeledField, RequiredField(..))
12 | import Lumi.Components.Progress (progressBar, progressCircle, progressDefaults)
13 | import Lumi.Components.Row (row_)
14 | import Lumi.Components.Size (Size(..))
15 | import Lumi.Components.Text (h2_)
16 | import Lumi.Components.Example (example)
17 | import React.Basic.Classic (Component, JSX, createComponent, make)
18 | import React.Basic.DOM as R
19 | import React.Basic.DOM.Events (targetValue)
20 | import React.Basic.Events (handler, handler_)
21 |
22 | component :: Component Unit
23 | component = createComponent "ProgressExample"
24 |
25 | data Action
26 | = Set Int
27 | | Increment
28 | | Reset
29 |
30 | type State =
31 | { percent :: Int
32 | }
33 |
34 | docs :: JSX
35 | docs = unit # make component
36 | { initialState: { percent: 20 }
37 | , render: \self ->
38 | column_
39 | [ column
40 | { style: R.css { maxWidth: "500px", padding: "20px 0" }
41 | , children:
42 | [ labeledField
43 | { label: R.text "Adjust progress:"
44 | , value:
45 | row_
46 | [ Input.input Input.range
47 | { value = show self.state.percent
48 | , min = toNullable $ Just 0.0
49 | , max = toNullable $ Just 100.0
50 | , onChange = handler targetValue (send self <<< Set <<< fromMaybe 0 <<< flip bind fromString)
51 | , style = R.css { maxWidth: "200px" }
52 | }
53 | , Button.button Button.primary
54 | { title = "+10%"
55 | , onPress = handler_ $ send self Increment
56 | , size = Small
57 | , style = R.css { marginLeft: "16px" }
58 | }
59 | , Button.button Button.primary
60 | { title = "Reset"
61 | , onPress = handler_ $ send self Reset
62 | , size = Small
63 | , style = R.css { marginLeft: "8px" }
64 | }
65 | ]
66 | , validationError: Nothing
67 | , required: Neither
68 | , forceTopLabel: false
69 | , style: R.css {}
70 | }
71 | ]
72 | }
73 |
74 | , h2_ "Bar"
75 | , example
76 | $ progressBar progressDefaults { completed = self.state.percent }
77 |
78 | , h2_ "Circle"
79 | , example
80 | $ progressCircle progressDefaults { completed = self.state.percent }
81 | ]
82 | }
83 | where
84 | send self = case _ of
85 | Set percent ->
86 | self.setState _ { percent = percent }
87 | Increment ->
88 | self.setState \state -> state { percent = min 100 $ state.percent + 10 }
89 | Reset ->
90 | self.setState _ { percent = 0 }
91 |
92 |
--------------------------------------------------------------------------------
/docs/Examples/Responsive.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Responsive where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Column (column_)
6 | import Lumi.Components.Example (example)
7 | import Lumi.Components.Responsive (desktop, mobile, phone, withMobile)
8 | import Lumi.Components.Text (body_, p_)
9 | import React.Basic.Classic (JSX)
10 |
11 | docs :: JSX
12 | docs =
13 | column_
14 | $ [ p_ "The following component renders different values at each of these sizes: desktop, mobile, and phone"
15 | , p_ "Note that phone sized screens still report themselves as \"mobile\" as well."
16 | , example
17 | $ column_
18 | [ withMobile \isMobile ->
19 | body_
20 | if isMobile then
21 | "Mobile: this text renders differently on mobile-sized screens."
22 | else
23 | "Not mobile: this text renders differently on mobile-sized screens."
24 | , mobile \_ ->
25 | body_ "Mobile: this text is only rendered on mobile-sized screens."
26 | , desktop \_ ->
27 | body_ "Desktop: this text is only rendered on desktop-sized screens."
28 | , phone \_ ->
29 | body_ "Phone: this text is only rendered on phone-sized screens."
30 | ]
31 | ]
32 |
--------------------------------------------------------------------------------
/docs/Examples/Row.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Row where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Button (button, primary)
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Row (responsiveRow_, row_)
8 | import Lumi.Components.Text (h2_, p_)
9 | import Lumi.Components.Example (example)
10 | import React.Basic.Classic (JSX)
11 |
12 | docs :: JSX
13 | docs =
14 | column_
15 | [ example
16 | $ row_
17 | [ button primary { title = "Button" }
18 | , button primary { title = "Button" }
19 | , button primary { title = "Button" }
20 | ]
21 |
22 | , h2_ "Responsive row"
23 | , p_ "* Resize the window to see how the component responds."
24 | , example
25 | $ responsiveRow_
26 | [ button primary { title = "Button" }
27 | , button primary { title = "Button" }
28 | , button primary { title = "Button" }
29 | ]
30 | ]
31 |
--------------------------------------------------------------------------------
/docs/Examples/ScrollManager.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.ScrollManager where
2 |
3 | import Lumi.Components.Column (column_)
4 | import Lumi.Components.Text (p_)
5 | import Lumi.Components.Example (exampleCode)
6 | import React.Basic.Classic (JSX)
7 |
8 | docs :: JSX
9 | docs =
10 | column_
11 | [ p_ "Used to normalize forward/back button scroll behavior across browsers."
12 | , p_ "`ScrollManager` should be used at the root of the application, just below the `react-router` `Router` component, as it only supports Window scrolling. For example:"
13 | , exampleCode """ReactDOM.render(
14 |
15 |
16 |
17 |
18 | ,
19 | document.getElementById("root")
20 | );
21 | """
22 | ]
23 |
--------------------------------------------------------------------------------
/docs/Examples/Slider.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Slider where
2 |
3 | import Prelude
4 |
5 | import Effect.Uncurried (mkEffectFn1)
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Text (p_)
8 | import Lumi.Components.Slider (slider)
9 | import Lumi.Components.Example (example)
10 | import React.Basic.Classic (Component, JSX, createComponent, make)
11 | import React.Basic.DOM as R
12 |
13 | component :: Component Unit
14 | component = createComponent "SliderExample"
15 |
16 | docs :: JSX
17 | docs = unit # make component
18 | { initialState
19 | , render
20 | }
21 | where
22 | initialState =
23 | { percent: 50
24 | }
25 |
26 | render self =
27 | column_
28 | [ p_ $ show self.state.percent <> "%"
29 | , example $
30 | slider
31 | { completed: self.state.percent
32 | , onChange: mkEffectFn1 \percent -> self.setState _ { percent = percent }
33 | , style: R.css { }
34 | }
35 | ]
36 |
--------------------------------------------------------------------------------
/docs/Examples/Spacing.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Spacing where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Lumi.Components.Color (colors)
7 | import Lumi.Components.Column (column_)
8 | import Lumi.Components.Example (example)
9 | import Lumi.Components.Row (row)
10 | import Lumi.Components.Spacing (Space(..), hspace, vspace)
11 | import Lumi.Components.Text (body_)
12 | import React.Basic.Classic (JSX)
13 | import React.Basic.DOM as R
14 |
15 | docs :: JSX
16 | docs =
17 | column_
18 | [ spacingExample "4px" S4
19 | , spacingExample "8px" S8
20 | , spacingExample "12px" S12
21 | , spacingExample "16px" S16
22 | , spacingExample "24px" S24
23 | , spacingExample "32px" S32
24 | , spacingExample "48px" S48
25 | , spacingExample "56px" S56
26 | , spacingExample "64px" S64
27 | , spacingExample "72px" S72
28 | , spacingExample "80px" S80
29 | , spacingExample "88px" S88
30 | , spacingExample "96px" S96
31 | , spacingExample "104px" S104
32 | , spacingExample "112px" S112
33 | ]
34 | where
35 | spacingExample text size =
36 | example $
37 | row
38 | { style: R.css { alignItems: "center" }
39 | , children:
40 | [ body_ text
41 | , hspace S12
42 | , row
43 | { style: R.css { backgroundColor: cssStringHSLA colors.black3 }
44 | , children:
45 | [ R.div_ [ vspace S64 ]
46 | , hspace size
47 | ]
48 | }
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/docs/Examples/StatusSlat.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.StatusSlat where
2 |
3 | import Prelude
4 |
5 | import Data.Nullable (notNull, null)
6 | import Lumi.Components.Column (column_)
7 | import Lumi.Components.Example (example)
8 | import Lumi.Components.Status (Status(..))
9 | import Lumi.Components.StatusSlat (statusSlat)
10 | import React.Basic.Classic (JSX)
11 |
12 | docs :: JSX
13 | docs =
14 | column_
15 | [ example $
16 | statusSlat { data: statusSlat_one }
17 | , example $
18 | statusSlat { data: statusSlat_two }
19 | , example $
20 | statusSlat { data: statusSlat_three }
21 | , example $
22 | statusSlat { data: statusSlat_four }
23 | ]
24 | where
25 | statusSlat_one =
26 | [ { label: "Status"
27 | , content: "Complete"
28 | , status: notNull Active
29 | }
30 | , { label: "Subtext"
31 | , content: "Product name"
32 | , status: null
33 | }
34 | , { label: "Date ordered"
35 | , content: "2018-02-13"
36 | , status: null
37 | }
38 | , { label: "Total"
39 | , content: "$20.000"
40 | , status: null
41 | }
42 | ]
43 |
44 | statusSlat_two =
45 | [ { label: "Status"
46 | , content: "Complete"
47 | , status: notNull Active
48 | }
49 | , { label: "Payment status"
50 | , content: "Paid"
51 | , status: null
52 | }
53 | ]
54 |
55 | statusSlat_three =
56 | [ { label: "Status"
57 | , content: "Cancelled"
58 | , status: notNull Error
59 | }
60 | , { label: "Price"
61 | , content: "$0.01"
62 | , status: null
63 | }
64 | , { label: "Due date"
65 | , content: "2/14/18"
66 | , status: notNull Unknown
67 | }
68 | ]
69 |
70 | statusSlat_four =
71 | [ { label: "Status"
72 | , content: "In progress"
73 | , status: notNull Warning
74 | }
75 | , { label: "Weight"
76 | , content: "5.00 lb"
77 | , status: null
78 | }
79 | , { label: "Size"
80 | , content: "Medium"
81 | , status: null
82 | }
83 | , { label: "Price"
84 | , content: "$6.54"
85 | , status: null
86 | }
87 | ]
88 |
--------------------------------------------------------------------------------
/docs/Examples/Svg.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Svg where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Column (column_)
6 | import Lumi.Components.Example (example)
7 | import Lumi.Components.Svg (userSvg, clientSvg)
8 | import Lumi.Components.Text (h2_)
9 | import React.Basic.Classic (JSX)
10 | import React.Basic.DOM as R
11 |
12 | docs :: JSX
13 | docs =
14 | column_
15 | [ h2_ "Client"
16 | , svgExample clientSvg
17 |
18 | , h2_ "User"
19 | , svgExample userSvg
20 | ]
21 | where
22 | svgExample svg =
23 | example $ R.div
24 | { style: R.css { maxHeight: 200, maxWidth: 200, height: "100%", width: "100%" }
25 | , children: [ svg ]
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/docs/Examples/Tab.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Tab where
2 |
3 | import Prelude
4 |
5 | import Control.Alternative (guard)
6 | import Data.Array (index, (..))
7 | import Data.Maybe (Maybe(..), fromMaybe)
8 | import Data.String (Pattern(..), split, toLower)
9 | import Effect.Uncurried (runEffectFn1)
10 | import Lumi.Components.Column (column_)
11 | import Lumi.Components.Tab (TabId(..), TabKey(..), tabs, urlParts)
12 | import Lumi.Components.Text (h2_)
13 | import Lumi.Components.Utility.ReactRouter (RouterProps, withRouter)
14 | import Lumi.Components.Example (example)
15 | import React.Basic.Classic (Component, JSX, createComponent, element, empty, toReactComponent)
16 | import React.Basic.DOM as R
17 | import Web.HTML.History (URL(..))
18 |
19 | component :: Component (RouterProps ())
20 | component = createComponent "TabExample"
21 |
22 | docs :: JSX
23 | docs = flip element {} $ withRouter $ toReactComponent identity component { render: \{ props } -> render props }
24 | where
25 | render props =
26 | column_
27 | [ h2_ "Demo 1"
28 | , example $
29 | R.div
30 | { style: R.css { maxWidth: "800px" }
31 | , children:
32 | [ tabs
33 | { currentLocation: URL $ "#" <> props.location.pathname <> props.location.search <> props.location.hash
34 | , useHash: true
35 | , navigate: Just \url ->
36 | let
37 | parts = urlParts url
38 | newUrl = parts.path <> parts.query <> parts.hash.path <> parts.hash.query
39 | newUrlNoHash = fromMaybe "" $ flip index 1 $ split (Pattern "#") newUrl
40 | in
41 | runEffectFn1 props.history.push $ URL $ newUrlNoHash
42 | , queryKey: TabKey "demo1"
43 | , style: R.css {}
44 | , tabStyle: R.css {}
45 | , selectedTabStyle: R.css {}
46 | , tabs: 1 .. 10 <#> \i ->
47 | let
48 | label = "Tab with a long title " <> show i
49 | in
50 | { content: \_ -> empty
51 | , id: TabId (toLower label)
52 | , label
53 | , count: (7*(i - 1) * (i - 1) `mod` 4) * 7 <$ guard (i `mod` 3 /= 1)
54 | , testId: Nothing
55 | }
56 | }
57 | ]
58 | }
59 |
60 | , h2_ "Demo 2"
61 | , example $
62 | tabs
63 | { currentLocation: URL $ "#" <> props.location.pathname <> props.location.search <> props.location.hash
64 | , useHash: true
65 | , navigate: Just \url ->
66 | let
67 | parts = urlParts url
68 | newUrl = parts.path <> parts.query <> parts.hash.path <> parts.hash.query
69 | newUrlNoHash = fromMaybe "" $ flip index 1 $ split (Pattern "#") newUrl
70 | in
71 | runEffectFn1 props.history.push $ URL $ newUrlNoHash
72 | , queryKey: TabKey "demo2"
73 | , style: R.css {}
74 | , tabStyle: R.css {}
75 | , selectedTabStyle: R.css {}
76 | , tabs: 1 .. 6 <#> \i ->
77 | let
78 | label = "Tab" <> show i
79 | in
80 | { content: \_ -> empty
81 | , id: TabId (toLower label)
82 | , label
83 | , count: (i `div` 2) <$ guard (i `mod` 4 == 1)
84 | , testId: Nothing
85 | }
86 | }
87 | ]
88 |
--------------------------------------------------------------------------------
/docs/Examples/Text.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Text where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Column (column_)
6 | import Lumi.Components.Text (body_, h1_, h2_, h3_, h4_, h5_, h6_, mainHeader_, p_, sectionHeader_, span_, subsectionHeader_, subtext_, title_, paragraph_)
7 | import Lumi.Components.Example (example)
8 | import React.Basic.Classic (JSX)
9 |
10 | docs :: JSX
11 | docs =
12 | column_
13 | [ p_ "There are six text sizes, each displayed below with its name. The lumi tags have no default padding. These sizes have also been mapped onto the browser tags with default padding."
14 | , example
15 | $ column_
16 | [ mainHeader_ "MainHeader"
17 | , title_ "Title"
18 | , sectionHeader_ "SectionHeader"
19 | , subsectionHeader_ "SubsectionHeader"
20 | , body_ "Body"
21 | , paragraph_ "Paragraph"
22 | , subtext_ "Subtext"
23 |
24 | , h1_ "h1"
25 | , h2_ "h2"
26 | , h3_ "h3"
27 | , h4_ "h4"
28 | , h5_ "h5"
29 | , h6_ "h6"
30 | , p_ "p"
31 | , span_ "span"
32 | ]
33 | ]
34 |
--------------------------------------------------------------------------------
/docs/Examples/Textarea.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Textarea where
2 |
3 | import Prelude
4 |
5 | import Data.Foldable (traverse_)
6 | import Lumi.Components.Column (column, column_)
7 | import Lumi.Components.Text (p_)
8 | import Lumi.Components.Textarea (defaults, textarea)
9 | import Lumi.Components.Example (example)
10 | import React.Basic.Classic (Component, JSX, createComponent, make)
11 | import React.Basic.DOM (css)
12 | import React.Basic.DOM.Events (targetValue)
13 | import React.Basic.Events (handler)
14 |
15 | component :: Component Unit
16 | component = createComponent "TextareaExample"
17 |
18 | docs :: JSX
19 | docs = unit # make component { initialState , render }
20 | where
21 | initialState =
22 | { example1: ""
23 | }
24 |
25 | render self =
26 | column_
27 | [ p_ "Empty"
28 | , example $
29 | column
30 | { style: css { alignSelf: "stretch" }
31 | , children:
32 | [ textarea defaults
33 | { lines = 3
34 | , placeholder = "Enter text here..."
35 | , value = self.state.example1
36 | , onChange = handler targetValue $ traverse_ \value -> self.setState _ { example1 = value }
37 | }
38 | ]
39 | }
40 | ]
41 |
--------------------------------------------------------------------------------
/docs/Examples/Toast.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Toast where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..), fromMaybe)
6 | import Effect.Random (random)
7 | import Lumi.Components.Button (button, primary)
8 | import Lumi.Components.ButtonGroup (buttonGroup)
9 | import Lumi.Components.Column (column_)
10 | import Lumi.Components.Input (input, text_)
11 | import Lumi.Components.Text (p, text)
12 | import Lumi.Components.Toast (toast, toastBubble, toastManager)
13 | import Lumi.Components.Example (example)
14 | import React.Basic.Classic (Component, JSX, createComponent, keyed, make)
15 | import React.Basic.DOM as R
16 | import React.Basic.DOM.Events (targetValue)
17 | import React.Basic.Events (handler, handler_)
18 |
19 | component :: Component Unit
20 | component = createComponent "ToastExample"
21 |
22 | docs :: JSX
23 | docs = unit # make component { initialState, render }
24 | where
25 | initialState =
26 | { message: Nothing
27 | , pendingMessage: "This is an example message"
28 | , key: ""
29 | }
30 |
31 | render self =
32 | column_
33 | [ example $
34 | toastBubble { message: self.state.pendingMessage }
35 |
36 | , text p
37 | { children =
38 | [ R.text "Click here to trigger a toast notification: "
39 | , buttonGroup
40 | { children:
41 | [ input text_
42 | { value = self.state.pendingMessage
43 | , onChange = handler targetValue \targetValue -> do
44 | self.setState _ { pendingMessage = fromMaybe "" targetValue }
45 | }
46 | , button primary
47 | { onPress = handler_ do
48 | key <- show <$> random
49 | self.setState _ { key = key, message = Just self.state.pendingMessage }
50 | , title = "Notify"
51 | }
52 | ]
53 | , joined: true
54 | , style: R.css {}
55 | }
56 | , toastManager
57 | , keyed self.state.key $
58 | toast { message: self.state.message }
59 | ]
60 | }
61 | ]
62 |
--------------------------------------------------------------------------------
/docs/Examples/Tooltip.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Examples.Tooltip where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Data.Nullable (toNullable)
7 | import Lumi.Components.Size (Size(..))
8 | import Lumi.Components.Column (column_)
9 | import Lumi.Components.Link as Link
10 | import Lumi.Components.Row (row_)
11 | import Lumi.Components.Text (body_)
12 | import Lumi.Components.Text as T
13 | import Lumi.Components.Example (example)
14 | import Lumi.Components.Tooltip (tooltip)
15 | import React.Basic.Classic (JSX)
16 | import React.Basic.DOM as R
17 | import Web.HTML.History (URL(..))
18 |
19 | docs :: JSX
20 | docs =
21 | column_
22 | [ example
23 | $ tooltip
24 | { variant: "basic"
25 | , style: R.css {}
26 | , text: R.text "Lorem ipsum"
27 | , content: body_ "Basic example"
28 | , size: toNullable Nothing
29 | }
30 |
31 | , example
32 | $ tooltip
33 | { variant: "top"
34 | , style: R.css {}
35 | , text: R.text "Lorem ipsum"
36 | , content: body_ "Top example"
37 | , size: toNullable Nothing
38 | }
39 |
40 | , example
41 | $ tooltip
42 | { variant: "bottom"
43 | , style: R.css {}
44 | , text: R.text "Lorem ipsum"
45 | , content: body_ "Bottom example"
46 | , size: toNullable Nothing
47 | }
48 |
49 | , example
50 | $ tooltip
51 | { variant: "left"
52 | , style: R.css {}
53 | , text: R.text "Lorem ipsum"
54 | , content: body_ "Left example"
55 | , size: toNullable Nothing
56 | }
57 |
58 | , example
59 | $ row_
60 | [ body_ "Hello, world see"
61 | , tooltip
62 | { variant: "top"
63 | , style: R.css { padding: "0 2px", textDecoration: "underline" }
64 | , text: R.text "Lorem ipsum"
65 | , content: body_ "here"
66 | , size: toNullable Nothing
67 | }
68 | , body_ "for more."
69 | ]
70 |
71 | , example
72 | $ row_
73 | [ body_ "Hello, world see"
74 | , tooltip
75 | { variant: "top"
76 | , style: R.css { padding: "0 2px", textDecoration: "underline" }
77 | , text: R.text "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis auctor libero non libero consequat, at iaculis diam venenatis. Donec nec porttitor tellus."
78 | , content: body_ "here"
79 | , size: toNullable $ Just $ Large
80 | }
81 | , body_ "for more."
82 | ]
83 |
84 | , example
85 | $ row_
86 | [ body_ "Hello, world see"
87 | , tooltip
88 | { variant: "top"
89 | , style: R.css { padding: "0 2px", textDecoration: "underline" }
90 | , text:
91 | T.text T.body
92 | { style = R.css {}
93 | , children =
94 | [ R.text "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis auctor libero non libero consequat, at iaculis diam venenatis. Donec nec porttitor tellus "
95 | , Link.link Link.defaults
96 | { href = URL "#/link"
97 | , text = R.text "click here"
98 | , target = Just "_blank"
99 | }
100 | , R.text "."
101 | ]
102 | }
103 | , content: body_ "here"
104 | , size: toNullable $ Just $ Large
105 | }
106 | , body_ "for more (includes link in tooltip)."
107 | ]
108 | ]
109 |
--------------------------------------------------------------------------------
/docs/Examples2/Box.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Examples.Box where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components (($$$))
6 | import Lumi.Components.Example (example)
7 | import Lumi.Components.MUIColorScheme (blue, green, red)
8 | import Lumi.Components.Spacing (Space(..), vspace)
9 | import Lumi.Components.Text (h2_, p_)
10 | import Lumi.Components2.Box (box, column, row)
11 | import Lumi.Styles.Box (FlexAlign(..), _alignSelf, _justify, _row)
12 | import React.Basic.Classic (JSX)
13 | import React.Basic.Emotion as E
14 |
15 | docs :: JSX
16 | docs =
17 | let
18 | childBox color =
19 | box _
20 | { css =
21 | \_ ->
22 | E.css
23 | { backgroundColor: E.color color
24 | , height: E.prop S40
25 | , width: E.prop S40
26 | }
27 | }
28 |
29 | exampleContent =
30 | [ childBox red
31 | , childBox green
32 | , childBox blue
33 | ]
34 | in
35 | box
36 | _
37 | { content =
38 | [ p_ "Box is a simple building block component. Lumi components default to building off of Box and generally expect their JSX arguments to be Box-compatible elements."
39 | , p_ "A Box is essentially a div which defaults to a flex column. The most common flex settings are available as prop modifiers. Nested boxes will also stretch to fill their given space by default. If a component shouldn't grow beyond a specific size under any circumstances, be sure to give it a max-width!"
40 | , vspace S24
41 | , h2_ "Defaults"
42 | , example
43 | $ box
44 | $$$ exampleContent
45 | , h2_ "Row"
46 | , example
47 | $ box
48 | $ _row
49 | $$$ exampleContent
50 | , h2_ "Align/justify"
51 | , example
52 | $ row
53 | $ _alignSelf Stretch -- only necessary because `example` isn't a Box
54 | $ _justify End
55 | $$$ exampleContent
56 | , h2_ "Space evenly"
57 | , example
58 | $ column
59 | $ _alignSelf Stretch -- only necessary because `example` isn't a Box
60 | $ _justify SpaceEvenly
61 | $$$ exampleContent
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/docs/Examples2/ButtonGroup.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Examples.ButtonGroup where
2 |
3 | import Prelude
4 |
5 | import Effect.Console (log)
6 | import Effect.Uncurried (mkEffectFn1)
7 | import Lumi.Components (($$$))
8 | import Lumi.Components.Column (column_)
9 | import Lumi.Components.Example (example)
10 | import Lumi.Components.NativeSelect (nativeSelect, defaults)
11 | import Lumi.Components.Text (h2_)
12 | import Lumi.Components2.Button (button, secondary)
13 | import Lumi.Components2.ButtonGroup (buttonGroup, joined)
14 | import React.Basic.Classic (JSX)
15 | import React.Basic.DOM as R
16 |
17 | docs :: JSX
18 | docs =
19 | column_
20 | $ [ h2_ "Not Joined"
21 | , example
22 | $ buttonGroup
23 | $$$
24 | [ button $$$ [ R.text "Button" ]
25 | , button
26 | $ secondary
27 | $$$ [ R.text "Button" ]
28 | ]
29 | , h2_ "Not Joined"
30 | , example
31 | $ buttonGroup
32 | $$$
33 | [ button $$$ [ R.text "Button" ]
34 | , button
35 | $ secondary
36 | $$$ [ R.text "Button" ]
37 | , button
38 | $ secondary
39 | $$$ [ R.text "Button" ]
40 | ]
41 | , h2_ "Not Joined"
42 | , example
43 | $ buttonGroup
44 | $$$
45 | [ button $$$ [ R.text "Button" ]
46 | , nativeSelect
47 | defaults
48 | { options = []
49 | , onChange = mkEffectFn1 \_ -> log "onChange"
50 | , value = "Foo bar"
51 | }
52 | , button
53 | $ secondary
54 | $$$ [ R.text "Button" ]
55 | ]
56 | , h2_ "Joined"
57 | , example
58 | $ buttonGroup
59 | $ joined
60 | $$$
61 | [ button
62 | $ secondary
63 | $$$ [ R.text "Button" ]
64 | , button
65 | $ secondary
66 | $$$ [ R.text "Button" ]
67 | ]
68 | , h2_ "Joined"
69 | , example
70 | $ buttonGroup
71 | $ joined
72 | $$$
73 | [ button
74 | $ secondary
75 | $$$ [ R.text "Button" ]
76 | , button
77 | $ secondary
78 | $$$ [ R.text "Button" ]
79 | , button
80 | $ secondary
81 | $$$ [ R.text "Button" ]
82 | ]
83 | ]
84 |
--------------------------------------------------------------------------------
/docs/Examples2/Clip.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Examples.Clip where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components (($$$))
6 | import Lumi.Components.Example (example)
7 | import Lumi.Components.Spacing (Space(..), vspace)
8 | import Lumi.Components2.Text as T
9 | import Lumi.Components2.Box (box)
10 | import Lumi.Components2.Clip (clip)
11 | import Lumi.Styles as S
12 | import React.Basic.Classic (JSX)
13 |
14 | docs :: JSX
15 | docs =
16 | box
17 | _
18 | { content =
19 | [ T.paragraph_ $$$ "The Clip component wraps the provided content with a grey border and a \"Copy\" button, which copies the text content into the system clipboard."
20 | , T.paragraph_ $$$ "If clipboard access is not allowed or not supported the text will be left highlighted, allowing the user to press ctrl+c manually. Only the plain text content is copied, not the HTML."
21 | , vspace S24
22 | , example
23 | $ clip
24 | $ _ { content =
25 | [ T.paragraph
26 | $ S.style_ (S.css { marginBottom: S.px 0 })
27 | $ T.truncate
28 | $$$ [ T.text $$$ "someone@email.com" ]
29 | ]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/docs/Examples2/Image.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Examples.Image where
2 |
3 | import Prelude
4 |
5 | import Data.Array as Array
6 | import Lumi.Components (($$$))
7 | import Lumi.Components.Example (example)
8 | import Lumi.Components.Spacing (Space(..), vspace)
9 | import Lumi.Components.Text (h4_)
10 | import Lumi.Components2.Image as Image
11 | import React.Basic.Classic (JSX)
12 |
13 | docs :: JSX
14 | docs =
15 | let flexo = "https://i.picsum.photos/id/985/1000/300.jpg?hmac=t_lmj43iuzwGqZaRMY1ee9udE_pCzfYLgCD49YrCPjw"
16 | -- "https://s3.amazonaws.com/lumi-blog/avatars/_x600/flexo.jpg"
17 | in Array.intercalate (vspace S16)
18 | [ h4_ "Image default (will respect image's original aspect ratio & dimensions)"
19 | , example
20 | $ Image.image
21 | $$$ flexo
22 | , h4_ "Image + resize { width: 40px, height: 120px }, the image will fill the height and width of its parent (object-fit: cover), maintaining its aspect ratio but cropping the image"
23 | , example
24 | $ Image.image
25 | $ Image.resize { width: 40, height: 120 }
26 | $$$ flexo
27 | , h4_ "Thumbnail default (will always have a square aspect ratio); and defaults to object-fit: cover"
28 | , example
29 | $ Image.thumbnail
30 | $$$ flexo
31 | , h4_ "Thumbnail default (will always have a square aspect ratio); but can be override with object-fit: contain"
32 | , example
33 | $ Image.thumbnail
34 | $ Image.contain
35 | $ _ { content = flexo
36 | }
37 | , h4_ "Thumbnail + resize 400px"
38 | , example
39 | $ Image.thumbnail
40 | $ Image.resizeSquare 400
41 | $$$ flexo
42 | , h4_ "Thumbnail + small"
43 | , example
44 | $ Image.thumbnail
45 | $ Image.small
46 | $$$ flexo
47 | , h4_ "Thumbnail + medium"
48 | , example
49 | $ Image.thumbnail
50 | $ Image.medium
51 | $$$ flexo
52 | , h4_ "Thumbnail + large"
53 | , example
54 | $ Image.thumbnail
55 | $ Image.large
56 | $$$ flexo
57 | , h4_ "Thumbnail + extra large"
58 | , example
59 | $ Image.thumbnail
60 | $ Image.extraLarge
61 | $$$ flexo
62 | , h4_ "Thumbnail + avatar (small)"
63 | , example
64 | $ Image.thumbnail
65 | $ Image.smallAvatar
66 | $$$ flexo
67 | , h4_ "Thumbnail + avatar (medium)"
68 | , example
69 | $ Image.thumbnail
70 | $ Image.mediumAvatar
71 | $$$ flexo
72 | , h4_ "Thumbnail + avatar (large)"
73 | , example
74 | $ Image.thumbnail
75 | $ Image.largeAvatar
76 | $$$ flexo
77 | , h4_ "Default placeholder (can be overriden)"
78 | , example
79 | $ Image.image
80 | $ Image.resize { width: 320, height: 240 }
81 | $$$ ""
82 | , example
83 | $ Image.thumbnail
84 | $ Image.medium
85 | $$$ ""
86 | ]
87 |
--------------------------------------------------------------------------------
/docs/Examples2/Link.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Examples.Link where
2 |
3 | import Prelude
4 | import Data.Maybe (Maybe(..))
5 | import Effect.Console (log)
6 | import Effect.Uncurried (mkEffectFn1)
7 | import Lumi.Components.Column (column_)
8 | import Lumi.Components.Example (example)
9 | import Lumi.Components.Text (body_)
10 | import Lumi.Components2.Link (link)
11 | import React.Basic.Classic (JSX)
12 | import React.Basic.DOM as R
13 | import Web.HTML.History (URL(..))
14 |
15 | docs :: JSX
16 | docs =
17 | column_
18 | [ example
19 | $ R.div
20 | { onClick: mkEffectFn1 \_ -> log "Propagated"
21 | , style: R.css { display: "flex", flexDirection: "column" }
22 | , children:
23 | [ link
24 | _
25 | { href = URL "#/link"
26 | , navigate = pure $ log "link clicked"
27 | , content = [ body_ "Click here" ]
28 | }
29 | , link
30 | _
31 | { href = URL "#/input"
32 | , target = Just "_blank"
33 | , content = [ body_ "This should open in a new tab" ]
34 | }
35 | ]
36 | }
37 | ]
38 |
--------------------------------------------------------------------------------
/docs/Examples2/QRCode.example.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Examples.QRCode where
2 |
3 | import Prelude
4 | import Data.Foldable (traverse_)
5 | import Data.Maybe (Maybe(..), fromMaybe)
6 | import Effect.Unsafe (unsafePerformEffect)
7 | import Lumi.Components.Example (example)
8 | import Lumi.Components.Input as Input
9 | import Lumi.Components.Spacing (Space(..), vspace)
10 | import Lumi.Components.Text (subsectionHeader_)
11 | import Lumi.Components2.Box (box)
12 | import Lumi.Components2.Link (link)
13 | import Lumi.Components2.QRCode (ErrorCorrectLevel(..), useQRCode)
14 | import Lumi.Styles as S
15 | import Lumi.Styles.Border as Border
16 | import Lumi.Styles.Box (FlexAlign(..))
17 | import Lumi.Styles.Box as Box
18 | import React.Basic.DOM.Events (capture, targetValue)
19 | import React.Basic.Hooks (JSX, component, useState, (/\))
20 | import React.Basic.Hooks as React
21 | import Web.HTML.History (URL(..))
22 |
23 | docs :: JSX
24 | docs =
25 | unit # unsafePerformEffect do
26 | component "QRCodeExample" \_ -> React.do
27 | value /\ setValue <- useState "https://www.lumi.com"
28 | pure
29 | $ box
30 | _
31 | { content =
32 | [ Input.input
33 | Input.text_
34 | { value = value
35 | , onChange = capture targetValue $ traverse_ (const >>> setValue)
36 | }
37 | , vspace S24
38 | , example
39 | $ qrcodeExample { value }
40 | ]
41 | }
42 |
43 | qrcodeExample :: { value :: String } -> JSX
44 | qrcodeExample =
45 | unsafePerformEffect do
46 | component "QRCode" \props -> React.do
47 | { qrcode, url } <- useQRCode ECLLow props.value
48 | pure
49 | $ box
50 | $ Box._align Center
51 | $ _
52 | { content =
53 | [ qrcode
54 | $ Border.border
55 | $ Border._round
56 | $ S.style_
57 | ( S.css
58 | { padding: S.px 16
59 | , width: S.px 140
60 | }
61 | )
62 | $ identity
63 | , vspace S8
64 | , link
65 | _
66 | { href = fromMaybe (URL "") url
67 | , download = Just "qrcode.svg"
68 | , content =
69 | [ subsectionHeader_ "Download SVG"
70 | ]
71 | }
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/docs/MUIColorScheme.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.MUIColorScheme where
2 |
3 | -- Copied from the deleted material UI color scheme from
4 | -- from https://github.com/purescript-contrib/purescript-colors/commit/0ec954c1c248ae601ee6c9eee0b60aa30f717241
5 |
6 | -- | Material Design color scheme, see
7 | -- | https://www.google.com/design/spec/style/color.html
8 | import Color (Color, rgb)
9 |
10 | red :: Color
11 | red = rgb 244 67 54
12 |
13 | pink :: Color
14 | pink = rgb 233 30 99
15 |
16 | purple :: Color
17 | purple = rgb 156 39 176
18 |
19 | deepPurple :: Color
20 | deepPurple = rgb 103 58 183
21 |
22 | indigo :: Color
23 | indigo = rgb 63 81 181
24 |
25 | blue :: Color
26 | blue = rgb 33 150 243
27 |
28 | lightBlue :: Color
29 | lightBlue = rgb 3 169 244
30 |
31 | cyan :: Color
32 | cyan = rgb 0 188 212
33 |
34 | teal :: Color
35 | teal = rgb 0 150 136
36 |
37 | green :: Color
38 | green = rgb 76 175 80
39 |
40 | lightGreen :: Color
41 | lightGreen = rgb 139 195 74
42 |
43 | lime :: Color
44 | lime = rgb 205 220 57
45 |
46 | yellow :: Color
47 | yellow = rgb 255 235 59
48 |
49 | amber :: Color
50 | amber = rgb 255 193 7
51 |
52 | orange :: Color
53 | orange = rgb 255 152 0
54 |
55 | deepOrange :: Color
56 | deepOrange = rgb 255 87 34
57 |
58 | brown :: Color
59 | brown = rgb 121 85 72
60 |
61 | grey :: Color
62 | grey = rgb 158 158 158
63 |
64 | blueGrey :: Color
65 | blueGrey = rgb 96 125 139
--------------------------------------------------------------------------------
/docs/ScrollManager.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { withRouter } from "react-router";
3 |
4 | // Delay a callback `fn` until the returned function
5 | // has not been called for `ms` milliseconds.
6 | const debounceFn = (ms, fn) => {
7 | let timeout = null;
8 | return () => {
9 | clearTimeout(timeout);
10 | timeout = setTimeout(fn, ms);
11 | };
12 | };
13 |
14 | // Retry an operation every `ms` milliseconds until
15 | // `fn` returns true or `maxTries` is reached.
16 | const retryFn = (ms, maxTries, fn) => {
17 | if (fn()) return;
18 |
19 | const innerRetryFn = currentTry => {
20 | if (currentTry > maxTries) return;
21 | setTimeout(() => {
22 | if (!fn()) {
23 | innerRetryFn(currentTry + 1);
24 | }
25 | }, ms);
26 | };
27 | innerRetryFn(2);
28 | };
29 |
30 | const restoreScroll = props => {
31 | const { history } = props;
32 | switch (history.action) {
33 | case "PUSH":
34 | // New forward navigation -- scroll to top
35 | window.scrollTo(0, 0);
36 | break;
37 | case "POP":
38 | // Back or forward navigation -- attempt scroll restore if necessary
39 | retryFn(props.onScrollDebounceMs, props.scrollRestoreMaxRetries, () => {
40 | // Short circuit if there's no scroll position to restore to
41 | if (!window.history.state) return true;
42 |
43 | const { scrollX, scrollY } = window.history.state;
44 | if (scrollX !== window.scrollX || scrollY !== window.scrollY) {
45 | // Scroll position out of sync -- attempt reset and loop
46 | // (if a render is unusually slow the scroll attempt may fail)
47 | window.scrollTo(scrollX, scrollY);
48 | return false;
49 | } else {
50 | // Scroll position restored
51 | return true;
52 | }
53 | });
54 | break;
55 | }
56 | };
57 |
58 | const ScrollManager = props => {
59 | useEffect(() => {
60 | // Save the scroll position any time scrolling stops
61 | const scrollListener = debounceFn(props.onScrollDebounceMs, () => {
62 | const { history, scrollX, scrollY } = window;
63 | if (
64 | !history.state ||
65 | scrollX !== history.state.scrollX ||
66 | scrollY !== history.state.scrollY
67 | ) {
68 | history.replaceState({ scrollX, scrollY }, "", history.location);
69 | }
70 | });
71 | window.addEventListener("scroll", scrollListener);
72 | restoreScroll(props);
73 | return () => {
74 | window.removeEventListener("scroll", scrollListener);
75 | };
76 | }, [props.onScrollDebounceMs, props.scrollRestoreMaxRetries]);
77 |
78 | return props.children;
79 | };
80 |
81 | ScrollManager.defaultProps = {
82 | onScrollDebounceMs: 50,
83 | scrollRestoreMaxRetries: 40
84 | };
85 |
86 | export default withRouter(ScrollManager);
87 |
--------------------------------------------------------------------------------
/docs/Utility/ReactRouter.js:
--------------------------------------------------------------------------------
1 | export { withRouter } from "react-router";
2 |
--------------------------------------------------------------------------------
/docs/Utility/ReactRouter.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Utility.ReactRouter where
2 |
3 | import Prelude
4 |
5 | import Effect.Uncurried (EffectFn1)
6 | import React.Basic.Classic (ReactComponent)
7 | import Web.HTML.History (URL)
8 |
9 | type RouterProps props =
10 | { history :: { push :: EffectFn1 URL Unit }
11 | , location :: { pathname :: String, search :: String, hash :: String }
12 | | props
13 | }
14 |
15 | foreign import withRouter
16 | :: forall props
17 | . ReactComponent (RouterProps props)
18 | -> ReactComponent { | props }
19 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Lumi Components
11 |
12 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/docs/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { HashRouter } from "react-router-dom";
4 | import ScrollManager from "./ScrollManager";
5 | import App from "./App";
6 |
7 | ReactDOM.render(
8 |
9 |
10 |
11 |
12 | ,
13 | document.getElementById("root")
14 | );
15 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "locked": {
5 | "lastModified": 1652776076,
6 | "narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=",
7 | "owner": "numtide",
8 | "repo": "flake-utils",
9 | "rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "numtide",
14 | "repo": "flake-utils",
15 | "type": "github"
16 | }
17 | },
18 | "nixpkgs": {
19 | "locked": {
20 | "lastModified": 1653326962,
21 | "narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=",
22 | "owner": "NixOS",
23 | "repo": "nixpkgs",
24 | "rev": "41cc1d5d9584103be4108c1815c350e07c807036",
25 | "type": "github"
26 | },
27 | "original": {
28 | "owner": "NixOS",
29 | "ref": "nixpkgs-unstable",
30 | "repo": "nixpkgs",
31 | "type": "github"
32 | }
33 | },
34 | "root": {
35 | "inputs": {
36 | "flake-utils": "flake-utils",
37 | "nixpkgs": "nixpkgs"
38 | }
39 | }
40 | },
41 | "root": "root",
42 | "version": 7
43 | }
44 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Provide an environment for working in this repo";
3 |
4 | # to handle mac and linux
5 | inputs.flake-utils.url = "github:numtide/flake-utils";
6 |
7 | # we want to use a consistent nixpkgs across developers.
8 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
9 |
10 | outputs = all@{ self, nixpkgs, flake-utils, ... }:
11 | flake-utils.lib.eachDefaultSystem (system:
12 | let
13 | pkgs = import nixpkgs {
14 | inherit system;
15 | };
16 | packages =
17 | let
18 | # everything we want available in our development environment that isn't managed by
19 | # npm, spago
20 | # we do not differentiate between libraries needed for building and tools at the moment.
21 | sharedPackages = with pkgs; [
22 | nodejs-16_x
23 | ];
24 | in
25 | sharedPackages;
26 | in {
27 | # produce our actual shell
28 | devShell = pkgs.mkShell rec {
29 | # make our packages available
30 | buildInputs = packages;
31 | };
32 | }
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "purescript-lumi-components",
3 | "version": "0.0.0",
4 | "description": "Lumi UI components",
5 | "private": true,
6 | "main": "index.js",
7 | "homepage": "https://lumihq.github.io/purescript-lumi-components",
8 | "repository": "lumihq/purescript-lumi-components",
9 | "files": [
10 | "src/components"
11 | ],
12 | "scripts": {
13 | "start": "run-s watch",
14 | "prewatch": "run-s eslint build:purs",
15 | "watch": "run-p watch:js watch:purs",
16 | "watch:js": "webpack-dev-server -wd --port 3000 --host 0.0.0.0",
17 | "watch:purs": "pulp -w build -I docs",
18 | "prebuild": "run-s eslint",
19 | "build": "run-s build:purs build:js",
20 | "build:js": "webpack -p",
21 | "build:purs": "pulp build -I docs",
22 | "setup": "npm install && bower install && spago install",
23 | "eslint": "eslint --ext .js,.jsx src docs webpack.config.js",
24 | "pscid": "pscid -I docs",
25 | "pscid:build": "run-s build:purs",
26 | "predeploy": "run-s build",
27 | "deploy": "gh-pages -d build",
28 | "clean": "rm -rf build output generated-docs .psa-stash .pulp-cache .psci-modules",
29 | "clean:deps": "rm -rf node_modules bower_components",
30 | "clean:everything": "run-s clean clean:deps"
31 | },
32 | "dependencies": {
33 | "@emotion/react": "^11.9.3",
34 | "big-integer": "^1.6.48",
35 | "jss": "^10.3.0",
36 | "jss-preset-default": "^10.3.0",
37 | "qrcode.react": "^3.1.0",
38 | "react": "^18.0.0",
39 | "react-dnd": "^11.0.0",
40 | "react-dnd-html5-backend": "^11.0.0",
41 | "react-dom": "^18.0.0",
42 | "react-media-hook": "^0.5.0",
43 | "react-password-strength-bar": "^0.4.1"
44 | },
45 | "devDependencies": {
46 | "@babel/core": "^7.10.4",
47 | "@babel/plugin-proposal-class-properties": "^7.10.4",
48 | "@babel/plugin-proposal-object-rest-spread": "^7.10.4",
49 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
50 | "@babel/preset-env": "^7.10.4",
51 | "@babel/preset-react": "^7.10.4",
52 | "babel-eslint": "^10.1.0",
53 | "babel-loader": "^8.1.0",
54 | "bower": "^1.8.4",
55 | "change-case": "^4.1.1",
56 | "chokidar": "^3.5.3",
57 | "clean-webpack-plugin": "^3.0.0",
58 | "eslint": "^7.3.1",
59 | "eslint-config-prettier": "^6.11.0",
60 | "eslint-plugin-prettier": "^3.1.4",
61 | "eslint-plugin-react": "^7.20.3",
62 | "gh-pages": "^3.1.0",
63 | "git-revision-webpack-plugin": "^3.0.6",
64 | "html-webpack-plugin": "^4.3.0",
65 | "npm-run-all": "^4.1.5",
66 | "null-loader": "^4.0.0",
67 | "purescript": "0.15.2",
68 | "prettier": "^2.0.5",
69 | "pscid": "^2.9.3",
70 | "pulp": "^16.0.0",
71 | "purescript-psa": "^0.7.3",
72 | "react-loadable": "^5.5.0",
73 | "react-media": "^1.10.0",
74 | "react-router": "^5.2.0",
75 | "react-router-dom": "^5.2.0",
76 | "resource-hints-webpack-plugin": "^0.0.2",
77 | "spago": "^0.21.0",
78 | "script-ext-html-webpack-plugin": "^2.1.4",
79 | "source-map-loader": "^1.0.1",
80 | "terser-webpack-plugin": "^3.0.6",
81 | "webpack": "^4.43.0",
82 | "webpack-bundle-analyzer": "^3.8.0",
83 | "webpack-cli": "^3.3.12",
84 | "webpack-dev-server": "^3.11.0",
85 | "webpack-manifest-plugin": "^2.2.0",
86 | "webpack-subresource-integrity": "^1.4.1"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/packages.dhall:
--------------------------------------------------------------------------------
1 | let upstream =
2 | https://github.com/purescript/package-sets/releases/download/psc-0.15.9-20230629/packages.dhall
3 | sha256:f91d36c7e4793fe4d7e042c57fef362ff3f9e9ba88454cd38686701e30bf545a
4 |
5 | let additions =
6 | { foreign-generic =
7 | { dependencies =
8 | [ "arrays"
9 | , "assert"
10 | , "bifunctors"
11 | , "console"
12 | , "control"
13 | , "effect"
14 | , "either"
15 | , "exceptions"
16 | , "foldable-traversable"
17 | , "foreign"
18 | , "foreign-object"
19 | , "identity"
20 | , "lists"
21 | , "maybe"
22 | , "newtype"
23 | , "partial"
24 | , "prelude"
25 | , "record"
26 | , "strings"
27 | , "transformers"
28 | , "tuples"
29 | , "typelevel-prelude"
30 | , "unsafe-coerce"
31 | ]
32 | , repo = "https://github.com/lumihq/purescript-foreign-generic.git"
33 | , version = "proxy-instances"
34 | }
35 | , promises =
36 | { dependencies =
37 | [ "console"
38 | , "datetime"
39 | , "effect"
40 | , "exceptions"
41 | , "functions"
42 | , "prelude"
43 | , "transformers"
44 | , "arrays"
45 | , "either"
46 | , "foldable-traversable"
47 | , "unfoldable"
48 | , "maybe"
49 | ]
50 | , repo = "https://github.com/clipperz/purescript-promises.git"
51 | , version = "a87a9b1cbab5446574a289a770c4f26bcd9dd21d"
52 | }
53 | }
54 |
55 | in (upstream // additions)
56 | with metadata.version = "v0.15.2"
57 |
--------------------------------------------------------------------------------
/spago.dhall:
--------------------------------------------------------------------------------
1 | { name = "lumi-components"
2 | , packages = ./packages.dhall
3 | , sources = [ "src/**/*.purs", "test/**/*.purs" ]
4 | , license = "Apache-2.0"
5 | , repository = "https://github.com/lumihq/purescript-lumi-components"
6 | , dependencies =
7 | [ "aff"
8 | , "aff-coroutines"
9 | , "arrays"
10 | , "bifunctors"
11 | , "colors"
12 | , "console"
13 | , "control"
14 | , "coroutines"
15 | , "datetime"
16 | , "effect"
17 | , "either"
18 | , "enums"
19 | , "exceptions"
20 | , "fixed-precision"
21 | , "foldable-traversable"
22 | , "foreign"
23 | , "foreign-object"
24 | , "free"
25 | , "heterogeneous"
26 | , "integers"
27 | , "js-timers"
28 | , "js-uri"
29 | , "maybe"
30 | , "media-types"
31 | , "newtype"
32 | , "nonempty"
33 | , "nullable"
34 | , "numbers"
35 | , "ordered-collections"
36 | , "parallel"
37 | , "partial"
38 | , "prelude"
39 | , "profunctor-lenses"
40 | , "react-basic"
41 | , "react-basic-classic"
42 | , "react-basic-dnd"
43 | , "react-basic-dom"
44 | , "react-basic-emotion"
45 | , "react-basic-hooks"
46 | , "record"
47 | , "refs"
48 | , "simple-json"
49 | , "st"
50 | , "strings"
51 | , "tailrec"
52 | , "transformers"
53 | , "tuples"
54 | , "unsafe-coerce"
55 | , "unsafe-reference"
56 | , "web-dom"
57 | , "web-events"
58 | , "web-file"
59 | , "web-html"
60 | , "web-storage"
61 | , "web-uievents"
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/src/JSS/JSS.js:
--------------------------------------------------------------------------------
1 | import * as jssGlobal from 'jss';
2 | export {default as preset} from 'jss-preset-default';
3 |
4 | export const createInstance_ = function (p) {
5 | return jssGlobal.create(p());
6 | };
7 |
8 | export const createStyleSheet_ = function (jss, styles) {
9 | return jss.createStyleSheet(styles);
10 | };
11 |
12 | export const globalAttachStyleSheet_ = function (stylesheet) {
13 | return stylesheet.attach();
14 | };
15 |
16 | export const toStringStyleSheet = function (stylesheet) {
17 | return stylesheet.toString();
18 | };
19 |
--------------------------------------------------------------------------------
/src/JSS/JSS.purs:
--------------------------------------------------------------------------------
1 | module JSS
2 | ( JSSConfig
3 | , JSSInstance
4 | , JSSStyleSheet
5 | , JSS
6 | , JSSValue
7 | , jss
8 | , createInstance
9 | , preset
10 | , createStyleSheet
11 | , globalAttachStyleSheet
12 | , toStringStyleSheet
13 | , important
14 | , jssValue
15 | ) where
16 |
17 | import Prelude
18 |
19 | import Effect (Effect)
20 | import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)
21 | import Unsafe.Coerce (unsafeCoerce)
22 |
23 | foreign import data JSSConfig :: Type
24 |
25 | foreign import data JSSInstance :: Type
26 |
27 | foreign import data JSSStyleSheet :: Type
28 |
29 | foreign import data JSS :: Type
30 |
31 | foreign import data JSSValue :: Type
32 |
33 | jss :: forall styles. {| styles } -> JSS
34 | jss = unsafeCoerce
35 |
36 | createInstance :: Effect JSSConfig -> Effect JSSInstance
37 | createInstance = runEffectFn1 createInstance_
38 |
39 | foreign import createInstance_ :: EffectFn1 (Effect JSSConfig) JSSInstance
40 |
41 | foreign import preset :: Effect JSSConfig
42 |
43 | createStyleSheet :: JSSInstance -> JSS -> Effect JSSStyleSheet
44 | createStyleSheet = runEffectFn2 createStyleSheet_
45 |
46 | foreign import createStyleSheet_ :: EffectFn2 JSSInstance JSS JSSStyleSheet
47 |
48 | globalAttachStyleSheet :: JSSStyleSheet -> Effect Unit
49 | globalAttachStyleSheet = runEffectFn1 globalAttachStyleSheet_
50 |
51 | foreign import globalAttachStyleSheet_ :: EffectFn1 JSSStyleSheet Unit
52 |
53 | foreign import toStringStyleSheet :: JSSStyleSheet -> String
54 |
55 | -- | Allows the mixing of types in the values passed to JSS.
56 | -- | For example, to create `!important` properties JSS
57 | -- | sometimes requires heterogeneous arrays:
58 | -- |
59 | -- | ```purs
60 | -- | [ [ "10px" ], "!important" ]
61 | -- | ```
62 | jssValue :: forall a. a -> JSSValue
63 | jssValue = unsafeCoerce
64 |
65 | important :: forall a. a -> JSSValue
66 | important a = jssValue [ jssValue [ a ], jssValue "!important" ]
67 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Badge.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Badge where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Effect.Unsafe (unsafePerformEffect)
7 | import JSS (JSS, jss)
8 | import Lumi.Components.Color (Color, colors)
9 | import Lumi.Components.Text (lumiSubtextFontSize)
10 | import React.Basic.Classic (JSX, element)
11 | import React.Basic.DOM as R
12 |
13 | type BadgeProps =
14 | { background :: Color
15 | , color :: Color
16 | , style :: R.CSS
17 | , text :: String
18 | }
19 |
20 | badge_ :: String -> JSX
21 | badge_ = badge <<< defaults { text = _ }
22 |
23 | badge :: BadgeProps -> JSX
24 | badge = \{ background, color, style, text } ->
25 | lumiBadgeElement
26 | { style: R.mergeStyles
27 | [ R.css
28 | { color: cssStringHSLA color
29 | , background: cssStringHSLA background
30 | }
31 | , style
32 | ]
33 | , children: R.text text
34 | }
35 | where
36 | lumiBadgeElement = element (unsafePerformEffect $ R.unsafeCreateDOMComponent "lumi-badge")
37 |
38 | defaults :: BadgeProps
39 | defaults =
40 | { background: colors.black5
41 | , color: colors.secondary
42 | , style: R.css {}
43 | , text: ""
44 | }
45 |
46 | styles :: JSS
47 | styles = jss
48 | { "@global":
49 | { "lumi-badge":
50 | { alignSelf: "baseline"
51 | , display: "inline"
52 | , fontSize: lumiSubtextFontSize
53 | , lineHeight: "17px"
54 | , borderRadius: "9px"
55 | , marginTop: "2px"
56 | , padding: "0px 6px"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Border.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Border where
2 |
3 | import Prelude
4 |
5 | import Color (toHexString)
6 | import Effect.Unsafe (unsafePerformEffect)
7 | import JSS (JSS, jss)
8 | import Lumi.Components.Color (colors)
9 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
10 | import React.Basic.DOM (unsafeCreateDOMComponent)
11 |
12 | data BorderStyle
13 | = SquareTop
14 | | SquareBottom
15 | | Round
16 | | Square
17 | | Top
18 | | Bottom
19 |
20 | toStyle :: BorderStyle -> String
21 | toStyle SquareTop = "square-top"
22 | toStyle SquareBottom = "square-bottom"
23 | toStyle Round = "round"
24 | toStyle Square = "square"
25 | toStyle Top = "top"
26 | toStyle Bottom = "bottom"
27 |
28 | component :: Component BorderProps
29 | component = createComponent "Border"
30 |
31 | type BorderProps =
32 | { children :: JSX
33 | , borderStyle :: BorderStyle
34 | }
35 |
36 | border :: BorderProps -> JSX
37 | border = makeStateless component render
38 | where
39 | lumiBorderElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-border")
40 |
41 | render props =
42 | lumiBorderElement
43 | { "data-border-style": toStyle props.borderStyle
44 | , children: props.children
45 | }
46 |
47 | borderSquare :: JSX -> JSX
48 | borderSquare children =
49 | border
50 | { children
51 | , borderStyle: Square
52 | }
53 |
54 | borderRound :: JSX -> JSX
55 | borderRound children =
56 | border
57 | { children
58 | , borderStyle: Round
59 | }
60 |
61 | borderSquareTop :: JSX -> JSX
62 | borderSquareTop children =
63 | border
64 | { children
65 | , borderStyle: SquareTop
66 | }
67 |
68 | borderSquareBottom :: JSX -> JSX
69 | borderSquareBottom children =
70 | border
71 | { children
72 | , borderStyle: SquareBottom
73 | }
74 |
75 | borderTop :: JSX -> JSX
76 | borderTop children =
77 | border
78 | { children
79 | , borderStyle: Top
80 | }
81 |
82 | borderBottom :: JSX -> JSX
83 | borderBottom children =
84 | border
85 | { children
86 | , borderStyle: Bottom
87 | }
88 |
89 |
90 | styles :: JSS
91 | styles = jss
92 | { "@global":
93 | { "lumi-border":
94 | { borderColor: toHexString colors.black3
95 | , borderStyle: "solid"
96 | , borderWidth: "1px"
97 | , padding: "16px"
98 | , overflow: "hidden"
99 | , "&[data-border-style='round']":
100 | { borderRadius: "5px"
101 | }
102 | , "&[data-border-style='square']":
103 | { borderRadius: "0px"
104 | }
105 | , "&[data-border-style='square-bottom']":
106 | { borderTopRightRadius: "5px"
107 | , borderTopLeftRadius: "5px"
108 | , borderBottomRightRadius: "0px"
109 | , borderBottomLeftRadius: "0px"
110 | }
111 | , "&[data-border-style='square-top']":
112 | { borderTopRightRadius: "0px"
113 | , borderTopLeftRadius: "0px"
114 | , borderBottomRightRadius: "5px"
115 | , borderBottomLeftRadius: "5px"
116 | }
117 | , "&[data-border-style='top']":
118 | { border: "inherit"
119 | , borderTop: "1px solid " <> toHexString colors.black3
120 | , paddingLeft: "0px"
121 | , paddingRight: "0px"
122 | }
123 | , "&[data-border-style='bottom']":
124 | { border: "inherit"
125 | , borderBottom: "1px solid " <> toHexString colors.black3
126 | , paddingLeft: "0px"
127 | , paddingRight: "0px"
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Lumi/Components/ButtonGroup.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.ButtonGroup where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import JSS (JSS, jss)
7 | import Lumi.Components.ZIndex (ziButtonGroup)
8 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
9 | import React.Basic.DOM (unsafeCreateDOMComponent, CSS)
10 |
11 | type ButtonGroupProps =
12 | { children :: Array JSX
13 | , style :: CSS
14 | , joined :: Boolean
15 | }
16 |
17 | component :: Component ButtonGroupProps
18 | component = createComponent "ButtonGroup"
19 |
20 | buttonGroup :: ButtonGroupProps -> JSX
21 | buttonGroup = makeStateless component render
22 | where
23 | render props =
24 | buttonGroupElement
25 | { "data-joined": props.joined
26 | , style: props.style
27 | , children: map renderChild props.children
28 | }
29 | where
30 | renderChild child =
31 | buttonGroupChildElement { children: [ child ] }
32 |
33 | buttonGroupElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-button-group")
34 | buttonGroupChildElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-button-group-child")
35 |
36 | styles :: JSS
37 | styles = jss
38 | { "@global":
39 | { "lumi-button-group":
40 | { height: "100%"
41 | , display: "flex"
42 | , flexFlow: "row"
43 |
44 | , "&[data-joined=false] > lumi-button-group-child":
45 | { marginRight: 10
46 | , "&:last-child": { marginRight: 0 }
47 | }
48 |
49 | , "&[data-joined=true] > lumi-button-group-child":
50 | { "&:not(:last-child) button.lumi":
51 | { marginRight: -1
52 | , borderTopRightRadius: 0
53 | , borderBottomRightRadius: 0
54 | }
55 | , "&:not(:first-child) button.lumi":
56 | { borderTopLeftRadius: 0
57 | , borderBottomLeftRadius: 0
58 | }
59 | , "&:focus": { zIndex: ziButtonGroup }
60 | , "&:hover": { zIndex: ziButtonGroup }
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Color.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Color
2 | ( Color
3 | , ColorName
4 | , ColorMap
5 | , colors
6 | , colorNames
7 | , shade
8 | ) where
9 |
10 | import Prelude
11 |
12 | import Color (darken, desaturate, lighten, rgb, rgba)
13 | import Color as C
14 | import Data.Newtype (class Newtype)
15 |
16 | type Color = C.Color
17 |
18 | newtype ColorName = ColorName String
19 |
20 | derive instance newtypeColorName :: Newtype ColorName _
21 |
22 | type ColorMap a =
23 | { black :: a
24 | , black1 :: a
25 | , black2 :: a
26 | , black3 :: a
27 | , black4 :: a
28 | , black5 :: a
29 | , black6 :: a
30 | , black7 :: a
31 | , black8 :: a
32 | , primary :: a
33 | , primary1 :: a
34 | , primary2 :: a
35 | , primary3 :: a
36 | , primary4 :: a
37 | , secondary :: a
38 | , accent1 :: a
39 | , accent11 :: a
40 | , accent12 :: a
41 | , accent13 :: a
42 | , accent2 :: a
43 | , accent21 :: a
44 | , accent22 :: a
45 | , accent23 :: a
46 | , accent3 :: a
47 | , accent31 :: a
48 | , accent32 :: a
49 | , accent33 :: a
50 | , white :: a
51 | , transparent :: a
52 | }
53 |
54 | colors :: ColorMap Color
55 | colors =
56 | { black: rgb 0x23 0x22 0x20
57 | , black1: rgb 0x91 0x90 0x8d
58 | , black2: rgb 0xbe 0xbc 0xb9
59 | , black3: rgb 0xdd 0xdd 0xdc
60 | , black4: rgb 0xe7 0xe6 0xe5
61 | , black5: rgb 0xf5 0xf4 0xf2
62 | , black6: rgb 0xf7 0xf6 0xf4
63 | , black7: rgb 0xf9 0xf8 0xf7
64 | , black8: rgb 0xfb 0xfa 0xf9
65 | , primary: rgb 0 150 212
66 | , primary1: rgb 151 221 243
67 | , primary2: rgb 181 230 246
68 | , primary3: rgb 211 241 250
69 | , primary4: rgb 235 247 253
70 | , secondary: rgb 0x91 0x90 0x8d
71 | , accent1: rgb 0x49 0xb8 0x60
72 | , accent11: rgb 0x78 0xdd 0x8d
73 | , accent12: rgb 0xbc 0xee 0xc6
74 | , accent13: rgb 0xe4 0xf5 0xe7
75 | , accent2: rgb 0xff 0xa5 0x02
76 | , accent21: rgb 0xff 0xb9 0x67
77 | , accent22: rgb 0xff 0xdc 0xb3
78 | , accent23: rgb 0xff 0xee 0xd9
79 | , accent3: rgb 0xf1 0x50 0x0d
80 | , accent31: rgb 0xf7 0x96 0x6d
81 | , accent32: rgb 0xfb 0xca 0xb6
82 | , accent33: rgb 0xfd 0xe5 0xdb
83 | , white: rgb 0xff 0xff 0xff
84 | , transparent: rgba 0 0 0 0.0
85 | }
86 |
87 | colorNames :: ColorMap ColorName
88 | colorNames =
89 | { black: ColorName "black"
90 | , black1: ColorName "black-1"
91 | , black2: ColorName "black-2"
92 | , black3: ColorName "black-3"
93 | , black4: ColorName "black-4"
94 | , black5: ColorName "black-5"
95 | , black6: ColorName "black-6"
96 | , black7: ColorName "black-7"
97 | , black8: ColorName "black-8"
98 | , primary: ColorName "primary"
99 | , primary1: ColorName "primary-1"
100 | , primary2: ColorName "primary-2"
101 | , primary3: ColorName "primary-3"
102 | , primary4: ColorName "primary-4"
103 | , secondary: ColorName "secondary"
104 | , accent1: ColorName "accent-1"
105 | , accent11: ColorName "accent-1-1"
106 | , accent12: ColorName "accent-1-2"
107 | , accent13: ColorName "accent-1-3"
108 | , accent2: ColorName "accent-2"
109 | , accent21: ColorName "accent-2-1"
110 | , accent22: ColorName "accent-2-2"
111 | , accent23: ColorName "accent-2-3"
112 | , accent3: ColorName "accent-3"
113 | , accent31: ColorName "accent-3-1"
114 | , accent32: ColorName "accent-3-2"
115 | , accent33: ColorName "accent-3-3"
116 | , white: ColorName "white"
117 | , transparent: ColorName "transparent"
118 | }
119 |
120 | shade ::
121 | { hue :: Color, white :: Color, black :: Color } ->
122 | { black :: Color
123 | , grey1 :: Color
124 | , grey2 :: Color
125 | , hue :: Color
126 | , hueDarker :: Color
127 | , hueDarkest :: Color
128 | , hueDisabled :: Color
129 | , white :: Color
130 | }
131 | shade { hue, white, black } =
132 | let
133 | hueDarker = darken 0.1 hue
134 | hueDarkest = darken 0.15 hue
135 | hueDisabled = lighten 0.4137 $ desaturate 0.1972 hue
136 | grey1 = lighten 0.7 black
137 | grey2 = lighten 0.78 black
138 | in
139 | { hue, hueDarker, hueDarkest, hueDisabled, grey1, grey2, white, black }
140 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Column.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Column where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import JSS (JSS, jss)
7 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
8 | import React.Basic.DOM (CSS, css, unsafeCreateDOMComponent)
9 |
10 | component :: Component ColumnProps
11 | component = createComponent "Column"
12 |
13 | type ColumnProps =
14 | { children :: Array JSX
15 | , style :: CSS
16 | }
17 |
18 | column :: ColumnProps -> JSX
19 | column = makeStateless component lumiColumnElement
20 | where
21 | lumiColumnElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-column")
22 |
23 | column_ :: Array JSX -> JSX
24 | column_ children = column { children, style: css {} }
25 |
26 | columnSelfStretch :: Array JSX -> JSX
27 | columnSelfStretch children = column
28 | { style: css { alignSelf: "stretch" }
29 | , children
30 | }
31 |
32 | responsiveColumn :: ColumnProps -> JSX
33 | responsiveColumn = makeStateless component lumiResponsiveColumnElement
34 | where
35 | lumiResponsiveColumnElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-responsive-column")
36 |
37 | responsiveColumn_ :: Array JSX -> JSX
38 | responsiveColumn_ children = responsiveColumn { children, style: css {} }
39 |
40 | styles :: JSS
41 | styles = jss
42 | { "@global":
43 | { "lumi-column":
44 | { boxSizing: "border-box"
45 | , display: "flex"
46 | , flexDirection: "column"
47 | }
48 |
49 | , "lumi-responsive-column":
50 | { boxSizing: "border-box"
51 | , display: "flex"
52 | , flexDirection: "column"
53 |
54 | , "@media (max-width: 860px)":
55 | { flexDirection: "row"
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Details.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Details where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import JSS (JSS, jss)
7 | import Lumi.Components.Text (body_)
8 | import React.Basic.DOM as R
9 | import React.Basic.Hooks (JSX, component)
10 |
11 | type DetailsProps =
12 | { summary :: JSX
13 | , expanded :: JSX
14 | , defaultOpen :: Boolean
15 | }
16 |
17 | details :: DetailsProps -> JSX
18 | details = unsafePerformEffect do
19 | component "Details" \props ->
20 | pure $ R.details
21 | { className: "lumi"
22 | , children:
23 | [ R.summary
24 | { className: "lumi"
25 | , children: [ props.summary ]
26 | }
27 | , props.expanded
28 | ]
29 | }
30 |
31 | defaults :: DetailsProps
32 | defaults =
33 | { summary: body_ "Details"
34 | , expanded: mempty
35 | , defaultOpen: false
36 | }
37 |
38 | styles :: JSS
39 | styles = jss
40 | { "@global":
41 | { "details.lumi":
42 | { "& > summary.lumi":
43 | { listStyle: "none"
44 |
45 | , "&:focus":
46 | { outline: "none"
47 | }
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Divider.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Divider where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Effect.Unsafe (unsafePerformEffect)
7 | import JSS (JSS, jss)
8 | import Lumi.Components.Color (colors)
9 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
10 | import React.Basic.DOM as R
11 |
12 | type DividerProps =
13 | { style :: R.CSS
14 | }
15 |
16 | component :: Component DividerProps
17 | component = createComponent "Divider"
18 |
19 | divider :: DividerProps -> JSX
20 | divider = makeStateless component $ R.hr <<< mapProps
21 | where
22 | mapProps props =
23 | { className: "lumi"
24 | , style: props.style
25 | }
26 |
27 | divider_ :: JSX
28 | divider_ = divider { style: R.css {} }
29 |
30 | flexDivider :: DividerProps -> JSX
31 | flexDivider = \{ style } -> lumiFlexDividerElement { style }
32 | where
33 | lumiFlexDividerElement = element (unsafePerformEffect $ R.unsafeCreateDOMComponent "lumi-flex-divider")
34 |
35 | flexDivider_ :: JSX
36 | flexDivider_ = flexDivider { style: R.css {} }
37 |
38 | styles :: JSS
39 | styles = jss
40 | { "@global":
41 | { "hr.lumi":
42 | { height: "1px"
43 | , alignSelf: "stretch"
44 | , color: cssStringHSLA colors.black4
45 | , background: cssStringHSLA colors.black4
46 | , fontSize: "0"
47 | , border: "0"
48 | , flexShrink: "0"
49 | , flexBasis: "1px"
50 | }
51 |
52 | , "lumi-flex-divider":
53 | { alignSelf: "stretch"
54 | , color: cssStringHSLA colors.black4
55 | , background: cssStringHSLA colors.black4
56 | , fontSize: "0"
57 | , border: "0"
58 | , flexShrink: "0"
59 | , flexBasis: "1px"
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Lumi/Components/DropdownButton.js:
--------------------------------------------------------------------------------
1 | export const checkIsEventTargetInTree = function (domNode, e) {
2 | return e.target === domNode || domNode.contains(e.target);
3 | };
4 |
--------------------------------------------------------------------------------
/src/Lumi/Components/FixedPrecisionInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Input(props) {
4 | var this_ = this;
5 | this_.bindRef = function (input) {
6 | this_.inputRef = input;
7 | };
8 | this_.onChange = function (e) {
9 | var target = e.target;
10 | var valueStr = target.value;
11 | var validationError = props.validate(valueStr);
12 | if (validationError) {
13 | target.setCustomValidity(validationError);
14 | return;
15 | }
16 | var value = parseFloat(valueStr);
17 | if (!isNaN(value)) {
18 | if (value > props.max) {
19 | target.setCustomValidity(
20 | "Value must be less than or equal to " + props.max + "."
21 | );
22 | } else if (value < props.min) {
23 | target.setCustomValidity(
24 | "Value must be greater than or equal to " + props.min + "."
25 | );
26 | } else {
27 | target.setCustomValidity("");
28 | }
29 | }
30 | };
31 | return this_;
32 | }
33 |
34 | Input.prototype = Object.create(React.Component.prototype);
35 |
36 | Input.displayName = "FixedPrecisionInputInner";
37 |
38 | Input.prototype.componentWillReceiveProps = function (newProps) {
39 | if (this.inputRef != null && this.inputRef.value !== newProps.defaultValue) {
40 | this.inputRef.value = newProps.defaultValue;
41 | }
42 | };
43 |
44 | Input.prototype.render = function () {
45 | return React.createElement(
46 | "input",
47 | Object.assign({}, this.props, {
48 | ref: this.bindRef,
49 | onChange: this.onChange,
50 | validate: undefined
51 | })
52 | );
53 | };
54 |
55 | export const input_ = Input;
56 |
57 | export const cancelWhenNotMatch = function (regex, e) {
58 | if (e.cancelable && !e.defaultPrevented && !regex.test(e.key)) {
59 | e.preventDefault();
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Form/Defaults.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Form.Defaults
2 | ( class FormDefaults
3 | , formDefaults
4 | , class FormDefaultsRecord
5 | , formDefaultsRecordBuilder
6 | ) where
7 |
8 | import Prelude
9 |
10 | import Data.Array.NonEmpty (NonEmptyArray)
11 | import Data.Either (Either(..))
12 | import Data.Map (Map)
13 | import Data.Map as Map
14 | import Data.Set (Set)
15 | import Data.Set as Set
16 | import Data.Maybe (Maybe(..))
17 | import Data.Symbol (class IsSymbol)
18 | import Lumi.Components.Form.Validation (Validated(..))
19 | import Prim.Row (class Cons, class Lacks)
20 | import Prim.RowList (class RowToList, Cons, Nil)
21 | import Record.Builder (Builder)
22 | import Record.Builder as Builder
23 | import Type.Proxy (Proxy(..))
24 | import Foreign.Object (Object)
25 | import Foreign.Object as Object
26 |
27 | -- | Provides default values for primitive data types to be used as initial
28 | -- | values in form builders.
29 | -- |
30 | -- | For example:
31 | -- |
32 | -- | ```purescript
33 | -- | build_ formDefaults ado
34 | -- | firstName <- focus_ (prop (Proxy :: Proxy "firstName")) textbox
35 | -- | lastName <- focus_ (prop (Proxy :: Proxy "lastName")) textbox
36 | -- | in { firstName, lastName }
37 | -- | ```
38 | class FormDefaults a where
39 | formDefaults :: a
40 |
41 | instance formDefaultsUnit :: FormDefaults Unit where formDefaults = unit
42 | instance formDefaultsBoolean :: FormDefaults Boolean where formDefaults = false
43 | instance formDefaultsNumber :: FormDefaults Number where formDefaults = 0.0
44 | instance formDefaultsInt :: FormDefaults Int where formDefaults = 0
45 | instance formDefaultsString :: FormDefaults String where formDefaults = ""
46 | instance formDefaultsArray :: FormDefaults (Array a) where formDefaults = []
47 | instance formDefaultsSet :: FormDefaults (Set a) where formDefaults = Set.empty
48 | instance formDefaultsMap :: FormDefaults (Map k a) where formDefaults = Map.empty
49 | instance formDefaultsObject :: FormDefaults (Object a) where formDefaults = Object.empty
50 |
51 | instance formDefaultsNonEmptyArray :: FormDefaults a => FormDefaults (NonEmptyArray a) where
52 | formDefaults = pure formDefaults
53 |
54 | instance formDefaultsProxy :: FormDefaults (Proxy a) where
55 | formDefaults = Proxy
56 |
57 | instance formDefaultsMaybe :: FormDefaults (Maybe a) where
58 | formDefaults = Nothing
59 |
60 | instance formDefaultsEither :: FormDefaults a => FormDefaults (Either a b) where
61 | formDefaults = Left formDefaults
62 |
63 | instance formDefaultsValidated :: FormDefaults a => FormDefaults (Validated a) where
64 | formDefaults = Fresh formDefaults
65 |
66 | instance formDefaultsRecord ::
67 | ( RowToList r rl
68 | , FormDefaultsRecord rl () r
69 | ) => FormDefaults (Record r) where
70 | formDefaults = Builder.build (formDefaultsRecordBuilder (Proxy :: Proxy rl)) {}
71 |
72 | --
73 | class FormDefaultsRecord rl r_ r | rl -> r_ r where
74 | formDefaultsRecordBuilder :: Proxy rl -> Builder { | r_ } { | r }
75 |
76 | instance formDefaultsRecordNil :: FormDefaultsRecord Nil () () where
77 | formDefaultsRecordBuilder _ = identity
78 |
79 | instance formDefaultsRecordCons ::
80 | ( IsSymbol l
81 | , FormDefaults a
82 | , FormDefaultsRecord tail r_ tailR
83 | , Lacks l tailR
84 | , Cons l a tailR r
85 | ) => FormDefaultsRecord (Cons l a tail) r_ r where
86 | formDefaultsRecordBuilder _ = head <<< tail
87 | where
88 | head = Builder.insert (Proxy :: Proxy l) (formDefaults :: a)
89 | tail = formDefaultsRecordBuilder (Proxy :: Proxy tail)
90 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Icon.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Icon where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import JSS (JSS, jss)
7 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
8 | import React.Basic.DOM (CSS, css, unsafeCreateDOMComponent)
9 |
10 | data IconType
11 | = ArrowDown
12 | | ArrowLeft
13 | | ArrowRight
14 | | ArrowUp
15 | | Bin
16 | | Cart
17 | | Check
18 | | Close
19 | | Currency
20 | | Grid
21 | | Hamburger
22 | | Indeterminate
23 | | Info
24 | | List
25 | | Minus
26 | | Overflow
27 | | Percent
28 | | Plus
29 | | Remove
30 | | Search
31 | | Rearrange
32 |
33 | instance showIconType :: Show IconType where
34 | show ArrowDown = "↓"
35 | show ArrowLeft = "←"
36 | show ArrowRight = "→"
37 | show ArrowUp = "↑"
38 | show Bin = "🗑"
39 | show Cart = "🛒"
40 | show Check = "✓"
41 | show Close = "X"
42 | show Currency = "$"
43 | show Grid = "⌗"
44 | show Hamburger = ""
45 | show Indeterminate = "⎻"
46 | show Info = "?"
47 | show List = ""
48 | show Minus = "-"
49 | show Overflow = "⋯"
50 | show Percent = "%"
51 | show Plus = "+"
52 | show Remove = "x"
53 | show Search = "🔍"
54 | show Rearrange = ""
55 |
56 | iconArrowDown :: IconType
57 | iconArrowDown = ArrowDown
58 |
59 | iconArrowLeft :: IconType
60 | iconArrowLeft = ArrowLeft
61 |
62 | iconArrowRight :: IconType
63 | iconArrowRight = ArrowRight
64 |
65 | iconArrowUp :: IconType
66 | iconArrowUp = ArrowUp
67 |
68 | iconBin :: IconType
69 | iconBin = Bin
70 |
71 | iconCart :: IconType
72 | iconCart = Cart
73 |
74 | iconClose :: IconType
75 | iconClose = Close
76 |
77 | iconCurrency :: IconType
78 | iconCurrency = Currency
79 |
80 | iconGrid :: IconType
81 | iconGrid = Grid
82 |
83 | iconHamburger :: IconType
84 | iconHamburger = Hamburger
85 |
86 | iconIndeterminate :: IconType
87 | iconIndeterminate = Indeterminate
88 |
89 | iconInfo :: IconType
90 | iconInfo = Info
91 |
92 | iconList :: IconType
93 | iconList = List
94 |
95 | iconMinus :: IconType
96 | iconMinus = Minus
97 |
98 | iconOverflow :: IconType
99 | iconOverflow = Overflow
100 |
101 | iconPercent :: IconType
102 | iconPercent = Percent
103 |
104 | iconPlus :: IconType
105 | iconPlus = Plus
106 |
107 | iconRemove :: IconType
108 | iconRemove = Remove
109 |
110 | iconSearch :: IconType
111 | iconSearch = Search
112 |
113 | iconRearrange :: IconType
114 | iconRearrange = Rearrange
115 |
116 | type IconProps =
117 | { type_ :: IconType
118 | , style :: CSS
119 | }
120 |
121 | component :: Component IconProps
122 | component = createComponent "Icon"
123 |
124 | icon :: IconProps -> JSX
125 | icon = makeStateless component $ lumiIconElement <<< mapProps
126 | where
127 | lumiIconElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-font-icon")
128 |
129 | mapProps props =
130 | { "data-icon-char": show props.type_
131 | , style: props.style
132 | }
133 |
134 | icon_ :: IconType -> JSX
135 | icon_ type_ = icon
136 | { type_
137 | , style: css {}
138 | }
139 |
140 | styles :: JSS
141 | styles = jss
142 | { "@font-face":
143 | { fontFamily: "lumi-font-icons"
144 | , src: "url(https://s3-us-west-2.amazonaws.com/lumi-gumball/fonts/unicode/lumi-font-icons.woff)"
145 | , fontWeight: "400"
146 | , fontStyle: "normal"
147 | }
148 |
149 | , "@global":
150 | { "lumi-font-icon":
151 | { fontSize: "inherit"
152 | , color: "inherit"
153 | , lineHeight: "inherit"
154 | , "&::before":
155 | { fontFamily: "lumi-font-icons!important"
156 | , content: "attr(data-icon-char)"
157 | , fontStyle: "normal!important"
158 | , fontWeight: "normal!important"
159 | , fontVariant: "normal!important"
160 | , textTransform: "none!important"
161 | , speak: "none"
162 | , "WebkitFontSmoothing": "antialiased"
163 | , "MozOsxFontSmoothing": "grayscale"
164 | , verticalAlign: "middle"
165 | }
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | var LumiInputElement = function (_props) {
4 | this.inputRef = React.createRef();
5 | return this;
6 | };
7 |
8 | LumiInputElement.prototype = Object.create(React.Component.prototype);
9 |
10 | LumiInputElement.displayName = "LumiInputElement";
11 |
12 | LumiInputElement.prototype.setIndeterminate = function () {
13 | if (this.inputRef.current.indeterminate !== this.props.indeterminate) {
14 | this.inputRef.current.indeterminate = this.props.indeterminate;
15 | }
16 | };
17 |
18 | LumiInputElement.prototype.componentDidMount = function () {
19 | this.setIndeterminate();
20 | };
21 |
22 | LumiInputElement.prototype.componentDidUpdate = function () {
23 | this.setIndeterminate();
24 | };
25 |
26 | LumiInputElement.prototype.render = function () {
27 | var this_ = this;
28 | var props = {};
29 | Object.keys(this.props).forEach(function (key) {
30 | if (key === "indeterminate") return;
31 | props[key] = this_.props[key];
32 | });
33 | props.ref = this.inputRef;
34 | return React.createElement("input", props);
35 | };
36 |
37 | export const lumiInputElement = LumiInputElement;
38 |
--------------------------------------------------------------------------------
/src/Lumi/Components/InputGroup.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.InputGroup where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Data.Array (fromFoldable)
7 | import Data.Nullable (Nullable, toMaybe)
8 | import Effect.Unsafe (unsafePerformEffect)
9 | import JSS (JSS, jss)
10 | import Lumi.Components.Color (colors)
11 | import Lumi.Components.ZIndex (ziInputGroup)
12 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
13 | import React.Basic.DOM (CSS, unsafeCreateDOMComponent)
14 | import React.Basic.DOM as R
15 |
16 | type InputGroupProps =
17 | { addOnLeft :: Nullable JSX
18 | , addOnRight :: Nullable JSX
19 | , inputContent :: Nullable JSX
20 | , style :: CSS
21 | }
22 |
23 | component :: Component InputGroupProps
24 | component = createComponent "InputGroup"
25 |
26 | inputGroup :: InputGroupProps -> JSX
27 | inputGroup = makeStateless component render
28 | where
29 | render props =
30 | inputGroupElement
31 | { style: props.style
32 | , children:
33 | [ inputGroupAddOn { children: fromFoldable (toMaybe props.addOnLeft) }
34 | , R.a { className: "input-container", children: fromFoldable (toMaybe props.inputContent) }
35 | , inputGroupAddOn { children : fromFoldable (toMaybe props.addOnRight) }
36 | ]
37 | }
38 |
39 | inputGroupElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-input-group")
40 | inputGroupAddOn = element (unsafePerformEffect $ unsafeCreateDOMComponent "input-group-addon")
41 |
42 | styles :: JSS
43 | styles = jss
44 | { "@global":
45 | { "lumi-input-group":
46 | { display: "flex"
47 | , width: "100%"
48 |
49 | , "& a.input-container":
50 | { flex: "1"
51 | , marginRight: "-1px"
52 | , "&:focus, &:hover": { zIndex: ziInputGroup }
53 | , "& input[type=\"text\"], & input.lumi[type=\"text\"]":
54 | { borderRadius: "0"
55 | , width: "100%"
56 | }
57 | }
58 |
59 | , "& input-group-addon:first-child button.lumi":
60 | { marginRight: "-1px"
61 | , borderTopRightRadius: "0"
62 | , borderBottomRightRadius: "0"
63 | , borderTopLeftRadius: buttonBorderRadius
64 | , borderBottomLeftRadius: buttonBorderRadius
65 | }
66 | , "& input:not(:first-child) button.lumi":
67 | { borderTopLeftRadius: "0"
68 | , borderBottomLeftRadius: "0"
69 | }
70 | , "& input:not(:last-child) button.lumi":
71 | { marginRight: "-1px"
72 | , borderTopRightRadius: "0"
73 | , borderBottomRightRadius: "0"
74 | }
75 | , "& input-group-addon:last-child button.lumi":
76 | { borderTopRightRadius: buttonBorderRadius
77 | , borderBottomRightRadius: buttonBorderRadius
78 | , borderTopLeftRadius:"0"
79 | , borderBottomLeftRadius:"0"
80 | }
81 |
82 | , "& input-group-addon button.lumi":
83 | { "&[data-color=\"secondary\"]":
84 | { backgroundColor: cssStringHSLA colors.transparent
85 | }
86 | , "&:focus, &:hover": { zIndex: ziInputGroup }
87 | }
88 | }
89 | }
90 | }
91 | where
92 | buttonBorderRadius = "3px"
93 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Layouts.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Layouts where
2 |
3 | import Color (cssStringHSLA)
4 | import JSS (JSS, jss)
5 | import Lumi.Components.Color (colors)
6 |
7 | styles :: JSS
8 | styles = jss
9 | { "@global":
10 | { "lumi-layout":
11 | { boxSizing: "border-box"
12 | , flex: "1"
13 | , display: "flex"
14 | , flexFlow: "column"
15 | , alignSelf: "stretch"
16 |
17 | , "& > lumi-layout-view-head":
18 | { boxSizing: "border-box"
19 | , flex: "none"
20 | , display: "flex"
21 | , flexDirection: "row"
22 | , padding: "12px 24px"
23 | , backgroundColor: cssStringHSLA colors.black6
24 | , borderBottom: [ "1px", "solid", cssStringHSLA colors.black4 ]
25 | }
26 |
27 | , "& > lumi-layout-view-body":
28 | { boxSizing: "border-box"
29 | , overflowY: "auto"
30 | , display: "flex"
31 | , flexFlow: "column"
32 | }
33 | }
34 |
35 | , "lumi-layout-centered":
36 | { display: "block"
37 | , width: "850px"
38 | , maxWidth: "90%"
39 | , margin: "5vh auto"
40 | }
41 |
42 | , "lumi-layout-centered-full-width":
43 | { display: "block"
44 | , width: "90%"
45 | , maxWidth: "90%"
46 | , margin: "5vh auto"
47 | }
48 |
49 | , "lumi-layout > lumi-layout-view-body, lumi-layout-centered, .lumi-layout-content":
50 | { "& lumi-section-header":
51 | { paddingTop: "32px"
52 | , paddingBottom: "12px"
53 | }
54 | }
55 |
56 | , "lumi-layout-view-body, lumi-layout-centered":
57 | { flex: "1"
58 | , "& lumi-section-header":
59 | { paddingTop: "32px"
60 | , paddingBottom: "12px"
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Layouts/Centered.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Layouts.Centered where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import React.Basic.Classic (JSX, element)
7 | import React.Basic.DOM (unsafeCreateDOMComponent)
8 |
9 | layout :: JSX -> JSX
10 | layout = \content ->
11 | lumiLayoutCentered
12 | { children: [content]
13 | }
14 | where
15 | lumiLayoutCentered = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-layout-centered")
16 |
17 | layoutFullWidth :: JSX -> JSX
18 | layoutFullWidth = \content ->
19 | lumiLayoutCentered
20 | { children: [content]
21 | }
22 | where
23 | lumiLayoutCentered = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-layout-centered-full-width")
24 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Layouts/OneColumnWithHeader.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Layouts.OneColumnWithHeader where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Effect.Unsafe (unsafePerformEffect)
7 | import Lumi.Components.Text (h2, text)
8 | import React.Basic.Classic (Component, JSX, createComponent, element, fragment, makeStateless)
9 | import React.Basic.DOM (unsafeCreateDOMComponent)
10 | import React.Basic.DOM as R
11 |
12 | component :: Component LayoutProps
13 | component = createComponent "OneColumnWithHeader"
14 |
15 | type LayoutProps =
16 | { titleContent :: JSX
17 | , additionalHeaderContent :: JSX
18 | , actionContent :: JSX
19 | , mainContent :: JSX
20 | , sidebarContent :: Maybe JSX
21 | }
22 |
23 | layout :: LayoutProps -> JSX
24 | layout = makeStateless component render
25 | where
26 | render { titleContent, additionalHeaderContent, actionContent, mainContent, sidebarContent } =
27 | lumiLayout
28 | { children:
29 | [ lumiLayoutViewHead
30 | { children:
31 | [ text $ h2
32 | { children = [titleContent]
33 | , style = R.css
34 | { paddingBottom: "0"
35 | , fontWeight: "normal"
36 | , marginRight: "12px"
37 | , flex: "none"
38 | }
39 | }
40 | , R.span
41 | { style: R.css
42 | { overflow: "visible"
43 | , flex: "1 1 70%"
44 | , wordWrap: "break-word"
45 | , padding: "0"
46 | }
47 | , children: [additionalHeaderContent]
48 | }
49 | , R.div
50 | { style: R.css { flex: "1 1 10px" }
51 | }
52 | , R.div
53 | { style: R.css { flex: "none" }
54 | , children: [actionContent]
55 | }
56 | ]
57 | }
58 | , lumiLayoutViewBody
59 | { className: "view-body view-scroll"
60 | , children: case sidebarContent of
61 | Nothing ->
62 | [ mainContent ]
63 | Just sbc ->
64 | [ R.div
65 | { className: "row row-justify-between row-no-padding"
66 | , children:
67 | [ R.div { className: "column view-scroll", children: [ mainContent]}
68 | , sidebarLayout { content: sbc }
69 | ]
70 | }
71 | ]
72 | }
73 | ]
74 | }
75 |
76 | lumiLayout = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-layout")
77 | lumiLayoutViewHead = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-layout-view-head")
78 | lumiLayoutViewBody = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-layout-view-body")
79 |
80 | sidebarLayoutComponent :: Component SidebarLayoutProps
81 | sidebarLayoutComponent = createComponent "Sidebar"
82 |
83 | type SidebarLayoutProps =
84 | { content :: JSX
85 | }
86 |
87 | sidebarLayout :: SidebarLayoutProps -> JSX
88 | sidebarLayout = makeStateless sidebarLayoutComponent render where
89 | render { content } =
90 | fragment $
91 | [ R.div
92 | { style: R.css { "flex": "0 0 30%" }
93 | , className: "column bl view-scroll"
94 | , maxLength: 400
95 | , children: [
96 | R.div { className: "ppa", children: [ content ]}
97 | ]
98 | }
99 | ]
100 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Layouts/Tabs.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Layouts.Tabs where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Data.NonEmpty (NonEmpty, oneOf)
7 | import Effect (Effect)
8 | import Lumi.Components.Tab (TabId, TabKey)
9 | import Lumi.Components.Tab as Tab
10 | import React.Basic.Classic (Component, JSX, createComponent, makeStateless)
11 | import React.Basic.DOM as R
12 | import Web.HTML.History (URL)
13 |
14 | component :: Component TabLayoutProps
15 | component = createComponent "TabsLayout"
16 |
17 | type TabLayoutProps =
18 | { currentLocation :: URL
19 | , queryKey :: TabKey
20 | , useHash :: Boolean
21 | , navigate :: Maybe (URL -> Effect Unit)
22 | , tabs :: NonEmpty Array
23 | { id :: TabId
24 | , label :: String
25 | , count :: Maybe Int
26 | , content :: Unit -> JSX
27 | }
28 | }
29 |
30 | tabLayout :: TabLayoutProps -> JSX
31 | tabLayout = makeStateless component render
32 | where
33 | render { currentLocation, navigate, queryKey, tabs, useHash } =
34 | Tab.tabs
35 | { style: R.css { "paddingLeft": "24px" }
36 | , tabStyle: R.css {}
37 | , selectedTabStyle: R.css {}
38 | , currentLocation
39 | , queryKey
40 | , useHash
41 | , navigate
42 | , tabs: oneOf tabs <#> \t ->
43 | { content: t.content
44 | , id: t.id
45 | , label: t.label
46 | , count: t.count
47 | , testId: Nothing
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Link.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Link where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Data.Foldable (foldMap)
7 | import Data.Maybe (Maybe(..))
8 | import Data.Newtype (un)
9 | import Data.Nullable (toNullable)
10 | import Data.String (Pattern(..), contains)
11 | import Effect (Effect)
12 | import Effect.Uncurried (runEffectFn1)
13 | import Effect.Unsafe (unsafePerformEffect)
14 | import JSS (JSS, jss)
15 | import Lumi.Components.Color (colors)
16 | import React.Basic.Classic (Component, JSX, createComponent, element, empty, makeStateless)
17 | import React.Basic.DOM (CSS, css, unsafeCreateDOMComponent)
18 | import React.Basic.DOM as R
19 | import React.Basic.DOM.Events (altKey, button, ctrlKey, metaKey, preventDefault, shiftKey, stopPropagation)
20 | import React.Basic.Events (handler, merge, syntheticEvent)
21 | import Web.HTML.History (URL(..))
22 |
23 |
24 | type LinkProps =
25 | { className :: Maybe String
26 | , href :: URL
27 | , navigate :: Maybe (Effect Unit)
28 | , style :: CSS
29 | , target :: Maybe String
30 | , testId :: Maybe String
31 | , text :: JSX
32 | }
33 |
34 | component :: Component LinkProps
35 | component = createComponent "Link"
36 |
37 | link :: LinkProps -> JSX
38 | link = makeStateless component render
39 | where
40 | lumiAnchorElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "a")
41 |
42 | render { className, href, navigate, style, target, testId, text } =
43 | lumiAnchorElement
44 | { children: text
45 | , className: "lumi" <> foldMap (" " <> _) className
46 | , "data-testid": toNullable testId
47 | , href: un URL href
48 | , onClick: handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent })
49 | \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do
50 | case navigate, button, metaKey, altKey, ctrlKey, shiftKey of
51 | Just n', Just 0, Just false, Just false, Just false, Just false ->
52 | runEffectFn1
53 | (handler (stopPropagation <<< preventDefault) $ const n')
54 | syntheticEvent
55 | _ , _ , _ , _ , _ , _ ->
56 | runEffectFn1
57 | (handler stopPropagation mempty)
58 | syntheticEvent
59 | , style
60 | , target: toNullable target
61 | }
62 |
63 | defaults :: LinkProps
64 | defaults =
65 | { className: Nothing
66 | , href: URL ""
67 | , navigate: Nothing
68 | , style: css {}
69 | , target: Nothing
70 | , testId: Nothing
71 | , text: empty
72 | }
73 |
74 | styles :: JSS
75 | styles = jss
76 | { "@global":
77 | { "a.lumi":
78 | { color: cssStringHSLA colors.primary
79 | , textDecoration: "none"
80 | , "&:visited":
81 | { color: cssStringHSLA colors.primary
82 | , textDecoration: "none"
83 | }
84 | , "&:hover":
85 | { cursor: "pointer"
86 | , textDecoration: "underline"
87 | }
88 | , "&:focus, &:active": { outline: "0" }
89 | }
90 | }
91 | }
92 |
93 | linkOut
94 | :: { href :: URL
95 | , label :: String
96 | }
97 | -> JSX
98 | linkOut { label, href: (URL href) } =
99 | link defaults
100 | { className = Just "action"
101 | , href = href'
102 | , text = R.text label
103 | , target = Just "_blank"
104 | }
105 | where href' = URL if contains (Pattern "http") href then href else "https://" <> href
--------------------------------------------------------------------------------
/src/Lumi/Components/Loader.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Loader where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Data.Nullable (Nullable)
7 | import Effect.Unsafe (unsafePerformEffect)
8 | import JSS (JSS, jss)
9 | import Lumi.Components.Color (Color, colors)
10 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
11 | import React.Basic.DOM (CSS, unsafeCreateDOMComponent)
12 |
13 | type LoaderProps
14 | = { style :: CSS
15 | , testId :: Nullable String
16 | }
17 |
18 | component :: Component LoaderProps
19 | component = createComponent "Loader"
20 |
21 | loader :: LoaderProps -> JSX
22 | loader = makeStateless component $ loaderElement <<< mapProps
23 | where
24 | loaderElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-loader")
25 |
26 | mapProps props =
27 | { style: props.style
28 | , "data-testid": props.testId
29 | }
30 |
31 | styles :: JSS
32 | styles =
33 | jss
34 | { "@global":
35 | { "lumi-loader":
36 | spinnerMixin
37 | { color: colors.black1
38 | , highlightColor: colors.black4
39 | , radius: "38px"
40 | , borderWidth: "5px"
41 | }
42 | , "@keyframes spin":
43 | { from: { transform: "rotate(0deg)" }
44 | , to: { transform: "rotate(360deg)" }
45 | }
46 | }
47 | }
48 |
49 | spinnerMixin ::
50 | { color :: Color
51 | , highlightColor :: Color
52 | , radius :: String
53 | , borderWidth :: String
54 | } ->
55 | JSS
56 | spinnerMixin { color: c, highlightColor, radius, borderWidth } =
57 | jss
58 | { boxSizing: "border-box"
59 | , content: "\"\""
60 | , display: "inline-block"
61 | , height: radius
62 | , width: radius
63 | , border: [ borderWidth, "solid", cssStringHSLA c ]
64 | , borderTopColor: cssStringHSLA highlightColor
65 | , borderRadius: "50%"
66 | , animation: "spin 1s infinite linear"
67 | , animationName: "spin"
68 | }
69 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Modal.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import React from 'react';
4 | var h = React.createElement;
5 | import ReactDOM from 'react-dom';
6 | var modalRoot = document.getElementById("modal-root");
7 |
8 | function assign(target, from) {
9 | for (var nextKey in from) {
10 | if (Object.prototype.hasOwnProperty.call(from, nextKey)) {
11 | target[nextKey] = from[nextKey];
12 | }
13 | }
14 | return target;
15 | }
16 |
17 | export const modalBuilder = function (ModalPortal) {
18 | var ModalComponent = function constructor(_props) {
19 | this.el = document.createElement("div");
20 | var this_ = this;
21 | this.requestClose = function (event) {
22 | if (this_.ownerHandlesClose()) {
23 | this_.props.onRequestClose(event);
24 | }
25 | };
26 | this.ownerHandlesClose = function () {
27 | return !!this_.props.onRequestClose;
28 | };
29 | this.portalRef = function (ref) {
30 | this_.portal = ref;
31 | };
32 | return this;
33 | };
34 | ModalComponent.prototype = Object.create(React.Component.prototype);
35 | ModalComponent.displayName = "Modal";
36 |
37 | ModalComponent.prototype.componentDidMount = function () {
38 | modalRoot.appendChild(this.el);
39 | };
40 |
41 | ModalComponent.prototype.componentWillUnmount = function () {
42 | modalRoot.removeChild(this.el);
43 | };
44 |
45 | ModalComponent.prototype.render = function () {
46 | return ReactDOM.createPortal(
47 | h(
48 | ModalPortal,
49 | assign(
50 | {
51 | ref: this.portalRef,
52 | requestClose: this.requestClose
53 | },
54 | this.props
55 | )
56 | ),
57 | this.el
58 | );
59 | };
60 |
61 | return ModalComponent;
62 | };
63 |
64 | export const toggleBodyClass = function (className, on) {
65 | if (on) {
66 | document.body.classList.add(className);
67 | } else {
68 | document.body.classList.remove(className);
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Navigation.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const checkIsEventTargetInTree = function (domNode, e) {
4 | return e.target === domNode || domNode.contains(e.target);
5 | };
6 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Orientation.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Orientation where
2 |
3 | import Prelude
4 |
5 | data Orientation
6 | = Horizontal
7 | | Vertical
8 |
9 | derive instance eqOrientation :: Eq Orientation
10 |
11 | instance showOrientation :: Show Orientation where
12 | show Horizontal = "horizontal"
13 | show Vertical = "vertical"
14 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Responsive.js:
--------------------------------------------------------------------------------
1 | export { useMediaPredicate as useMedia_ } from "react-media-hook";
2 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Responsive.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Responsive where
2 |
3 | import Prelude
4 |
5 | import Effect.Uncurried (EffectFn1, runEffectFn1)
6 | import Effect.Unsafe (unsafePerformEffect)
7 | import React.Basic.Hooks (Component, Hook, JSX, component, unsafeHook)
8 | import React.Basic.Hooks as React
9 |
10 | foreign import data MediaQuery :: Type
11 |
12 | foreign import data UseMediaQuery :: Type -> Type
13 |
14 | useMedia :: String -> Hook UseMediaQuery Boolean
15 | useMedia mq = unsafeHook (runEffectFn1 useMedia_ mq)
16 |
17 | foreign import useMedia_ :: EffectFn1 String Boolean
18 |
19 | useIsPhone :: Hook UseMediaQuery Boolean
20 | useIsPhone = map not (useMedia ("(min-width: " <> show minWidthNonPhone <> "px)"))
21 |
22 | useIsMobile :: Hook UseMediaQuery Boolean
23 | useIsMobile = map not useIsDesktop
24 |
25 | useIsDesktop :: Hook UseMediaQuery Boolean
26 | useIsDesktop = useMedia ("(min-width: " <> show minWidthDesktop <> "px)")
27 |
28 | -- | Prefer `useIsPhone` or `phone` to using this value
29 | -- | directly. Named in the negative because -- | this
30 | -- | width is the first size that is _not_ a phone and
31 | -- | trying to use this width as a "max-width" leaves
32 | -- | out fractional pixels (447.5px) which sometimes
33 | -- | occur on high-dpi displays.
34 | minWidthNonPhone :: Int
35 | minWidthNonPhone = 448
36 |
37 | minWidthDesktop :: Int
38 | minWidthDesktop = 860
39 |
40 | mobile :: (Unit -> JSX) -> JSX
41 | mobile = \render ->
42 | c \isMobile -> if isMobile then render unit else mempty
43 | -- WARNING: Don't add any arguments to the definition of `mobile`!
44 | -- `c` must be fully applied at module creation!
45 | where
46 | c = unsafePerformEffect (mkMediaComponent "Mobile" useIsMobile)
47 |
48 | withMobile :: (Boolean -> JSX) -> JSX
49 | withMobile = c
50 | -- WARNING: Don't add any arguments to the `withMobile` function!
51 | -- `c` must be fully applied at module creation!
52 | where
53 | c = unsafePerformEffect (mkMediaComponent "Mobile" useIsMobile)
54 |
55 | phone :: (Unit -> JSX) -> JSX
56 | phone = \render ->
57 | c \isPhone -> if isPhone then render unit else mempty
58 | -- WARNING: Don't add any arguments to the definition of `phone`!
59 | -- `c` must be fully applied at module creation!
60 | where
61 | c = unsafePerformEffect (mkMediaComponent "Phone" useIsPhone)
62 |
63 | withPhone :: (Boolean -> JSX) -> JSX
64 | withPhone = c
65 | -- WARNING: Don't add any arguments to the `withPhone` function!
66 | -- `c` must be fully applied at module creation!
67 | where
68 | c = unsafePerformEffect (mkMediaComponent "Phone" useIsPhone)
69 |
70 | desktop :: (Unit -> JSX) -> JSX
71 | desktop = \render ->
72 | c \isDesktop -> if isDesktop then render unit else mempty
73 | -- WARNING: Don't add any arguments the `desktop` function!
74 | -- `c` must be fully applied at module creation!
75 | where
76 | c = unsafePerformEffect (mkMediaComponent "Desktop" useIsDesktop)
77 |
78 | withDesktop :: (Boolean -> JSX) -> JSX
79 | withDesktop = c
80 | -- WARNING: Don't add any arguments to the `withDesktop` function!
81 | -- `c` must be fully applied at module creation!
82 | where
83 | c = unsafePerformEffect (mkMediaComponent "Desktop" useIsDesktop)
84 |
85 | mkMediaComponent ::
86 | String ->
87 | Hook UseMediaQuery Boolean ->
88 | Component (Boolean -> JSX)
89 | mkMediaComponent name umq = do
90 | component ("MediaQuery(" <> name <> ")") \render -> React.do
91 | isMatch <- umq
92 | pure $ render isMatch
93 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Row.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Row where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import JSS (JSS, jss)
7 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
8 | import React.Basic.DOM (CSS, css, unsafeCreateDOMComponent)
9 |
10 | component :: Component RowProps
11 | component = createComponent "Row"
12 |
13 | type RowProps =
14 | { children :: Array JSX
15 | , style :: CSS
16 | }
17 |
18 | row :: RowProps -> JSX
19 | row = makeStateless component lumiRowElement
20 | where
21 | lumiRowElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-row")
22 |
23 | row_ :: Array JSX -> JSX
24 | row_ children = row { children, style: css {} }
25 |
26 | responsiveRow :: RowProps -> JSX
27 | responsiveRow = makeStateless component lumiResponsiveRowElement
28 | where
29 | lumiResponsiveRowElement = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-responsive-row")
30 |
31 | responsiveRow_ :: Array JSX -> JSX
32 | responsiveRow_ children = responsiveRow { children, style: css {} }
33 |
34 | styles :: JSS
35 | styles = jss
36 | { "@global":
37 | { "lumi-row":
38 | { boxSizing: "border-box"
39 | , display: "flex"
40 | , flexDirection: "row"
41 | }
42 |
43 | , "lumi-responsive-row":
44 | { boxSizing: "border-box"
45 | , display: "flex"
46 | , flexDirection: "row"
47 |
48 | , "@media (max-width: 860px)":
49 | { flexDirection: "column"
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Size.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Size where
2 |
3 | import Prelude
4 |
5 |
6 | data Size
7 | = Small
8 | | Medium
9 | | Large
10 | | ExtraLarge
11 | | ExtraExtraLarge
12 |
13 | derive instance eqSize :: Eq Size
14 | derive instance ordSize :: Ord Size
15 |
16 | instance showSize :: Show Size where
17 | show Small = "small"
18 | show Medium = "medium"
19 | show Large = "large"
20 | show ExtraLarge = "extra-large"
21 | show ExtraExtraLarge = "extra-extra-large"
22 |
23 | small :: Size
24 | small = Small
25 |
26 | medium :: Size
27 | medium = Medium
28 |
29 | large :: Size
30 | large = Large
31 |
32 | extraLarge :: Size
33 | extraLarge = ExtraLarge
34 |
35 | extraExtraLarge :: Size
36 | extraExtraLarge = ExtraExtraLarge
37 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Slider.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Slider where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Data.Int (fromString)
7 | import Data.Maybe (Maybe(..), fromMaybe)
8 | import Data.Nullable (toNullable)
9 | import Effect.Uncurried (EffectFn1, runEffectFn1)
10 | import Effect.Unsafe (unsafePerformEffect)
11 | import JSS (JSS, jss)
12 | import Lumi.Components.Color (colors)
13 | import Lumi.Components.Input as Input
14 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
15 | import React.Basic.DOM as R
16 | import React.Basic.DOM.Events (targetValue)
17 | import React.Basic.Events as Events
18 |
19 | type SliderProps =
20 | { completed :: Int
21 | , onChange :: EffectFn1 Int Unit
22 | , style :: R.CSS
23 | }
24 |
25 | component :: Component SliderProps
26 | component = createComponent "Slider"
27 |
28 | slider :: SliderProps -> JSX
29 | slider = makeStateless component render
30 | where
31 | render props =
32 | lumiSliderElement
33 | { style: props.style
34 | , children:
35 | [ Input.input Input.range
36 | { value = show props.completed
37 | , min = toNullable $ Just 0.0
38 | , max = toNullable $ Just 100.0
39 | , onChange = Events.handler targetValue \targetValue -> do
40 | runEffectFn1 props.onChange $ fromMaybe 0 $ fromString =<< targetValue
41 | }
42 | , lumiValueBarElement
43 | { style: R.css { width: show props.completed <> "%" }
44 | }
45 | ]
46 | }
47 |
48 | lumiSliderElement = element $ unsafePerformEffect $ R.unsafeCreateDOMComponent "lumi-slider"
49 | lumiValueBarElement = element $ unsafePerformEffect $ R.unsafeCreateDOMComponent "lumi-value-bar"
50 |
51 | styles :: JSS
52 | styles = jss
53 | { "@global":
54 | { "lumi-slider":
55 | { position: "relative"
56 | , width: "100%"
57 |
58 | , "& input.lumi[type=\"range\"]":
59 | { position: "absolute"
60 | , left: "0px"
61 | , padding: "0px"
62 | , margin: "0px"
63 |
64 | , width: "100%"
65 | , height: "4px"
66 | }
67 | , "& lumi-value-bar":
68 | { position: "absolute"
69 | , padding: "0px"
70 | , margin: "0px"
71 |
72 | , backgroundColor: cssStringHSLA colors.primary
73 | , height: "4px"
74 | , pointerEvents: "none"
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Spacing.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Spacing
2 | ( Space(..)
3 | , hspace
4 | , hspaceWithStyle
5 | , vspace
6 | , toPixels
7 | ) where
8 |
9 | import Prelude
10 |
11 | import React.Basic.Classic (JSX)
12 | import React.Basic.DOM as R
13 | import React.Basic.Emotion (class IsStyleProperty, str)
14 |
15 | data Space
16 | = S4 | S8 | S12 | S16 | S24 | S32 | S40 | S48 | S56
17 | | S64 | S72 | S80 | S88 | S96 | S104 | S112
18 |
19 | hspaceWithStyle :: R.CSS -> Space -> JSX
20 | hspaceWithStyle sty size =
21 | R.div
22 | { style: sty <> R.css
23 | { paddingLeft: toPixels size
24 | }
25 | }
26 |
27 | hspace :: Space -> JSX
28 | hspace = hspaceWithStyle mempty
29 |
30 | vspace :: Space -> JSX
31 | vspace size =
32 | R.div
33 | { style: R.css
34 | { paddingTop: toPixels size
35 | }
36 | }
37 |
38 | toPixels :: Space -> String
39 | toPixels S4 = "4px"
40 | toPixels S8 = "8px"
41 | toPixels S12 = "12px"
42 | toPixels S16 = "16px"
43 | toPixels S24 = "24px"
44 | toPixels S32 = "32px"
45 | toPixels S40 = "40px"
46 | toPixels S48 = "48px"
47 | toPixels S56 = "56px"
48 | toPixels S64 = "64px"
49 | toPixels S72 = "72px"
50 | toPixels S80 = "80px"
51 | toPixels S88 = "88px"
52 | toPixels S96 = "96px"
53 | toPixels S104 = "104px"
54 | toPixels S112 = "112px"
55 |
56 | instance isStylePropertySpace :: IsStyleProperty Space where
57 | prop = str <<< toPixels
58 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Status.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Status where
2 |
3 | import Prelude
4 |
5 | data Status
6 | = Active
7 | | Warning
8 | | Error
9 | | Unknown
10 |
11 | derive instance eqStatus :: Eq Status
12 |
13 | instance showStatus :: Show Status where
14 | show Active = "active"
15 | show Warning = "warning"
16 | show Error = "error"
17 | show Unknown = "unknown"
18 |
19 | active :: Status
20 | active = Active
21 |
22 | warning :: Status
23 | warning = Warning
24 |
25 | error :: Status
26 | error = Error
27 |
28 | unknown :: Status
29 | unknown = Unknown
30 |
--------------------------------------------------------------------------------
/src/Lumi/Components/StatusSlat.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.StatusSlat where
2 |
3 | -- TODO remove this in favor of Lumi.Common.Components.StatusSlat or vice-versa.
4 | -- We probably don't need both
5 |
6 | import Prelude
7 |
8 | import Color (cssStringHSLA)
9 | import Data.Nullable (Nullable, toNullable, toMaybe)
10 | import Effect.Unsafe (unsafePerformEffect)
11 | import JSS (JSS, jss)
12 | import Lumi.Components.Color (colors)
13 | import Lumi.Components.Column (column)
14 | import Lumi.Components.Row (row)
15 | import Lumi.Components.Status (Status)
16 | import Lumi.Components.Text (subtext_, title_)
17 | import React.Basic.Classic (Component, JSX, createComponent, element, makeStateless)
18 | import React.Basic.DOM (css, unsafeCreateDOMComponent)
19 |
20 | type StatusCellProps =
21 | { data :: Array
22 | { label :: String
23 | , content :: String
24 | , status :: Nullable Status
25 | }
26 | }
27 |
28 | component :: Component StatusCellProps
29 | component = createComponent "StatusSlat"
30 |
31 | statusSlat :: StatusCellProps -> JSX
32 | statusSlat = makeStateless component $ lumiStatusSlat <<< mapProps
33 | where
34 | mapProps props =
35 | { className: "lumi"
36 | , children:
37 | row
38 | { style: css
39 | { height: "100%" }
40 | , children: map toStatusCell props.data
41 | }
42 | }
43 |
44 | toStatusCell { label, content, status } =
45 | lumiStatusSlatCell
46 | { "data-status": toNullable (map show (toMaybe status))
47 | , children:
48 | column
49 | { style: css
50 | { height: "100%"
51 | , justifyContent: "center"
52 | }
53 | , children:
54 | [ subtext_ label
55 | , title_ content
56 | ]
57 | }
58 | }
59 |
60 | lumiStatusSlat = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-status-slat")
61 | lumiStatusSlatCell = element (unsafePerformEffect $ unsafeCreateDOMComponent "lumi-status-slat-cell")
62 |
63 | styles :: JSS
64 | styles = jss
65 | { "@global":
66 | { "lumi-status-slat":
67 | { border: [ "1px", "solid", cssStringHSLA colors.black4 ]
68 | , width: "100%"
69 | , height: "64px"
70 | , display: "inline-block"
71 | , borderRadius: "5px"
72 | , "& lumi-status-slat-cell":
73 | { borderRight: [ "1px", "solid", cssStringHSLA colors.black4 ]
74 | , height: "100%"
75 | , minWidth: "calc(25% - (2 * 16px))"
76 | , display: "inline-block"
77 | , padding: "0 16px"
78 | , whiteSpace: "nowrap"
79 | , overflow: "hidden"
80 | , textOverflow: "ellipsis"
81 | , "&:last-child":
82 | { borderRight: "none"
83 | }
84 | , "&[data-status=\"active\"]":
85 | { color: cssStringHSLA colors.accent1
86 | }
87 | , "&[data-status=\"warning\"]":
88 | { color: cssStringHSLA colors.accent2
89 | }
90 | , "&[data-status=\"error\"]":
91 | { color: cssStringHSLA colors.accent3
92 | }
93 | , "&[data-status=\"unknown\"]":
94 | { color: cssStringHSLA colors.black1
95 | }
96 | }
97 | }
98 |
99 |
100 |
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Styles.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Styles where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Data.Traversable (traverse_)
7 | import Effect (Effect)
8 | import JSS (JSS, jss)
9 | import JSS as JSS
10 | import Lumi.Components.Badge as Badge
11 | import Lumi.Components.Border as Border
12 | import Lumi.Components.Breadcrumb as Breadcrumb
13 | import Lumi.Components.Button (styles) as Button
14 | import Lumi.Components.ButtonGroup as ButtonGroup
15 | import Lumi.Components.Card as Card
16 | import Lumi.Components.CardGrid as CardGrid
17 | import Lumi.Components.Color (colors)
18 | import Lumi.Components.Column as Column
19 | import Lumi.Components.Details as Details
20 | import Lumi.Components.Divider as Divider
21 | import Lumi.Components.DropdownButton as DropdownButton
22 | import Lumi.Components.EditableTable as EditableTable
23 | import Lumi.Components.Form as Form
24 | import Lumi.Components.Icon as Icon
25 | import Lumi.Components.Images as Images
26 | import Lumi.Components.Input as Input
27 | import Lumi.Components.InputGroup as InputGroup
28 | import Lumi.Components.LabeledField as LabeledField
29 | import Lumi.Components.Layouts as Layouts
30 | import Lumi.Components.Link as Link
31 | import Lumi.Components.List as List
32 | import Lumi.Components.Loader as Loader
33 | import Lumi.Components.Lockup as Lockup
34 | import Lumi.Components.Modal as Modal
35 | import Lumi.Components.NativeSelect as NativeSelect
36 | import Lumi.Components.Navigation as Navigation
37 | import Lumi.Components.Pagination as Pagination
38 | import Lumi.Components.Pill as Pill
39 | import Lumi.Components.Row as Row
40 | import Lumi.Components.Select as Select
41 | import Lumi.Components.Slider as Slider
42 | import Lumi.Components.StatusSlat as StatusSlat
43 | import Lumi.Components.Tab as Tab
44 | import Lumi.Components.Table as Table
45 | import Lumi.Components.Text as Text
46 | import Lumi.Components.Textarea as Textarea
47 | import Lumi.Components.Toast as Toast
48 | import Lumi.Components.Tooltip as Tooltip
49 | import Lumi.Components.Upload as Upload
50 |
51 | attachGlobalComponentStyles :: Effect Unit
52 | attachGlobalComponentStyles = do
53 | jssInstance <- JSS.createInstance JSS.preset
54 | traverse_ (JSS.globalAttachStyleSheet <=< JSS.createStyleSheet jssInstance)
55 | [ globals
56 | , Badge.styles
57 | , Border.styles
58 | , Breadcrumb.styles
59 | , Button.styles
60 | , ButtonGroup.styles
61 | , Card.styles
62 | , CardGrid.styles
63 | , Column.styles
64 | , Details.styles
65 | , Divider.styles
66 | , DropdownButton.styles
67 | , EditableTable.styles
68 | , Form.styles
69 | , Icon.styles
70 | , Images.styles
71 | , Input.styles
72 | , InputGroup.styles
73 | , LabeledField.styles
74 | , Layouts.styles
75 | , Link.styles
76 | , List.styles
77 | , Loader.styles
78 | , Lockup.styles
79 | , Modal.styles
80 | , NativeSelect.styles
81 | , Navigation.styles
82 | , Pagination.styles
83 | , Pill.styles
84 | , Row.styles
85 | , Select.styles
86 | , Slider.styles
87 | , StatusSlat.styles
88 | , Tab.styles
89 | , Table.styles
90 | , Text.styles
91 | , Text.styles
92 | , Textarea.styles
93 | , Toast.styles
94 | , Tooltip.styles
95 | , Upload.styles
96 | ]
97 |
98 | globals :: JSS
99 | globals = jss
100 | { "@global":
101 | { "*.lumi":
102 | { boxSizing: "border-box"
103 | , margin: "0"
104 | }
105 | , "react-basic-ref":
106 | { display: "flex"
107 | , flexDirection: "column"
108 | , minHeight: "0"
109 | , minWidth: "0"
110 | }
111 | , "body":
112 | { color: cssStringHSLA colors.black
113 | , backgroundColor: cssStringHSLA colors.white
114 | , fontFeatureSettings: "tnum"
115 | , textRendering: "optimizeLegibility"
116 | , "WebkitFontSmoothing": "antialiased"
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Svg.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Svg where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Lumi.Components.Color (colors)
7 | import React.Basic.Classic (JSX)
8 | import React.Basic.DOM.SVG as RS
9 |
10 | placeholderSvg :: JSX
11 | placeholderSvg =
12 | RS.svg
13 | { viewBox: "0 0 24 24"
14 | , fill: "none"
15 | , stroke: cssStringHSLA colors.black4
16 | , xmlns: "http://www.w3.org/2000/svg"
17 | , children:
18 | [ "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
19 | ]
20 | <#>
21 | { d: _
22 | , strokeLinecap: "round"
23 | , strokeLinejoin: "round"
24 | }
25 | >>> RS.path
26 | }
27 |
28 | clientSvg :: JSX
29 | clientSvg =
30 | RS.svg
31 | { xmlns: "http://www.w3.org/2000/svg"
32 | , width: "100%"
33 | , viewBox: "0 0 200 200"
34 | , children:
35 | [ RS.g
36 | { fill
37 | , fillRule
38 | , children:
39 | [ RS.path { d }
40 | , RS.path
41 | { d: "M32.939 71.563l10.19-2.73 35.199 131.365H67.356z"
42 | }
43 | ]
44 | }
45 | ]
46 | }
47 | where
48 | fill = "#DDDDDC"
49 | fillRule = "evenodd"
50 | d = "M147.61 55.396a2.89 2.89 0 0 0-2.458.186c-.15.085-15.444 8.745-28.157 12.151-4.362 1.17-7.85 1.576-10.364 1.205-3-.44-3.763-1.892-4.09-3.11-.95-3.546-4.434-8.997-17.082-5.608C71.61 63.93 53.366 76.076 52.593 76.59c-.977.656-1.435 1.829-1.141 2.928l16.669 62.21c.225.84.862 1.519 1.702 1.81a2.85 2.85 0 0 0 2.534-.328c.182-.116 18.226-12.13 31.16-15.595 9.158-2.454 10.03.796 10.315 1.864 1.303 4.863 6.098 9.785 21.532 5.65l.006-.002c13.374-3.584 28.609-12.122 29.248-12.488 1.098-.616 1.64-1.862 1.325-3.039l-16.728-62.427a2.652 2.652 0 0 0-1.605-1.776"
51 |
52 | userSvg :: JSX
53 | userSvg =
54 | RS.svg
55 | { xmlns: "http://www.w3.org/2000/svg"
56 | , width: "100%"
57 | , viewBox: "0 0 200 200"
58 | , children:
59 | [ RS.g
60 | { fill
61 | , fillRule
62 | , children:
63 | [ RS.path { d }
64 | ]
65 | }
66 | ]
67 | }
68 | where
69 | fill = "#DDDDDC"
70 | fillRule = "nonzero"
71 | d = "M30.613 200c4.223-39.011 29.994-69 69.387-69s65.164 29.989 69.387 69H30.613zM100 120c30.281 0 39-17.909 38-40s-12.31-40-38-40c-25.69 0-37 17.909-38 40s7.719 40 38 40z"
72 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Tab.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const urlParts = function (href) {
4 | var base = href;
5 | var path = "";
6 | var query = "";
7 | var hash = { path: "", query: "" };
8 | try {
9 | var a = document.createElement("a");
10 | a.href = href;
11 | base = a.protocol + "//" + a.host;
12 | path = a.pathname;
13 | query = a.search;
14 | var hashParts = a.hash.split("?");
15 | hash.path = hashParts[0] || "";
16 | hash.query = hashParts[1] != null ? "?" + hashParts[1] : "";
17 | } catch (err) {
18 | console.warn(err);
19 | }
20 | return {
21 | base: base,
22 | path: path,
23 | query: query,
24 | hash: hash
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Table.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // This is here until we can get rid of Immutable
4 | export const dataSize = function (data) {
5 | return data.size ? data.size : data.length;
6 | };
7 |
8 | export const getMouseEventPositionWithOffset = function (domNode, e) {
9 | var bounds = domNode.getBoundingClientRect();
10 | return {
11 | x: e.pageX - window.scrollX - bounds.left,
12 | y: e.pageY - window.scrollY - bounds.top
13 | };
14 | };
15 |
16 | export const checkIsEventTargetInTree = function (domNode, e) {
17 | return e.target === domNode || domNode.contains(e.target);
18 | };
19 |
20 | export const isRightClick = function (e) {
21 | return e.button === 2;
22 | };
23 |
24 | export const hasWindowSelection = function () {
25 | return window.getSelection().type === "Range";
26 | };
27 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Textarea.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.Textarea where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Data.Nullable (Nullable, toNullable)
7 | import Effect.Uncurried (mkEffectFn1)
8 | import Foreign.Object (fromHomogeneous)
9 | import JSS (JSS, jss)
10 | import Lumi.Components.Input (lumiInputDisabledStyles, lumiInputFocusInvalidStyles, lumiInputFocusStyles, lumiInputHoverStyles, lumiInputInvalidStyles, lumiInputPlaceholderStyles, lumiInputStyles)
11 | import React.Basic.Classic (Component, JSX, createComponent, makeStateless)
12 | import React.Basic.DOM (CSS, css)
13 | import React.Basic.DOM as R
14 | import React.Basic.Events (EventHandler)
15 | import Unsafe.Coerce (unsafeCoerce)
16 |
17 | type TextareaProps
18 | = { autoComplete :: String
19 | , disabled :: Boolean
20 | , lines :: Int
21 | , maxLength :: Nullable Int
22 | , minLength :: Nullable Int
23 | , name :: String
24 | , onBlur :: Nullable EventHandler
25 | , onChange :: EventHandler
26 | , onFocus :: Nullable EventHandler
27 | , placeholder :: String
28 | , readOnly :: Boolean
29 | , required :: Boolean
30 | , resizable :: Boolean
31 | , style :: CSS
32 | , testId :: Nullable String
33 | , value :: String
34 | }
35 |
36 | component :: Component TextareaProps
37 | component = createComponent "Textarea"
38 |
39 | textarea :: TextareaProps -> JSX
40 | textarea = makeStateless component render
41 | where
42 | render props =
43 | R.textarea
44 | { _data: fromHomogeneous
45 | { testid: unsafeNullable props.testId
46 | , "disable-resize": show (not props.resizable)
47 | }
48 | , autoComplete: props.autoComplete
49 | , className: "lumi"
50 | , disabled: props.disabled
51 | , maxLength: unsafeNullable props.maxLength
52 | , minLength: unsafeNullable props.minLength
53 | , name: props.name
54 | , onBlur: unsafeNullable props.onBlur
55 | , onChange: props.onChange
56 | , onFocus: unsafeNullable props.onFocus
57 | , placeholder: props.placeholder
58 | , required: props.required
59 | , readOnly: props.readOnly
60 | , rows: props.lines
61 | , style: props.style
62 | , value: props.value
63 | }
64 |
65 | unsafeNullable :: forall a. Nullable a -> a
66 | unsafeNullable = unsafeCoerce
67 |
68 | defaults :: TextareaProps
69 | defaults =
70 | { autoComplete: "on"
71 | , disabled: false
72 | , lines: 2
73 | , maxLength: toNullable Nothing
74 | , minLength: toNullable Nothing
75 | , name: ""
76 | , onBlur: toNullable Nothing
77 | , onChange: mkEffectFn1 $ pure <<< const unit
78 | , onFocus: toNullable Nothing
79 | , placeholder: ""
80 | , required: false
81 | , readOnly: false
82 | , resizable: true
83 | , style: css {}
84 | , testId: toNullable Nothing
85 | , value: ""
86 | }
87 |
88 | styles :: JSS
89 | styles =
90 | jss
91 | { "@global":
92 | { "textarea.lumi":
93 | { fontFamily: "inherit"
94 | , fontSize: "14px"
95 | , lineHeight: "20px"
96 | , touchAction: "manipulation"
97 | , boxSizing: "border-box"
98 | , resize: "vertical"
99 | , extend: lumiInputStyles
100 | , minHeight: "40px"
101 | , height: "unset"
102 | , "@media (min-width: 860px)":
103 | { minHeight: "32px"
104 | , height: "unset"
105 | }
106 | , "&:hover": lumiInputHoverStyles
107 | , "&:invalid": lumiInputInvalidStyles
108 | , "&:focus":
109 | { extend: lumiInputFocusStyles
110 | , "&:invalid": lumiInputFocusInvalidStyles
111 | }
112 | , "&:disabled": lumiInputDisabledStyles
113 | , "&::-webkit-input-placeholder": lumiInputPlaceholderStyles
114 | , "&::-moz-placeholder": lumiInputPlaceholderStyles
115 | , "&:-ms-input-placeholder": lumiInputPlaceholderStyles
116 | , "&::placeholder": lumiInputPlaceholderStyles
117 | , "&[data-disable-resize=\"true\"]": { resize: "none" }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Lumi/Components/Upload.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const createObjectURL = function (file) {
4 | return URL.createObjectURL(file);
5 | };
6 |
7 | export const xhrUpload = function (uri, file, onProgress, onError, onComplete) {
8 | var xhr = new XMLHttpRequest();
9 |
10 | xhr.upload.addEventListener(
11 | "progress",
12 | function (e) {
13 | onProgress({ totalBytes: e.total | 0, uploadedBytes: e.loaded | 0 });
14 | },
15 | { passive: true }
16 | );
17 |
18 | xhr.addEventListener(
19 | "load",
20 | function (e) {
21 | onComplete(e.responseText);
22 | },
23 | { passive: true }
24 | );
25 |
26 | xhr.addEventListener(
27 | "error",
28 | function (e) {
29 | onError(e.error);
30 | },
31 | { passive: true }
32 | );
33 |
34 | xhr.open("post", uri, true);
35 | xhr.setRequestHeader("Content-Type", "multipart/form-data");
36 | xhr.send(file);
37 | };
38 |
--------------------------------------------------------------------------------
/src/Lumi/Components/ZIndex.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components.ZIndex where
2 |
3 | ziButtonGroup :: Int
4 | ziButtonGroup = 1000
5 |
6 | ziDropdownButton :: Int
7 | ziDropdownButton = 1000
8 |
9 | ziInputGroup :: Int
10 | ziInputGroup = 1000
11 |
12 | ziModal :: Int
13 | ziModal = 10000
14 |
15 | ziNavigationBar :: Int
16 | ziNavigationBar = 100
17 |
18 | ziNavigationDropdown :: Int
19 | ziNavigationDropdown = 101
20 |
21 | ziSelect :: Int
22 | ziSelect = 1000
23 |
24 | ziTableHeader :: Int
25 | ziTableHeader = 12
26 |
27 | ziTableHeaderMenu :: Int
28 | ziTableHeaderMenu = 14
29 |
30 | ziTableLockedColumn :: Int
31 | ziTableLockedColumn = 11
32 |
33 | ziTableLockedColumnHeader :: Int
34 | ziTableLockedColumnHeader = 13
35 |
36 | ziToast :: Int
37 | ziToast = 100000
38 |
39 | ziTooltip :: Int
40 | ziTooltip = 1000000
41 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/Box.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Box where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import Lumi.Components (LumiComponent, lumiComponent)
7 | import Lumi.Styles (toCSS)
8 | import Lumi.Styles.Box as Styles.Box
9 | import Lumi.Styles.Theme (useTheme)
10 | import React.Basic.Classic (JSX)
11 | import React.Basic.DOM as R
12 | import React.Basic.Emotion as E
13 | import React.Basic.Hooks as React
14 |
15 | type BoxProps
16 | = ( content :: Array JSX )
17 |
18 | box :: LumiComponent BoxProps
19 | box =
20 | unsafePerformEffect do
21 | lumiComponent "Box" { content: [] } \props -> React.do
22 | theme <- useTheme
23 | pure
24 | $ E.element R.div'
25 | { children: props.content
26 | , className: props.className
27 | , css: theme # toCSS Styles.Box.box <> props.css
28 | }
29 |
30 | row :: LumiComponent BoxProps
31 | row = box <<< Styles.Box._row
32 |
33 | column :: LumiComponent BoxProps
34 | column = box <<< Styles.Box._column
35 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/ButtonGroup.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.ButtonGroup where
2 |
3 | import Prelude
4 |
5 | import Effect.Unsafe (unsafePerformEffect)
6 | import Lumi.Components (PropsModifier)
7 | import Lumi.Components as L
8 | import Lumi.Components.ZIndex (ziButtonGroup)
9 | import Lumi.Styles (css, nested, px, str, style_, toCSS)
10 | import Lumi.Styles.Box (_row, box)
11 | import Lumi.Styles.Theme (useTheme)
12 | import React.Basic.DOM as R
13 | import React.Basic.Emotion as E
14 | import React.Basic.Hooks (JSX)
15 | import React.Basic.Hooks as React
16 |
17 | type ButtonGroupProps
18 | = ( component :: ButtonGroup
19 | , content :: Array JSX
20 | )
21 |
22 | buttonGroup :: L.LumiComponent ButtonGroupProps
23 | buttonGroup =
24 | unsafePerformEffect do
25 | L.lumiComponent "ButtonGroup" { component: ButtonGroup, content: [] } \props -> React.do
26 | theme <- useTheme
27 | pure
28 | $ E.element R.div'
29 | { className: props.className
30 | , children: props.content
31 | , css: theme # toCSS styles <> props.css
32 | }
33 |
34 | where
35 | styles =
36 | box
37 | <<< _row
38 | <<< style_
39 | ( css
40 | { label: str "buttonGroup"
41 | , "& > *:not(:last-child)":
42 | nested
43 | $ css
44 | { marginRight: px 8
45 | }
46 | }
47 | )
48 |
49 |
50 | data ButtonGroup = ButtonGroup
51 |
52 | type ButtonGroupModifier = forall r. PropsModifier ( component :: ButtonGroup | r )
53 |
54 | joined :: ButtonGroupModifier
55 | joined =
56 | style_
57 | $ css
58 | { label: str "joined"
59 | , "& > *:not(:last-child)":
60 | nested
61 | $ css
62 | { marginRight: px (-1)
63 | , borderTopRightRadius: px 0
64 | , borderBottomRightRadius: px 0
65 | }
66 | , "& > *:not(:first-child)":
67 | nested
68 | $ css
69 | { borderTopLeftRadius: px 0
70 | , borderBottomLeftRadius: px 0
71 | }
72 | , "& > *:focus, & > *:hover":
73 | nested
74 | $ css
75 | { zIndex: px ziButtonGroup
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/Clip.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const selectNodeContents = node => {
4 | const range = document.createRange();
5 | range.selectNodeContents(node);
6 | var sel = window.getSelection();
7 | sel.removeAllRanges();
8 | sel.addRange(range);
9 | return sel;
10 | };
11 |
12 | export const copyNodeContents = (success, failure, node) => {
13 | try {
14 | const sel = selectNodeContents(node);
15 | if (window.navigator.clipboard != null) {
16 | window.navigator.clipboard
17 | .writeText(sel.toString().trim())
18 | .then(() => {
19 | sel.removeAllRanges();
20 | })
21 | .then(success, failure);
22 | return;
23 | } else {
24 | const copyResult = window.document.execCommand("copy");
25 | if (copyResult) {
26 | sel.removeAllRanges();
27 | return success();
28 | } else {
29 | return failure(new Error("Failed to copy"));
30 | }
31 | }
32 | } catch (e) {
33 | failure(e);
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/Clip.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Clip where
2 |
3 | import Prelude
4 |
5 | import Data.Either (Either(..))
6 | import Data.Foldable (for_)
7 | import Data.Nullable (Nullable)
8 | import Data.Nullable as Nullable
9 | import Effect (Effect)
10 | import Effect.Aff (Aff, Error, Milliseconds(..), delay, makeAff, message, nonCanceler)
11 | import Effect.Class (liftEffect)
12 | import Effect.Console as Console
13 | import Effect.Uncurried (EffectFn1, EffectFn3, mkEffectFn1, runEffectFn3)
14 | import Effect.Unsafe (unsafePerformEffect)
15 | import Lumi.Components (LumiComponent, lumiComponent, ($$$))
16 | import Lumi.Components.Spacing (Space(..), hspace)
17 | import Lumi.Components2.Box (box)
18 | import Lumi.Components2.Button (linkButton, loadingContent, onPress)
19 | import Lumi.Styles (toCSS)
20 | import Lumi.Styles as S
21 | import Lumi.Styles.Box (FlexAlign(..), _align, _flex)
22 | import Lumi.Styles.Box as Styles.Box
23 | import Lumi.Styles.Clip as Styles.Clip
24 | import Lumi.Styles.Theme (LumiTheme(..), useTheme)
25 | import React.Basic.DOM as R
26 | import React.Basic.Emotion as E
27 | import React.Basic.Hooks (JSX, Ref, readRefMaybe, useRef)
28 | import React.Basic.Hooks as React
29 | import Web.DOM (Node)
30 |
31 | type ClipProps
32 | = ( content :: Array JSX
33 | )
34 |
35 | clip :: LumiComponent ClipProps
36 | clip =
37 | unsafePerformEffect do
38 | lumiComponent "Clip" defaults \props -> React.do
39 | theme@(LumiTheme { colors }) <- useTheme
40 | ref <- useRef Nullable.null
41 | let
42 | copyButton =
43 | linkButton
44 | $ S.style_ (S.css { "&:disabled": S.nested $ S.css { color: S.color colors.black1 } })
45 | $ onPress do
46 | copy ref
47 | delay $ Milliseconds 5000.0
48 | $ loadingContent [ box $ _flex $ _align End $$$ [ R.text "Copied!" ] ]
49 | $$$ [ box $ _flex $ _align End $$$ [ R.text "Copy" ] ]
50 | pure
51 | $ E.element R.div'
52 | { className: props.className
53 | , css: theme # toCSS Styles.Clip.clip <> props.css
54 | , children:
55 | [ E.element R.div'
56 | { className: ""
57 | , css: theme # toCSS (Styles.Box.box <<< Styles.Box._justify Center)
58 | , ref
59 | , children: props.content
60 | }
61 | , hspace S8
62 | , box
63 | $ _align End
64 | $$$ [ copyButton ]
65 | ]
66 | }
67 | where
68 | defaults = { content: [] }
69 |
70 | copy :: Ref (Nullable Node) -> Aff Unit
71 | copy nodeRef = do
72 | nodeM <- liftEffect do readRefMaybe nodeRef
73 | for_ nodeM \node ->
74 | makeAff \done -> do
75 | runEffectFn3 copyNodeContents
76 | ( done $ Right unit )
77 | ( mkEffectFn1 \e -> do
78 | Console.error $ message e
79 | done $ Right unit
80 | )
81 | node
82 | pure nonCanceler
83 |
84 | foreign import copyNodeContents :: EffectFn3 (Effect Unit) (EffectFn1 Error Unit) Node Unit
85 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/Link.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Link where
2 |
3 | import Prelude
4 |
5 | import Data.Maybe (Maybe(..))
6 | import Data.Newtype (un)
7 | import Effect (Effect)
8 | import Effect.Uncurried (runEffectFn1)
9 | import Effect.Unsafe (unsafePerformEffect)
10 | import Foreign.Object (fromHomogeneous)
11 | import Lumi.Components (LumiComponent, lumiComponent, unsafeMaybeToNullableAttr)
12 | import Lumi.Styles (toCSS)
13 | import Lumi.Styles.Link as Styles.Link
14 | import Lumi.Styles.Theme (useTheme)
15 | import React.Basic.Classic (JSX)
16 | import React.Basic.DOM as R
17 | import React.Basic.DOM.Events (altKey, button, ctrlKey, metaKey, preventDefault, shiftKey, stopPropagation)
18 | import React.Basic.Emotion as E
19 | import React.Basic.Events (handler, merge, syntheticEvent)
20 | import React.Basic.Hooks as React
21 | import Web.HTML.History (URL(..))
22 |
23 | type LinkProps
24 | = ( href :: URL
25 | , navigate :: Maybe (Effect Unit)
26 | , tabIndex :: Maybe Int
27 | , target :: Maybe String
28 | , rel :: Maybe String
29 | , download :: Maybe String
30 | , ariaLabel :: Maybe String
31 | , content :: Array JSX
32 | , className :: String
33 | )
34 |
35 | link :: LumiComponent LinkProps
36 | link =
37 | unsafePerformEffect do
38 | lumiComponent "Link" defaults \props@{ className } -> React.do
39 | theme <- useTheme
40 | pure
41 | $ E.element R.a'
42 | { _aria: unsafeMaybeToNullableAttr $ map (fromHomogeneous <<< { label: _ }) props.ariaLabel
43 | , css: theme # toCSS Styles.Link.link <> props.css
44 | , children: props.content
45 | , className
46 | , href: un URL props.href
47 | , onClick:
48 | handler (merge { button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent }) \{ button, metaKey, altKey, ctrlKey, shiftKey, syntheticEvent } -> do
49 | case props.navigate, button, metaKey, altKey, ctrlKey, shiftKey of
50 | Just n', Just 0, Just false, Just false, Just false, Just false ->
51 | runEffectFn1
52 | (handler (stopPropagation <<< preventDefault) $ const n')
53 | syntheticEvent
54 | _, _, _, _, _, _ ->
55 | runEffectFn1
56 | (handler stopPropagation mempty)
57 | syntheticEvent
58 | , target: unsafeMaybeToNullableAttr props.target
59 | , rel: unsafeMaybeToNullableAttr props.rel
60 | , tabIndex: unsafeMaybeToNullableAttr props.tabIndex
61 | , download: unsafeMaybeToNullableAttr props.download
62 | }
63 | where
64 | defaults =
65 | { className: ""
66 | , href: URL ""
67 | , navigate: Nothing
68 | , tabIndex: Nothing
69 | , target: Nothing
70 | , rel: Nothing
71 | , download: Nothing
72 | , ariaLabel: Nothing
73 | , content: []
74 | }
75 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/PasswordStrength.js:
--------------------------------------------------------------------------------
1 | export {default as passwordStrengthBar} from 'react-password-strength-bar';
--------------------------------------------------------------------------------
/src/Lumi/Components2/PasswordStrength.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.PasswordStrength where
2 |
3 |
4 | import React.Basic.Hooks as Hooks
5 |
6 | type PasswordStrengthProps =
7 | { password :: String
8 | , minLength :: Int
9 | }
10 |
11 | foreign import passwordStrengthBar :: Hooks.ReactComponent PasswordStrengthProps
--------------------------------------------------------------------------------
/src/Lumi/Components2/QRCode.js:
--------------------------------------------------------------------------------
1 | export {default as qrcode_} from "qrcode.react";
2 |
3 | export const generateSVGUrl = ref => () => {
4 | const containerNode = ref.current;
5 | if (containerNode == null) {
6 | throw new Error("Cannot save the contents of an empty ref");
7 | }
8 | const svgNode = containerNode.querySelector("svg");
9 | if (svgNode == null) {
10 | throw new Error("Inner SVG node not found");
11 | }
12 |
13 | const data = new XMLSerializer().serializeToString(svgNode);
14 | const svg = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
15 | const url = URL.createObjectURL(svg);
16 | const dispose = () => URL.revokeObjectURL(url);
17 | return { url, dispose };
18 | };
19 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/QRCode.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.QRCode
2 | ( useQRCode
3 | , UseQRCode
4 | , ErrorCorrectLevel(..)
5 | , errorCorrectLevelToString
6 | , qrcode_
7 | , generateSVGUrl
8 | ) where
9 |
10 | import Prelude
11 |
12 | import Data.Maybe (Maybe(..))
13 | import Data.Newtype (class Newtype)
14 | import Data.Nullable as Nullable
15 | import Effect (Effect)
16 | import Effect.Unsafe (unsafePerformEffect)
17 | import Lumi.Components (LumiComponent, lumiComponent)
18 | import Lumi.Styles (StyleModifier, style_, toCSS)
19 | import Lumi.Styles.Box (box)
20 | import Lumi.Styles.Theme (useTheme)
21 | import React.Basic.DOM as R
22 | import React.Basic.Emotion as E
23 | import React.Basic.Hooks (type (/\), Hook, ReactComponent, Ref, UnsafeReference(..), UseEffect, UseMemo, UseRef, UseState, coerceHook, element, useEffect, useMemo, useRef, useState, (/\))
24 | import React.Basic.Hooks as React
25 | import Web.DOM (Node)
26 | import Web.HTML.History (URL(..))
27 |
28 | useQRCode :: ErrorCorrectLevel -> String -> Hook UseQRCode { qrcode :: LumiComponent (), url :: Maybe URL }
29 | useQRCode level value =
30 | coerceHook React.do
31 | ref <- useRef Nullable.null
32 | qrcode <-
33 | useMemo (value /\ level) \_ ->
34 | unsafePerformEffect do
35 | lumiComponent "QRCode" {} \props -> React.do
36 | theme <- useTheme
37 | pure
38 | $ E.element R.div'
39 | { children:
40 | [ element qrcode_
41 | { value
42 | , level: errorCorrectLevelToString level
43 | , renderAs: "svg"
44 | , xmlns: "http://www.w3.org/2000/svg"
45 | , size: Nullable.null
46 | , bgColor: "rgba(255,255,255,0.0)"
47 | , fgColor: "rgba(0,0,0,1.0)"
48 | }
49 | ]
50 | , ref
51 | , className: props.className
52 | , css: theme # toCSS qrcodeStyle <> props.css
53 | }
54 | url /\ setUrl <- useState Nothing
55 | useEffect (UnsafeReference qrcode) do
56 | svgUrl <- generateSVGUrl ref
57 | setUrl \_ -> Just $ URL svgUrl.url
58 | pure svgUrl.dispose
59 | pure { qrcode, url }
60 |
61 | qrcodeStyle :: StyleModifier
62 | qrcodeStyle = box <<< style_ (E.css { label: E.str "qrcode" })
63 |
64 | newtype UseQRCode hooks
65 | = UseQRCode
66 | ( UseEffect
67 | ( UnsafeReference (LumiComponent ()) )
68 | ( UseState
69 | ( Maybe URL )
70 | ( UseMemo
71 | (String /\ ErrorCorrectLevel)
72 | (LumiComponent ())
73 | (UseRef (Nullable.Nullable Node) hooks)
74 | )
75 | )
76 | )
77 |
78 | derive instance ntUseQRCode :: Newtype (UseQRCode hooks) _
79 |
80 | data ErrorCorrectLevel
81 | = ECLLow
82 | | ECLMedium
83 | | ECLQuartile
84 | | ECLHigh
85 |
86 | derive instance eqErrorCorrectLevel :: Eq ErrorCorrectLevel
87 |
88 | errorCorrectLevelToString :: ErrorCorrectLevel -> String
89 | errorCorrectLevelToString = case _ of
90 | ECLLow -> "L"
91 | ECLMedium -> "M"
92 | ECLQuartile -> "Q"
93 | ECLHigh -> "H"
94 |
95 | foreign import qrcode_ ::
96 | ReactComponent
97 | { value :: String
98 | , level :: String
99 | , renderAs :: String
100 | , xmlns :: String
101 | , size :: Nullable.Nullable Int
102 | , bgColor :: String
103 | , fgColor :: String
104 | }
105 |
106 | foreign import generateSVGUrl :: Ref (Nullable.Nullable Node) -> Effect { url :: String, dispose :: Effect Unit }
107 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/ScrollObserver.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // Walks up the DOM tree starting at the given node to find
4 | // the first parent with a scroll bar (including auto, which
5 | // on some devices hides the scroll bar until hovered).
6 | export const getScrollParent = node => () => {
7 | if (!(node instanceof HTMLElement)) {
8 | return document.body;
9 | }
10 |
11 | const computedStyle = window.getComputedStyle(node);
12 | const overflowY = computedStyle && computedStyle.overflowY;
13 | const overflowX = computedStyle && computedStyle.overflowX;
14 | const isScrollable =
15 | (overflowY &&
16 | !(overflowY.includes("visible") || overflowY.includes("hidden"))) ||
17 | (overflowX &&
18 | !(overflowX.includes("visible") || overflowX.includes("hidden")));
19 |
20 | if (isScrollable && node.scrollHeight >= node.clientHeight) {
21 | return node;
22 | } else {
23 | return getScrollParent(node.parentNode)();
24 | }
25 | };
26 |
27 | export const addPassiveEventListener = type => listener => capture => target => () =>
28 | target.addEventListener(type, listener, { passive: true, capture });
29 |
30 | export const removePassiveEventListener = type => listener => capture => target => () =>
31 | target.removeEventListener(type, listener, { passive: true, capture });
32 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/ScrollObserver.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.ScrollObserver where
2 |
3 | import Prelude
4 | import Data.Newtype (class Newtype)
5 | import Data.Nullable (Nullable)
6 | import Data.Nullable as Nullable
7 | import Effect (Effect)
8 | import Effect.Unsafe (unsafePerformEffect)
9 | import Lumi.Components (LumiComponent, lumiComponentFromHook)
10 | import React.Basic.Classic (JSX)
11 | import React.Basic.Hooks (Hook, UnsafeReference(..), UseEffect, UseState, coerceHook, useEffect, useState, (/\))
12 | import React.Basic.Hooks as React
13 | import Web.DOM (Element, Node)
14 | import Web.DOM.Element (scrollLeft, scrollTop)
15 | import Web.DOM.Element as Element
16 | import Web.Event.Event (EventType(..))
17 | import Web.Event.EventTarget (EventListener, EventTarget, eventListener)
18 |
19 | newtype UseScrollObserver hooks
20 | = UseScrollObserver (UseEffect (UnsafeReference (Nullable Node)) (UseState Boolean (UseState Boolean hooks)))
21 |
22 | derive instance ntUseScrollObserver :: Newtype (UseScrollObserver hooks) _
23 |
24 | useScrollObserver :: Nullable Node -> Hook UseScrollObserver { hasScrolledX :: Boolean, hasScrolledY :: Boolean }
25 | useScrollObserver root =
26 | coerceHook React.do
27 | hasScrolledY /\ setHasScrolledY <- useState false
28 | hasScrolledX /\ setHasScrolledX <- useState false
29 | useEffect (UnsafeReference root) do
30 | scrollParent <- getScrollParent root
31 | let
32 | onScroll = do
33 | top <- scrollTop scrollParent
34 | left <- scrollLeft scrollParent
35 | setHasScrolledY \_ -> top > 0.0
36 | setHasScrolledX \_ -> left > 0.0
37 | onScrollListener <- eventListener \_ -> onScroll
38 | onScroll
39 | Element.toEventTarget scrollParent # addPassiveEventListener (EventType "scroll") onScrollListener false
40 | pure do
41 | Element.toEventTarget scrollParent # removePassiveEventListener (EventType "scroll") onScrollListener false
42 | pure { hasScrolledY, hasScrolledX }
43 |
44 | type ScrollObserverProps
45 | = ( node :: Nullable Node
46 | , render :: { hasScrolledX :: Boolean, hasScrolledY :: Boolean } -> JSX
47 | )
48 |
49 | scrollObserver :: LumiComponent ScrollObserverProps
50 | scrollObserver =
51 | unsafePerformEffect do
52 | lumiComponentFromHook "ScrollObserver" defaults (useScrollObserver <<< _.node)
53 | where
54 | defaults = { node: Nullable.null, render: \_ -> mempty }
55 |
56 | foreign import getScrollParent :: Nullable Node -> Effect Element
57 |
58 | -- | Adds a listener to an event target. The boolean argument indicates whether
59 | -- | the listener should be added for the "capture" phase.
60 | foreign import addPassiveEventListener ::
61 | EventType ->
62 | EventListener ->
63 | Boolean ->
64 | EventTarget ->
65 | Effect Unit
66 |
67 | -- | Removes a listener to an event target. The boolean argument indicates
68 | -- | whether the listener should be removed for the "capture" phase.
69 | foreign import removePassiveEventListener ::
70 | EventType ->
71 | EventListener ->
72 | Boolean ->
73 | EventTarget ->
74 | Effect Unit
75 |
--------------------------------------------------------------------------------
/src/Lumi/Components2/Slat.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Components2.Slat
2 | ( SlatProps
3 | , SlatInteraction
4 | , slat
5 | , _interactive
6 | , _interactiveBackground
7 | , module Styles.Slat
8 | ) where
9 |
10 | import Prelude
11 | import Data.Maybe (Maybe(..))
12 | import Data.Newtype (un)
13 | import Effect (Effect)
14 | import Effect.Unsafe (unsafePerformEffect)
15 | import Lumi.Components (LumiComponent, PropsModifier, lumiComponent, propsModifier)
16 | import Lumi.Styles (style, style_, toCSS)
17 | import Lumi.Styles.Slat (_interactive, slat) as Styles.Slat.Hidden
18 | import Lumi.Styles.Slat hiding (_interactive,slat) as Styles.Slat
19 | import Lumi.Styles.Theme (LumiTheme(..), useTheme)
20 | import React.Basic.DOM as R
21 | import React.Basic.DOM.Events (capture_)
22 | import React.Basic.Emotion as E
23 | import React.Basic.Hooks (JSX)
24 | import React.Basic.Hooks as React
25 | import Web.HTML.History (URL(..))
26 |
27 | type SlatProps
28 | = ( content :: Array JSX
29 | , interaction :: Maybe SlatInteraction
30 | )
31 |
32 | type SlatInteraction
33 | = { onClick :: Effect Unit
34 | , tabIndex :: Int
35 | , href :: Maybe URL
36 | }
37 |
38 | slat :: LumiComponent SlatProps
39 | slat =
40 | unsafePerformEffect do
41 | lumiComponent "Slat" defaults \props@{ className } -> React.do
42 | theme <- useTheme
43 | pure case props.interaction of
44 | Nothing ->
45 | E.element R.div'
46 | { css: theme # toCSS slatStyle <> props.css
47 | , children: props.content
48 | , className
49 | }
50 | Just interaction@{ href: Nothing } ->
51 | E.element R.button'
52 | { css: theme # toCSS slatStyleInteractive <> props.css
53 | , children: props.content
54 | , onClick: capture_ interaction.onClick
55 | , tabIndex: interaction.tabIndex
56 | , className
57 | }
58 | Just interaction@{ href: Just href } ->
59 | E.element R.a'
60 | { css: theme # toCSS slatStyleInteractive <> props.css
61 | , children: props.content
62 | , onClick: capture_ interaction.onClick
63 | , tabIndex: interaction.tabIndex
64 | , href: un URL href
65 | , className
66 | }
67 | where
68 | defaults =
69 | { content: []
70 | , interaction: Nothing
71 | }
72 |
73 | slatStyle =
74 | Styles.Slat.Hidden.slat
75 | <<< style_ (E.css { appearance: E.none })
76 |
77 | slatStyleInteractive =
78 | slatStyle
79 | <<< Styles.Slat.Hidden._interactive
80 |
81 | _interactive :: SlatInteraction -> PropsModifier SlatProps
82 | _interactive interaction =
83 | propsModifier
84 | _
85 | { interaction = Just interaction
86 | }
87 |
88 | _interactiveBackground :: SlatInteraction -> PropsModifier SlatProps
89 | _interactiveBackground interaction =
90 | propsModifier
91 | _
92 | { interaction = Just interaction
93 | }
94 | <<< style \(LumiTheme theme) ->
95 | E.css
96 | { "&:hover":
97 | E.nested
98 | $ E.css
99 | { backgroundColor: E.color theme.colors.primary4
100 | , borderColor: E.color theme.colors.black4
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Lumi/Styles.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles
2 | ( module Lumi.Styles
3 | , module Emotion
4 | ) where
5 |
6 | import Prelude
7 |
8 | import Data.Foldable (fold)
9 | import Lumi.Components (PropsModifier, propsModifier)
10 | import Lumi.Styles.Theme (LumiTheme)
11 | import React.Basic.Emotion hiding (element,style) as Emotion
12 |
13 | type StyleModifier = forall props. PropsModifier props
14 |
15 | -- | Lift a themed set of styles into a `StyleModifier` for composition with other modifiers.
16 | style :: (LumiTheme -> Emotion.Style) -> StyleModifier
17 | style f = propsModifier \props -> props { css = props.css <> f }
18 |
19 | -- | Lift a static set of styles into a `StyleModifier` for composition with other modifiers.
20 | style_ :: Emotion.Style -> StyleModifier
21 | style_ = style <<< const
22 |
23 | -- | Lift an array of themed styles into a `StyleModifier` for composition with other modifiers.
24 | styles :: Array (LumiTheme -> Emotion.Style) -> StyleModifier
25 | styles = style <<< fold
26 |
27 | -- | Lift an array of static styles into a `StyleModifier` for composition with other modifiers.
28 | styles_ :: Array Emotion.Style -> StyleModifier
29 | styles_ = style_ <<< fold
30 |
31 | -- | Flatten a `PropsModifier` and extract the Emotion styles for use with `React.Basic.Emotion.element`.
32 | -- | This function is mainly used inside component implementations where the `LumiComponent` boundary
33 | -- | gives way to DOM components or other `ReactComponent`s.
34 | toCSS ::
35 | PropsModifier () ->
36 | LumiTheme ->
37 | Emotion.Style
38 | toCSS m = (m identity { className: "", css: mempty }).css
39 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Border.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Border where
2 |
3 | import Prelude
4 |
5 | import Lumi.Components.Spacing (Space(..))
6 | import Lumi.Styles (StyleModifier, px2, solid, style, style_)
7 | import Lumi.Styles.Box (box)
8 | import Lumi.Styles.Box as Box
9 | import Lumi.Styles.Theme (LumiTheme(..))
10 | import React.Basic.Emotion (color, css, px, nested, none, prop, str)
11 |
12 | border :: StyleModifier
13 | border =
14 | box
15 | <<< style \(LumiTheme theme) ->
16 | css
17 | { label: str "border"
18 | , borderWidth: px 1
19 | , borderColor: color theme.colors.black4
20 | , borderStyle: solid
21 | , padding: px2 8 16
22 | }
23 |
24 | _round :: StyleModifier
25 | _round =
26 | style_
27 | $ css
28 | { borderRadius: px 4
29 | }
30 |
31 | _topBottom :: StyleModifier
32 | _topBottom =
33 | style_
34 | ( css
35 | { borderLeft: none
36 | , borderRight: none
37 | , borderRadius: px 0
38 | , paddingLeft: px 0
39 | , paddingRight: px 0
40 | }
41 | )
42 |
43 | _interactive :: StyleModifier
44 | _interactive =
45 | Box._interactive
46 | <<< Box._focusable
47 | <<< style \(LumiTheme theme) ->
48 | css
49 | { "&:hover":
50 | nested
51 | $ css
52 | { borderColor: color theme.colors.black2
53 | }
54 | }
55 |
56 | _listSpaced :: StyleModifier
57 | _listSpaced =
58 | style \(LumiTheme theme) ->
59 | css
60 | { "&:not(:first-child)":
61 | nested
62 | $ css
63 | { marginTop: prop S8
64 | }
65 | }
66 |
67 | _listCompact :: StyleModifier
68 | _listCompact =
69 | style \(LumiTheme theme) ->
70 | css
71 | { "&:first-child":
72 | nested
73 | $ css
74 | { borderTopColor: color theme.colors.transparent
75 | }
76 | , "&:last-child":
77 | nested
78 | $ css
79 | { borderBottomColor: color theme.colors.transparent
80 | }
81 | , "&:not(:first-child)":
82 | nested
83 | $ css
84 | { marginTop: px (-1)
85 | , ":not(:hover)":
86 | nested
87 | $ css
88 | { borderTopColor: color theme.colors.transparent
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Box.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Box where
2 |
3 | import Prelude
4 |
5 | import Color (cssStringHSLA)
6 | import Lumi.Styles (StyleModifier, auto, baseline, borderBox, center, column, flex, flexEnd, flexStart, manipulation, none, pointer, px, row, spaceAround, spaceBetween, spaceEvenly, stretch, style, style_, wrap)
7 | import Lumi.Styles.Theme (LumiTheme(..))
8 | import React.Basic.Emotion (class IsStyleProperty, css, nested, prop, str)
9 |
10 | box :: StyleModifier
11 | box =
12 | style_
13 | $ css
14 | { label: str "box"
15 | , display: flex
16 | , flexDirection: column
17 | , boxSizing: borderBox
18 | , minHeight: px 0
19 | , minWidth: auto
20 | , flex: str "0 0 auto"
21 | , margin: px 0
22 | , padding: px 0
23 | }
24 |
25 | _row :: StyleModifier
26 | _row =
27 | style_
28 | $ css { flexDirection: row }
29 |
30 | _column :: StyleModifier
31 | _column =
32 | style_
33 | $ css { flexDirection: column }
34 |
35 | _wrap :: StyleModifier
36 | _wrap =
37 | style_
38 | $ css { flexWrap: wrap }
39 |
40 | _flex :: StyleModifier
41 | _flex =
42 | style_
43 | $ css { flex: str "1" }
44 |
45 | data FlexAlign
46 | = Start
47 | | End
48 | | Center
49 | | Stretch
50 | | Baseline
51 | | SpaceAround
52 | | SpaceBetween
53 | | SpaceEvenly
54 |
55 | instance isStylePropertyFlexAlign :: IsStyleProperty FlexAlign where
56 | prop a =
57 | case a of
58 | Start -> flexStart
59 | End -> flexEnd
60 | Center -> center
61 | Stretch -> stretch
62 | Baseline -> baseline
63 | SpaceAround -> spaceAround
64 | SpaceBetween -> spaceBetween
65 | SpaceEvenly -> spaceEvenly
66 |
67 | _justify :: FlexAlign -> StyleModifier
68 | _justify a = style_ $ css { justifyContent: prop a }
69 |
70 | _align :: FlexAlign -> StyleModifier
71 | _align a = style_ $ css { alignItems: prop a }
72 |
73 | _alignSelf :: FlexAlign -> StyleModifier
74 | _alignSelf a = style_ $ css { alignSelf: prop a }
75 |
76 | _interactive :: StyleModifier
77 | _interactive =
78 | style_
79 | $ css
80 | $ { touchAction: manipulation
81 | , userSelect: none
82 | , cursor: pointer
83 | }
84 |
85 | _focusable :: StyleModifier
86 | _focusable =
87 | style \(LumiTheme theme) ->
88 | css
89 | { "&:focus-within, &:active":
90 | nested
91 | $ css
92 | { outline: str "0"
93 | , boxShadow: str ("0 0 0 3px " <> cssStringHSLA theme.colors.primary3)
94 | }
95 | , "&::-moz-focus-inner":
96 | nested
97 | $ css
98 | { border: str "0"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Clip.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Clip where
2 |
3 | import Prelude
4 |
5 | import Lumi.Styles (StyleModifier, style)
6 | import Lumi.Styles.Border (_round, border)
7 | import Lumi.Styles.Box (FlexAlign(..), _justify, _row)
8 | import Lumi.Styles.Theme (LumiTheme(..))
9 | import React.Basic.Emotion (color, css)
10 |
11 | clip :: StyleModifier
12 | clip =
13 | border
14 | <<< _round
15 | <<< _row
16 | <<< _justify SpaceBetween
17 | <<< style \(LumiTheme { colors }) ->
18 | css
19 | { borderColor: color colors.black5
20 | , backgroundColor: color colors.black5
21 | }
22 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Link.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Link where
2 |
3 | import Prelude
4 |
5 | import Lumi.Styles (StyleModifier, color, nested, none, pointer, style, underline)
6 | import Lumi.Styles.Box (box)
7 | import Lumi.Styles.Theme (LumiTheme(..))
8 | import React.Basic.Emotion (css)
9 |
10 | link :: StyleModifier
11 | link =
12 | box
13 | <<< style \(LumiTheme theme) ->
14 | css
15 | { color: color theme.colors.primary
16 | , textDecoration: none
17 | , "&:visited":
18 | nested
19 | $ css
20 | { color: color theme.colors.primary
21 | , textDecoration: none
22 | }
23 | , "&:hover":
24 | nested
25 | $ css
26 | { cursor: pointer
27 | , textDecoration: underline
28 | }
29 | }
30 |
31 | secondary :: StyleModifier
32 | secondary = style \(LumiTheme theme) ->
33 | css
34 | { color: color theme.colors.black
35 | , textDecoration: none
36 | , "&:visited":
37 | nested
38 | $ css
39 | { color: color theme.colors.black
40 | , textDecoration: none
41 | }
42 | , "&:hover":
43 | nested
44 | $ css
45 | { cursor: pointer
46 | , textDecoration: underline
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Loader.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Loader where
2 |
3 |
4 | import Lumi.Styles (Style, StyleModifier, StyleProperty, borderBox, color, css, inlineBlock, keyframes, percent, px, solid, str, style)
5 | import Lumi.Styles.Theme (LumiTheme(..))
6 |
7 | loader :: StyleModifier
8 | loader =
9 | style \(LumiTheme { colors }) ->
10 | ( mkLoader
11 | { color: color colors.black1
12 | , radius: px 38
13 | , borderWidth: px 5
14 | }
15 | )
16 |
17 | spin :: StyleProperty
18 | spin =
19 | keyframes
20 | { from: css { transform: str "rotate(0deg)" }
21 | , to: css { transform: str "rotate(360deg)" }
22 | }
23 |
24 | mkLoader ::
25 | { color :: StyleProperty
26 | , radius :: StyleProperty
27 | , borderWidth :: StyleProperty
28 | } ->
29 | Style
30 | mkLoader { color: c, radius, borderWidth } =
31 | css
32 | { boxSizing: borderBox
33 | , content: str "\"\""
34 | , display: inlineBlock
35 | , height: radius
36 | , width: radius
37 | , borderWidth: borderWidth
38 | , borderStyle: solid
39 | , borderColor: c
40 | , borderTopColor: str "transparent"
41 | , borderRadius: percent 50.0
42 | , animation: str "1s infinite linear"
43 | , animationName: spin
44 | }
45 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Responsive.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Responsive where
2 |
3 | import Prelude
4 |
5 | import Lumi.Styles (Style, StyleModifier, css, nested, style, toCSS)
6 |
7 | -- | Create a style modifier that, only in a desktop-sized screen, applies the
8 | -- | styles accumulated in the modifier passed in as argument.
9 | -- |
10 | -- | NOTE: the value passed in as argument must be a props modifier that touches
11 | -- | no component-specific props, a property that currently defines style
12 | -- | modifiers.
13 | onDesktop :: StyleModifier -> StyleModifier
14 | onDesktop m =
15 | style \theme ->
16 | desktopQuery
17 | $ toCSS m
18 | $ theme
19 |
20 | -- | Guard a style so that it's only applied in a desktop resolution.
21 | desktopQuery :: Style -> Style
22 | desktopQuery style =
23 | css
24 | { "@media (min-width: 860px)": nested style
25 | }
26 |
27 | -- | Create a style modifier that, only in a mobile-sized screen, applies the
28 | -- | styles accumulated in the modifier passed in as argument.
29 | -- |
30 | -- | NOTE: the value passed in as argument must be a props modifier that touches
31 | -- | no component-specific props, a property that currently defines style
32 | -- | modifiers.
33 | onMobile :: StyleModifier -> StyleModifier
34 | onMobile m =
35 | style \theme ->
36 | mobileQuery
37 | $ toCSS m
38 | $ theme
39 |
40 | -- | Guard a style so that it's only applied in a mobile screen resolution.
41 | mobileQuery :: Style -> Style
42 | mobileQuery style =
43 | css
44 | { "@media (max-width: 859px)": nested style
45 | }
46 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Slat.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Slat
2 | ( slat
3 | , slatColumn
4 | , module Border
5 | ) where
6 |
7 | import Prelude
8 |
9 | import Lumi.Components.Spacing (Space(S16))
10 | import Lumi.Styles (StyleModifier, style_)
11 | import Lumi.Styles.Box (FlexAlign(..), _align, _justify, _row)
12 | import Lumi.Styles.Border (border)
13 | import Lumi.Styles.Border hiding (border) as Border
14 | import React.Basic.Emotion (css, nested, prop, str, unset)
15 |
16 | slat :: StyleModifier
17 | slat =
18 | border
19 | <<< _row
20 | <<< _align Center
21 | <<< _justify SpaceBetween
22 | <<< style_
23 | ( css
24 | { label: str "slat"
25 | , flex: str "0 0 content"
26 | , color: unset
27 | , backgroundColor: unset
28 | , textDecoration: unset
29 | }
30 | )
31 |
32 | slatColumn :: Int -> StyleModifier
33 | slatColumn flexGrow =
34 | style_
35 | $ css
36 | $ { flexGrow: str (show flexGrow)
37 | , "&:not(:first-child)":
38 | nested
39 | $ css
40 | { marginLeft: prop S16
41 | , alignItems: prop End
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/Lumi/Styles/Theme.purs:
--------------------------------------------------------------------------------
1 | module Lumi.Styles.Theme where
2 |
3 | import Prelude
4 |
5 | import Data.Int as Int
6 | import Data.Newtype (class Newtype)
7 | import Effect.Unsafe (unsafePerformEffect)
8 | import Lumi.Components.Color (Color, ColorMap, ColorName, colorNames, colors)
9 | import React.Basic.Classic (ReactContext, createContext)
10 | import React.Basic.Hooks (Hook, UseContext, useContext)
11 |
12 | newtype LumiTheme
13 | = LumiTheme
14 | { colors :: ColorMap Color
15 | , colorNames :: ColorMap ColorName
16 | , fontSizes :: TextMap Int
17 | -- | We store the factors for finding the line height and bottom margin for a
18 | -- | given font size instead of actual values so that it's easier to override
19 | -- | the theme with different font sizes while following the Lumi style
20 | -- | guidelines.
21 | , lineHeightFactor :: Number
22 | , textMarginFactor :: Number
23 | }
24 |
25 | type TextMap a =
26 | { subtext :: a
27 | , body :: a
28 | , subSubsectionHeader :: a
29 | , subsectionHeader :: a
30 | , sectionHeader :: a
31 | , title :: a
32 | , mainHeader :: a
33 | }
34 |
35 | derive instance newtypeLumiTheme :: Newtype LumiTheme _
36 |
37 | defaultTheme :: LumiTheme
38 | defaultTheme = LumiTheme
39 | { colors
40 | , colorNames
41 | , fontSizes:
42 | { subtext: 12
43 | , body: 14
44 | , subSubsectionHeader: 15
45 | , subsectionHeader: 17
46 | , sectionHeader: 20
47 | , title: 24
48 | , mainHeader: 30
49 | }
50 | , lineHeightFactor: 17.0 / 14.0
51 | , textMarginFactor: 9.0 / 17.0
52 | }
53 |
54 | lumiThemeContext :: ReactContext LumiTheme
55 | lumiThemeContext =
56 | unsafePerformEffect do
57 | createContext defaultTheme
58 |
59 | useTheme :: Hook (UseContext LumiTheme) LumiTheme
60 | useTheme = useContext lumiThemeContext
61 |
62 | textFontSize :: LumiTheme -> (forall a. TextMap a -> a) -> Int
63 | textFontSize (LumiTheme { fontSizes }) selector = selector fontSizes
64 |
65 | textLineHeight :: LumiTheme -> (forall a. TextMap a -> a) -> Int
66 | textLineHeight (LumiTheme { fontSizes, lineHeightFactor }) selector =
67 | Int.floor $ Int.toNumber (selector fontSizes) * lineHeightFactor
68 |
69 | textMargin :: LumiTheme -> (forall a. TextMap a -> a) -> Int
70 | textMargin (LumiTheme { fontSizes, textMarginFactor }) selector =
71 | Int.floor $ Int.toNumber (selector fontSizes) * textMarginFactor
72 |
--------------------------------------------------------------------------------