Checkbox with label}
49 | subtitle={
50 | Checkbox can be provided with a label.
51 | }
52 | content={
53 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | }
65 | code={[toggleLabel, dark]}
66 | />
67 |
68 |
73 |
74 |
75 | )
76 | }
77 | }
78 |
79 | export default CheckboxView
80 |
--------------------------------------------------------------------------------
/src/util/dom-helpers.js:
--------------------------------------------------------------------------------
1 | const defaultRootCSSVariables = {
2 | '--light-bg': '#E4EBF5',
3 | '--light-bg-dark-shadow': '#bec8e4',
4 | '--light-bg-light-shadow': '#ffffff',
5 | '--dark-bg': '#444444',
6 | '--dark-bg-dark-shadow': '#363636',
7 | '--dark-bg-light-shadow': '#525252',
8 | '--white': '#ffffff',
9 | '--black': '#000000',
10 | '--primary': '#f71b94',
11 | '--primary-dark': '#aa0660',
12 | '--primary-light': '#fa7ac0',
13 | '--g-text-color-light': 'rgba(0, 0, 0, 0.87)',
14 | '--g-text-color-disabled-light': 'rgba(0, 0, 0, 0.38)',
15 | '--g-text-color-secondary-light': 'rgba(0, 0, 0, 0.60)',
16 | '--g-text-color-dark': 'rgba(255, 255, 255, 0.87)',
17 | '--g-text-color-disabled-dark': 'rgba(255, 255, 255, 0.38)',
18 | '--g-text-color-secondary-dark': 'rgba(255, 255, 255, 0.60)',
19 | '--g-bg-color-disabled-light': '#dee5e8',
20 | '--g-bg-color-disabled-dark': '#727272'
21 | }
22 |
23 | /**
24 | * Function to change a given css variable
25 | * @param {*} element
26 | * @param {*} variable
27 | * @param {*} value
28 | */
29 | export const setCSSVariable = (element, variable, value) => {
30 | if (element && value) {
31 | element.style.setProperty(variable, String(value))
32 | }
33 | }
34 |
35 | /**
36 | * Getter function to fetch all default css variables object
37 | */
38 | export const getDefaultThemeVariables = () => {
39 | return defaultRootCSSVariables
40 | }
41 |
42 | /**
43 | * Function to change root css variables
44 | * @param {*} themeObject
45 | */
46 | export const overrideThemeVariables = (themeObject) => {
47 | const root = document.querySelector(':root')
48 | const themeVariables = Object.keys(themeObject)
49 | if (root && themeObject) {
50 | themeVariables.forEach((themeVar) => {
51 | const varValue = themeObject[themeVar]
52 | if (varValue && themeVar.startsWith('--')) {
53 | setCSSVariable(root, themeVar, varValue)
54 | }
55 | })
56 | }
57 | }
58 |
59 | /**
60 | *
61 | * @param {*} path
62 | * @param {*} element
63 | */
64 | export const detectElementInDOM = (path = [], element = 'null') => {
65 | return path
66 | .map((elem) => elem.nodeName)
67 | .join('-')
68 | .toLowerCase()
69 | .includes(element.toLowerCase())
70 | }
71 |
72 | /**
73 | *
74 | * @param {*} event : DOM click event
75 | * @param {*} node : DOM node to detect click inside of
76 | */
77 |
78 | export const findClickInside = (event, node) => {
79 | let currentNode = event.target
80 | try {
81 | do {
82 | if (currentNode === node) {
83 | // click is inside
84 | return true
85 | }
86 | currentNode = currentNode.parentNode
87 | } while (currentNode)
88 | // click is outside
89 | return false
90 | } catch (err) {
91 | throw new Error(err)
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/card/CardHeader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import styles from './Card.module.css'
4 | import { getModuleClasses, passDownProp } from '../../util'
5 | import {
6 | G_BOOL,
7 | G_NODE,
8 | DEFAULT_PROPS,
9 | DEFAULT_PROPS_TYPE,
10 | CARD_HEAD_PASS_DOWN,
11 | CARD_CHILD_PASS_DOWN
12 | } from '../../assets/index'
13 |
14 | class CardHeader extends React.Component {
15 | static displayName = 'NuCardHeader'
16 |
17 | static defaultProps = DEFAULT_PROPS
18 |
19 | static propTypes = {
20 | title: G_NODE,
21 | avatar: G_NODE,
22 | action: G_NODE,
23 | rounded: G_BOOL,
24 | subtitle: G_NODE,
25 | disabled: G_BOOL,
26 | ...DEFAULT_PROPS_TYPE
27 | }
28 |
29 | getClass(classType) {
30 | const { dark, rounded } = this.props
31 | switch (classType) {
32 | case 'wrapper':
33 | return getModuleClasses(
34 | styles,
35 | `
36 | nu-card-header
37 | nu-card-header--${dark ? 'dark' : 'light'}
38 | ${rounded ? 'nu-card-header--rounded' : ''}
39 | `
40 | )
41 | case 'content':
42 | return getModuleClasses(styles, 'nu-header-content')
43 | case 'avatar':
44 | return getModuleClasses(styles, 'nu-header-avatar')
45 | case 'content-left':
46 | return getModuleClasses(
47 | styles,
48 | 'nu-header-content nu-header-content--left'
49 | )
50 | }
51 | }
52 |
53 | render() {
54 | const {
55 | style,
56 | title,
57 | avatar,
58 | action,
59 | children,
60 | subtitle,
61 | className
62 | } = this.props
63 | const cardTitle = passDownProp(title, this.props, CARD_HEAD_PASS_DOWN)
64 | const cardAvatar = passDownProp(avatar, this.props, CARD_HEAD_PASS_DOWN)
65 | const cardAction = passDownProp(action, this.props, CARD_HEAD_PASS_DOWN)
66 | const cardSubTitle = passDownProp(subtitle, this.props, CARD_HEAD_PASS_DOWN)
67 | const cardChildren = passDownProp(
68 | children,
69 | this.props,
70 | CARD_CHILD_PASS_DOWN
71 | )
72 | return (
73 |
74 | {cardAvatar || cardTitle || cardSubTitle || cardAction ? (
75 |
76 |
77 | {cardAvatar ? (
78 |
{cardAvatar}
79 | ) : null}
80 |
81 | {cardTitle}
82 | {cardSubTitle}
83 |
84 |
85 | {cardAction}
86 |
87 | ) : null}
88 | {cardChildren}
89 |
90 | )
91 | }
92 | }
93 |
94 | export default CardHeader
95 |
--------------------------------------------------------------------------------
/example/src/docs/api/chip-api.js:
--------------------------------------------------------------------------------
1 | import { createApiDoc, defaultApiDoc } from '../index.js'
2 |
3 | export const chipApi = (dark) => {
4 | return [
5 | ...defaultApiDoc(dark),
6 | createApiDoc(
7 | dark,
8 | 'label',
9 | 'Boolean',
10 | 'false',
11 | 'Removes the large border radius.'
12 | ),
13 | createApiDoc(dark, 'bordered', 'Boolean', 'false', 'Adds a thin border.'),
14 | createApiDoc(
15 | dark,
16 | 'outlined',
17 | 'Boolean',
18 | 'false',
19 | `Removes the chip's elevation and adds a thin border.`
20 | ),
21 | createApiDoc(
22 | dark,
23 | 'closable',
24 | 'Boolean',
25 | 'false',
26 | 'Adds a close icon at the end of chip.'
27 | ),
28 | createApiDoc(
29 | dark,
30 | 'bordered',
31 | 'Boolean',
32 | 'false',
33 | 'Adds a close icon at the end of chip.'
34 | ),
35 | createApiDoc(
36 | dark,
37 | 'closable',
38 | 'Boolean',
39 | 'false',
40 | 'Adds a close icon at the end of chip.'
41 | ),
42 | createApiDoc(
43 | dark,
44 | 'action',
45 | 'Node',
46 | '',
47 | 'Designates a specific Node for action icon, can be anything from icon to text.'
48 | ),
49 | createApiDoc(
50 | dark,
51 | 'prepend',
52 | 'Node',
53 | '',
54 | 'Adds a specific Node before chip content, can be anything from icon to text.'
55 | ),
56 | createApiDoc(
57 | dark,
58 | 'append',
59 | 'Node',
60 | '',
61 | 'Adds a specific Node after chip content, can be anything from icon to text.'
62 | ),
63 | createApiDoc(
64 | dark,
65 | 'closeIcon',
66 | 'Node',
67 | '',
68 | 'Override the default icon used for closable chips.'
69 | ),
70 | createApiDoc(
71 | dark,
72 | 'type',
73 | 'String',
74 | "'success' | 'info' | 'warning' | 'error'",
75 | 'Specify a success, info, warning or error chip color.'
76 | ),
77 | createApiDoc(
78 | dark,
79 | 'color',
80 | 'String',
81 | '',
82 | 'Applies specified color to the chip component.'
83 | ),
84 | createApiDoc(
85 | dark,
86 | 'link',
87 | 'String',
88 | '',
89 | 'Wraps the chip in tag and sets its href to the given link value.'
90 | ),
91 | createApiDoc(
92 | dark,
93 | 'active',
94 | 'Boolean',
95 | 'false',
96 | 'Controls whether the chip is in active state.'
97 | ),
98 | createApiDoc(dark, 'flat', 'Boolean', 'false', 'Removes elevation.'),
99 | createApiDoc(
100 | dark,
101 | 'onAction',
102 | 'Function',
103 | '',
104 | 'Called when action is clicked.'
105 | )
106 | ]
107 | }
108 |
--------------------------------------------------------------------------------
/src/components/button/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import styles from './Button.module.css'
4 |
5 | import { getModuleClasses, passDownProp, pickKeys } from '../../util'
6 | import {
7 | BUTTON_PROP_TYPES,
8 | DEFAULT_PROPS,
9 | MOUSE_EVENTS,
10 | SIZES
11 | } from '../../assets/index'
12 |
13 | class Button extends React.Component {
14 | static displayName = 'NuButton'
15 |
16 | static defaultProps = {
17 | size: 'medium',
18 | ...DEFAULT_PROPS
19 | }
20 |
21 | static propTypes = BUTTON_PROP_TYPES
22 |
23 | getValidSize(size) {
24 | return SIZES.find((s) => s === size) || 'medium'
25 | }
26 |
27 | getClasses(classType) {
28 | const {
29 | type,
30 | dark,
31 | size,
32 | text,
33 | block,
34 | active,
35 | noPress,
36 | rounded,
37 | disabled,
38 | bordered,
39 | outlined,
40 | depressed
41 | } = this.props
42 | switch (classType) {
43 | case 'container':
44 | return getModuleClasses(
45 | styles,
46 | `
47 | nu-button
48 | cursor-pointer
49 | nu-button--${type}
50 | ${text ? 'nu-button--text' : ''}
51 | ${block ? 'nu-button--block' : ''}
52 | ${active ? 'nu-button--active' : ''}
53 | nu-button--${this.getValidSize(size)}
54 | nu-button--${dark ? 'dark' : 'light'}
55 | ${rounded ? 'nu-button--rounded' : ''}
56 | ${noPress ? 'nu-button--no-press' : ''}
57 | ${outlined ? 'nu-button--outlined' : ''}
58 | ${bordered ? 'nu-button--bordered' : ''}
59 | ${disabled ? 'nu-button--disabled' : ''}
60 | ${depressed ? 'nu-button--depressed' : ''}
61 | `
62 | )
63 | case 'input':
64 | return getModuleClasses(styles, 'nu-button-inner')
65 | default:
66 | break
67 | }
68 | }
69 |
70 | render() {
71 | const {
72 | id,
73 | type,
74 | style,
75 | color,
76 | bgColor,
77 | disabled,
78 | outlined,
79 | children,
80 | className
81 | } = this.props
82 | const btnChildren = passDownProp(children, this.props, 'dark')
83 | return (
84 |
95 |
98 |
99 | )
100 | }
101 | }
102 |
103 | export default Button
104 |
--------------------------------------------------------------------------------
/src/transitions/SlideCarousel.jsx:
--------------------------------------------------------------------------------
1 | import React, { cloneElement } from 'react'
2 |
3 | import TransitionWrapper from './TransitionWrapper'
4 | import { G_NUM, G_BOOL, G_STRING } from '../assets'
5 | import { callCallback } from '../util'
6 |
7 | class SlideCarousel extends React.Component {
8 | static displayName = 'NuSlideCarousel'
9 |
10 | static defaultProps = {
11 | axis: 'Y',
12 | duration: 250,
13 | reverse: false,
14 | origin: 'center center'
15 | }
16 |
17 | static propTypes = {
18 | axis: G_STRING,
19 | reverse: G_BOOL,
20 | duration: G_NUM,
21 | origin: G_STRING
22 | }
23 |
24 | slideTransition = {
25 | transition: `transform ${this.props.duration}ms ease-in-out`
26 | }
27 |
28 | transitionStyles = {
29 | enteringX: { transform: 'translateX(-100%)' },
30 | enteringXR: { transform: 'translateX(100%)' },
31 | enteringY: { transform: 'translateY(-100%)' },
32 | enteringYR: { transform: 'translateY(100%)' },
33 | entered: {
34 | transform: 'translate(0px)',
35 | ...this.slideTransition
36 | },
37 | exitingX: {
38 | ...this.slideTransition,
39 | transform: 'translateX(100%)'
40 | },
41 | exitingXR: {
42 | ...this.slideTransition,
43 | transform: 'translateX(-100%)'
44 | },
45 | exitingY: {
46 | ...this.slideTransition,
47 | transform: 'translateY(100%)'
48 | },
49 | exitingYR: {
50 | ...this.slideTransition,
51 | transform: 'translateY(-100%)'
52 | }
53 | }
54 |
55 | constructor(props) {
56 | super(props)
57 | this.state = { status: '' }
58 | this.updateStatus = this.updateStatus.bind(this)
59 | }
60 |
61 | get type() {
62 | const { axis, reverse } = this.props
63 | return `${axis.toUpperCase()}${reverse ? 'R' : ''}`
64 | }
65 |
66 | getStatus(status) {
67 | return status === 'entering' || status === 'exiting'
68 | ? `${status}${this.type}`
69 | : status
70 | }
71 |
72 | updateStatus(status) {
73 | this.setState({ status })
74 |
75 | callCallback(this.props.onUpdate, status)
76 | }
77 |
78 | render() {
79 | const { status } = this.state
80 | const { origin, duration, children: child, ...otherProps } = this.props
81 | const display = status ? (status === 'exited' ? 'none' : undefined) : 'none'
82 | return (
83 |
88 | {cloneElement(child, {
89 | style: {
90 | display,
91 | top: '0',
92 | position: 'absolute',
93 | transformOrigin: origin,
94 | ...this.transitionStyles[this.getStatus(status)],
95 | ...child.props.style
96 | }
97 | })}
98 |
99 | )
100 | }
101 | }
102 |
103 | export default SlideCarousel
104 |
--------------------------------------------------------------------------------
/src/components/carousel/Carousel.module.css:
--------------------------------------------------------------------------------
1 | .nu-carousel {
2 | width: 100%;
3 | overflow: hidden;
4 | position: relative;
5 | }
6 |
7 | .nu-carousel.nu-carousel--light {
8 | --text-color: var(--g-text-color-light);
9 | }
10 |
11 | .nu-carousel.nu-carousel--dark {
12 | --text-color: var(--g-text-color-dark);
13 | }
14 |
15 | .nu-carousel-container {
16 | height: inherit;
17 | overflow: inherit;
18 | position: inherit;
19 | }
20 |
21 | .nu-carousel-controls {
22 | bottom: 0;
23 | z-index: 1;
24 | width: 100%;
25 | height: 48px;
26 | display: flex;
27 | margin: 0px auto;
28 | position: absolute;
29 | align-items: center;
30 | justify-content: center;
31 | }
32 |
33 | .nu-carousel-delimiter {
34 | margin: 8px;
35 | width: 16px;
36 | height: 16px;
37 | cursor: pointer;
38 | border-radius: 50%;
39 | transition: all 250ms ease-in-out;
40 | border: 2px solid var(--text-color);
41 | }
42 |
43 | .nu-carousel-delimiter:after {
44 | top: 50%;
45 | width: 0%;
46 | height: 0%;
47 | content: '';
48 | margin: auto;
49 | display: block;
50 | border-radius: 50%;
51 | position: relative;
52 | transform: translateY(-50%);
53 | background: var(--text-color);
54 | transition: all 250ms ease-in-out;
55 | }
56 |
57 | .nu-carousel-delimiter.nu-carousel-delimiter--active {
58 | border: none;
59 | }
60 |
61 | .nu-carousel-delimiter.nu-carousel-delimiter--active:after {
62 | height: 100%;
63 | width: 100%;
64 | }
65 |
66 | .nu-carousel:hover .nu-carousel-arrow.nu-carousel-arrow--prev {
67 | transform: scale(1.5) rotate(180deg) translate(0px);
68 | }
69 | .nu-carousel:hover .nu-carousel-arrow.nu-carousel-arrow--next {
70 | transform: scale(1.5) translate(0px);
71 | }
72 |
73 | .nu-carousel-arrow {
74 | z-index: 2;
75 | font-size: 48px;
76 | cursor: pointer;
77 | margin: 0px 16px;
78 | user-select: none;
79 | padding: 0px 16px;
80 | line-height: 48px;
81 | border-radius: 50%;
82 | position: absolute;
83 | top: calc(50% - 28px);
84 | color: var(--text-color);
85 | transition: transform 0.3s;
86 | transform-origin: center center;
87 | -webkit-tap-highlight-color: transparent !important;
88 | }
89 |
90 | .nu-carousel-arrow.nu-carousel-arrow--prev {
91 | padding-bottom: 8px;
92 | transform: scale(1.5) rotate(180deg) translate(150%);
93 | }
94 |
95 | .nu-carousel-arrow.nu-carousel-arrow--next {
96 | right: 0;
97 | transform: scale(1.5) translate(150%);
98 | }
99 |
100 | .nu-carousel-arrow.nu-carousel-arrow--prev.nu-carousel-arrow--always {
101 | transform: scale(1.5) rotate(180deg) translate(0px) !important;
102 | }
103 |
104 | .nu-carousel-arrow.nu-carousel-arrow--next.nu-carousel-arrow--always {
105 | transform: scale(1.5) translate(0px) !important;
106 | }
107 |
108 | .nu-carousel-item {
109 | width: 100%;
110 | height: 100%;
111 | display: flex;
112 | contain: strict;
113 | align-items: center;
114 | justify-content: center;
115 | }
116 |
--------------------------------------------------------------------------------
/example/src/pages/IconButtonView.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Icon from '@mdi/react'
4 | import {
5 | mdiRun,
6 | mdiStar,
7 | mdiBroom,
8 | mdiSpeaker,
9 | mdiOpacity,
10 | mdiTrashCanOutline
11 | } from '@mdi/js'
12 |
13 | import { Card, H4, Divider, H6, Subtitle1, IconButton } from 'ui-neumorphism'
14 |
15 | import DocCard from '../containers/DocCard.jsx'
16 | import ApiCard from '../containers/ApiCard.jsx'
17 | import { iconButtons, iconButtonApi } from '../docs/'
18 |
19 | const url =
20 | 'https://github.com/AKAspanion/ui-neumorphism/blob/master/example/src/pages/IconButtonView.jsx'
21 |
22 | class IconButtonView extends React.Component {
23 | render() {
24 | const { dark } = this.props
25 | return (
26 |
27 |
32 | Icon buttons are commonly found in app bars and toolbars.
33 |
34 | Icons are appropriate for buttons that allow a single choice to be
35 | selected or deselected, such as adding or removing a star to an item.
36 |
37 |
45 |
46 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | }
75 | code={[iconButtons, dark]}
76 | />
77 |
78 |
83 |
84 |
85 | )
86 | }
87 | }
88 |
89 | export default IconButtonView
90 |
--------------------------------------------------------------------------------
/example/src/docs/api/badge-api.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createApiDoc, defaultApiDoc } from '../index.js'
3 |
4 | const align = (dark, type) =>
5 | createApiDoc(
6 | dark,
7 | type,
8 | 'Boolean',
9 | 'false',
10 | `Aligns the badge towards the ${type}.`
11 | )
12 |
13 | export const badgeApi = (dark) => {
14 | return [
15 | ...defaultApiDoc(dark),
16 | createApiDoc(
17 | dark,
18 | 'dot',
19 | 'Boolean',
20 | 'false',
21 | 'Reduce the size of the badge and hide its contents.'
22 | ),
23 | createApiDoc(
24 | dark,
25 | 'max',
26 | 'Number',
27 | '',
28 | 'Caps the value of the badge content at this number.'
29 | ),
30 | align(dark, 'left'),
31 | align(dark, 'bottom'),
32 | createApiDoc(
33 | dark,
34 | 'color',
35 | 'String',
36 | --white,
37 | 'The css color of the badge.'
38 | ),
39 | createApiDoc(
40 | dark,
41 | 'bgColor',
42 | 'String',
43 | --primary,
44 | 'The css background color of the badge.'
45 | ),
46 | createApiDoc(
47 | dark,
48 | 'borderColor',
49 | 'String',
50 | --white,
51 | 'The css border color of the badge.'
52 | ),
53 | createApiDoc(
54 | dark,
55 | 'content',
56 | 'Node',
57 | '',
58 | 'Any content you want injected into the badge.'
59 | ),
60 | createApiDoc(
61 | dark,
62 | 'label',
63 | 'String',
64 | '',
65 |
66 | The aria-label used for the badge
67 |
68 | ),
69 | createApiDoc(
70 | dark,
71 | 'overlap',
72 | 'Boolean',
73 | 'false',
74 | 'Overlaps the given content on top of the component.'
75 | ),
76 | createApiDoc(
77 | dark,
78 | 'square',
79 | 'Boolean',
80 | 'false',
81 | "Removes the badge's border-radius."
82 | ),
83 | createApiDoc(
84 | dark,
85 | 'visible',
86 | 'Boolean',
87 | 'true',
88 | 'Controls whether the badge is visible or hidden.'
89 | ),
90 | createApiDoc(
91 | dark,
92 | 'inline',
93 | 'Boolean',
94 | 'false',
95 |
96 | Moves the badge to be inline with the wrapping element. Supports the
97 | usage of the left prop.
98 |
99 | ),
100 | createApiDoc(
101 | dark,
102 | 'bordered',
103 | 'Boolean',
104 | 'false',
105 |
106 | Applies a 2px border around the badge . With dot property,
107 | width is 1.5px.
108 |
109 | ),
110 | createApiDoc(
111 | dark,
112 | 'noPadding',
113 | 'Boolean',
114 | 'false',
115 | Removes padding from badge content.
116 | )
117 | ]
118 | }
119 |
--------------------------------------------------------------------------------
/src/components/button-toggle-group/ToggleButtonGroup.jsx:
--------------------------------------------------------------------------------
1 | import React, { Children, cloneElement } from 'react'
2 |
3 | import { ToggleButton } from '../index'
4 |
5 | import { callCallback, passDownProp } from '../../util'
6 | import {
7 | G_BOOL,
8 | DEFAULT_PROPS,
9 | CARD_PASS_DOWN,
10 | BUTTON_PROP_TYPES,
11 | BUTTON_GROUP_VALUE
12 | } from '../../assets'
13 |
14 | class ToggleButtonGroup extends React.Component {
15 | static displayName = 'NuToggleButtonGroup'
16 |
17 | static defaultProps = DEFAULT_PROPS
18 |
19 | static propTypes = {
20 | multiple: G_BOOL,
21 | mandatory: G_BOOL,
22 | ...BUTTON_PROP_TYPES,
23 | value: BUTTON_GROUP_VALUE
24 | }
25 |
26 | constructor(props) {
27 | super(props)
28 | this.state = {
29 | active: props.value,
30 | key: 1
31 | }
32 | }
33 |
34 | componentDidUpdate(props, state) {
35 | const { active } = this.state
36 | if (JSON.stringify(state.active) !== JSON.stringify(active)) {
37 | callCallback(props.onChange, { active })
38 | }
39 | }
40 |
41 | handleClick(event) {
42 | let active = ''
43 | const { selected, value } = event
44 | const { key, active: stateActive } = this.state
45 | const { multiple, mandatory, onClick } = this.props
46 | if (selected) {
47 | if (multiple) {
48 | active = [...(stateActive || []), value]
49 | } else {
50 | active = value
51 | }
52 | } else {
53 | if (multiple) {
54 | active = (stateActive || []).filter((a) => a !== value)
55 | if (mandatory && !active.length) {
56 | active = [value]
57 | }
58 | } else {
59 | if (mandatory) {
60 | active = value
61 | }
62 | }
63 | }
64 |
65 | this.setState({ active })
66 | this.setState({ key: key + 1 })
67 |
68 | callCallback(onClick, { event, active })
69 | }
70 |
71 | render() {
72 | const { style, children, multiple, className } = this.props
73 | const buttons = passDownProp(
74 | Children.map(children, (child) => {
75 | if (child.type === ToggleButton) {
76 | let selected = false
77 | const { active } = this.state
78 | const { value } = child.props
79 |
80 | if (Array.isArray(active)) {
81 | const trimmedActive = multiple
82 | ? active
83 | : active.filter((a, i) => i === 0)
84 | selected = !!trimmedActive.find((a) => a === value)
85 | } else {
86 | selected = active === value
87 | }
88 |
89 | return cloneElement(child, {
90 | selected,
91 | key: this.state.key,
92 | onChange: (e) => this.handleClick(e, child)
93 | })
94 | }
95 | }),
96 | this.props,
97 | ['size', 'color', ...CARD_PASS_DOWN]
98 | )
99 | return (
100 |
101 | {buttons}
102 |
103 | )
104 | }
105 | }
106 |
107 | export default ToggleButtonGroup
108 |
--------------------------------------------------------------------------------
/example/src/docs/common.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Body2, Caption, Subtitle2 } from 'ui-neumorphism'
3 | import CodeBlock from '../containers/CodeBlock.jsx'
4 |
5 | export const createApiDoc = (
6 | dark,
7 | name,
8 | type,
9 | initial,
10 | description,
11 | codeCaption,
12 | descriptionCode
13 | ) => {
14 | return {
15 | title: name,
16 | name: {name},
17 | type: (
18 |
19 | {type}
20 |
21 | ),
22 | initial: (
23 |
24 | {initial}
25 |
26 | ),
27 | description: (
28 |
29 |
30 | {description}
31 |
32 | {codeCaption ? (
33 |
34 | {codeCaption}
35 |
36 | ) : null}
37 | {descriptionCode ? (
38 |
39 | {descriptionCode}
40 |
41 | ) : null}
42 |
43 | )
44 | }
45 | }
46 |
47 | export const defaultApiDoc = (dark, nodark) => {
48 | const data = [
49 | createApiDoc(
50 | dark,
51 | 'style',
52 | 'Object',
53 | '{}',
54 | 'Styles to be applied on component container.'
55 | ),
56 | createApiDoc(
57 | dark,
58 | 'className',
59 | 'String',
60 | '',
61 | 'Classes to be applied on component container.'
62 | )
63 | ]
64 | if (!nodark) {
65 | data.unshift(
66 | createApiDoc(
67 | dark,
68 | 'dark',
69 | 'Boolean',
70 | 'false',
71 |
72 | Changes theme to dark when true.
73 |
74 | )
75 | )
76 | }
77 | return data
78 | }
79 |
80 | export const eventDoc = (dark, type, caption, code) => {
81 | return createApiDoc(
82 | dark,
83 | `on${type}`,
84 | 'Function',
85 | '',
86 |
87 | Callback for {type.toLowerCase()} event.
88 |
,
89 | caption,
90 | code
91 | )
92 | }
93 |
94 | const styleDoc = (dark, prop, type) =>
95 | createApiDoc(dark, prop, 'Number', '', `Sets the ${prop} for the ${type}.`)
96 |
97 | export const cssDimensionsApi = (dark, type) => {
98 | return [
99 | styleDoc(dark, 'width', type),
100 | styleDoc(dark, 'height', type),
101 | styleDoc(dark, 'minWidth', type),
102 | styleDoc(dark, 'maxWidth', type),
103 | styleDoc(dark, 'minHeight', type),
104 | styleDoc(dark, 'maxHeight', type)
105 | ]
106 | }
107 |
108 | export const positionApi = (dark, type) => {
109 | return ['top', 'left', 'right', 'bottom'].map((pos) =>
110 | createApiDoc(
111 | dark,
112 | pos,
113 | 'Boolean',
114 | 'false',
115 |
116 | Aligns the {type} towards the {pos}.
117 |
118 | )
119 | )
120 | }
121 |
--------------------------------------------------------------------------------
/example/src/containers/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { NavLink } from 'react-router-dom'
3 |
4 | import routes from '../routes/index.js'
5 |
6 | import { Card, withClickOutside, detectElementInDOM } from 'ui-neumorphism'
7 |
8 | class Sidebar extends React.Component {
9 | get isSmall() {
10 | const { size } = this.props
11 | return size === 'sm' || size === 'xs'
12 | }
13 |
14 | handleClick = (e) => {
15 | this.handleClickOutside(e)
16 | this.props.onClick(e)
17 | }
18 |
19 | handleClickOutside = (e) => {
20 | const { open } = this.props
21 | if (open && this.isSmall && !detectElementInDOM(e.path, 'button')) {
22 | this.props.onOutsideClick()
23 | }
24 | }
25 |
26 | componentDidMount() {
27 | document.getElementById('list-item-1').checked = true
28 | }
29 |
30 | render() {
31 | const { dark, open } = this.props
32 | return (
33 |
40 |
41 |
47 | Home
48 |
49 |
50 |
51 |
57 | Examples
58 |
59 |
60 |
61 |
62 |
65 |
66 | {routes.map((route, i) => {
67 | const { name, path } = route
68 | if (name && path !== '/home') {
69 | return (
70 | -
71 |
77 | {name}
78 |
79 |
80 | )
81 | } else {
82 | return null
83 | }
84 | })}
85 |
86 |
87 |
88 |
94 | Typography
95 |
96 |
97 |
98 | )
99 | }
100 | }
101 |
102 | export default withClickOutside(Sidebar)
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [ui-neumorphism](https://akaspanion.github.io/ui-neumorphism/)
2 |
3 | [](https://www.npmjs.com/package/ui-neumorphism) [](https://travis-ci.org/AKAspanion/ui-neumorphism) [](https://github.com/AKAspanion/ui-neumorphism/blob/master/LICENSE) [](https://standardjs.com)
4 |
5 | > React component library designed on the "new skeuomorphism" or "neumorphism" UI/UX trend.
6 |
7 | All the components are designed according to the neumorphic design trend.
8 |
9 | Library features more than 50 individual components.
10 |
11 | ## Demo
12 |
13 | 
14 |
15 | View examples [here](https://akaspanion.github.io/ui-neumorphism/examples).
16 |
17 | ## Documentation
18 |
19 | Each component, and its API is well documented, with various examples along with their code and preview.
20 |
21 | Check out the documentation [here](https://akaspanion.github.io/ui-neumorphism/).
22 |
23 | ## Install
24 |
25 | ```bash
26 | npm install --save ui-neumorphism
27 | ```
28 |
29 | ## Usage
30 |
31 | ```jsx
32 | import React, { Component } from 'react'
33 |
34 | import { Button } from 'ui-neumorphism'
35 | import 'ui-neumorphism/dist/index.css'
36 |
37 | class Example extends Component {
38 | render() {
39 | return
40 | }
41 | }
42 | ```
43 |
44 | ## Theming
45 |
46 | In neumorphism UI, theming is dead simple.
47 | It is accomplished by using and modifying root css variables for colors.
48 |
49 | To change the theme, you modify the root css variables directly or with the help of `overrideThemeVariables()` function, like this.
50 |
51 | ```javascript
52 | import React, { Component } from 'react'
53 |
54 | import { overrideThemeVariables } from 'ui-neumorphism'
55 | import 'ui-neumorphism/dist/index.css'
56 |
57 | class App extends Component {
58 | componentDidMount() {
59 | // takes an object of css variable key-value pairs
60 | overrideThemeVariables({
61 | '--light-bg': '#E9B7B9',
62 | '--light-bg-dark-shadow': '#ba9294',
63 | '--light-bg-light-shadow': '#ffdcde',
64 | '--dark-bg': '#292E35',
65 | '--dark-bg-dark-shadow': '#21252a',
66 | '--dark-bg-light-shadow': '#313740',
67 | '--primary': '#8672FB',
68 | '--primary-dark': '#4526f9',
69 | '--primary-light': '#c7befd'
70 | })
71 | }
72 |
73 | ...
74 | }
75 | ```
76 |
77 | Here `--light-bg` and `--dark-bg` are css variables that hold specific values of color.
78 |
79 | Using this power of CSS variables, you can change the app theme from anywhere in the entire application.
80 |
81 | All the css variable definition is present in root tag of [index.css](/src/components/styles.css).
82 | These variables are used to theme the library and its available for you to modify.
83 |
84 | ## License
85 |
86 | MIT © [AKAspanion](https://github.com/AKAspanion)
87 |
--------------------------------------------------------------------------------
/src/components/dialog/Dialog.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createPortal } from 'react-dom'
3 |
4 | import { Grow } from '../../index'
5 |
6 | import styles from './Dialog.module.css'
7 |
8 | import {
9 | getModuleClasses,
10 | findClickInside,
11 | passDownProp,
12 | callCallback,
13 | pickKeys
14 | } from '../../util'
15 | import {
16 | DEFAULT_PROPS,
17 | CSS_DIMENSIONS,
18 | DEFAULT_PROPS_TYPE
19 | } from '../../assets/index'
20 |
21 | class Dialog extends React.Component {
22 | static displayName = 'NuDialog'
23 |
24 | static defaultProps = {
25 | visible: false,
26 | ...DEFAULT_PROPS
27 | }
28 |
29 | static propTypes = DEFAULT_PROPS_TYPE
30 |
31 | constructor(props) {
32 | super(props)
33 | this.handleClickInside = this.handleClickInside.bind(this)
34 | }
35 |
36 | get dialog() {
37 | const sizeStyles = {}
38 | const { style, visible, children, className } = this.props
39 | const dialogChildren = passDownProp(children, this.props, ['dark'])
40 |
41 | const pickedStyles = pickKeys(this.props, CSS_DIMENSIONS)
42 | Object.keys(pickedStyles).map(
43 | (key) => (sizeStyles[key] = `${pickedStyles[key]}px`)
44 | )
45 |
46 | return createPortal(
47 |
52 |
53 |
54 |
60 | {dialogChildren}
61 |
62 |
63 |
,
64 | document.body
65 | )
66 | }
67 |
68 | getClasses(name) {
69 | const { dark } = this.props
70 | switch (name) {
71 | case 'dialog':
72 | return getModuleClasses(
73 | styles,
74 | `
75 | nu-dialog
76 | nu-dialog--${dark ? 'dark' : 'light'}
77 | `
78 | )
79 | default:
80 | return getModuleClasses(styles, name)
81 | }
82 | }
83 |
84 | handleClickInside(e) {
85 | const contentDOM = document.getElementById('nudialogcontent')
86 | const isContentClicked = findClickInside(e, contentDOM)
87 | const { onClose, persistent } = this.props
88 | if (!isContentClicked && !persistent) {
89 | callCallback(onClose, true)
90 | }
91 | }
92 |
93 | changeBodyAttrs() {
94 | const { visible } = this.props
95 | if (visible) {
96 | document.body.style.overflow = 'hidden'
97 | } else {
98 | document.body.style.overflow = null
99 | }
100 | }
101 |
102 | componentDidUpdate() {
103 | this.changeBodyAttrs()
104 | }
105 |
106 | componentDidMount() {
107 | this.changeBodyAttrs()
108 | }
109 |
110 | render() {
111 | const { visible } = this.props
112 | return visible ? this.dialog : null
113 | }
114 | }
115 |
116 | export default Dialog
117 |
--------------------------------------------------------------------------------
/example/src/docs/code/card-code.js:
--------------------------------------------------------------------------------
1 | export const simpleCard = (dark, type) => {
2 | const darkProp = dark ? ' dark' : '';
3 | return `
4 |
5 |
6 | Word of the day
7 |
8 |
9 | be•nev•o•lent
10 |
11 |
12 | adjective
13 |
14 |
15 | well meaning and kindly.
16 |
17 | "a benevolent smile"
18 |
19 |
20 |
21 |
24 |
25 | `
26 | }
27 |
28 | export const simpleCardCopy = (dark, type, data) => {
29 | const darkProp = dark ? ' dark' : '';
30 | return `
31 | ${data || '...'}
32 | `
33 | }
34 |
35 | export const elevationCard = (dark) => {
36 | return `${[0, 1, 2, 3, 4, 5]
37 | .map((i) =>
38 | simpleCardCopy(
39 | dark,
40 | `elevation={${i}} width={100} height={100}`,
41 | String(i)
42 | )
43 | )
44 | .join('\n')}`
45 | }
46 |
47 | export const mediaCard = (dark) => {
48 | const darkProp = dark ? ' dark' : '';
49 | return `
50 |
55 |
56 |
57 | Number 1
58 |
59 |
60 | Radhanagar Beach
61 | Havock Island, Andaman
62 |
63 |
64 |
65 |
68 |
71 |
72 | `
73 | }
74 |
75 | export const complexCard = (dark) => {
76 | const darkProp = dark ? ' dark' : '';
77 | return `
78 | Lorem ipsum}
80 | subtitle={Lorem ipsum dolor sit amet}
81 | action={
82 |
83 |
84 |
85 | }
86 | />
87 |
88 |
89 |
90 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Atque
91 | architecto reprehenderit magnam esse est id ipsum ut delectus.
92 | Consequuntur suscipit hic eum ea adipisci, illum sed iure saepe
93 | aperiam quia!
94 |
95 |
96 |
97 |
100 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | `
112 | }
113 |
--------------------------------------------------------------------------------
/src/components/progress/ProgressLinear.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import styles from './Progress.module.css'
4 | import { getModuleClasses, uid, setCSSVariable } from '../../util'
5 | import {
6 | PROGRESS_LINEAR_PROP_TYPES,
7 | PROGRESS_LINEAR_DEFAULT_PROPS
8 | } from '../../assets/index'
9 |
10 | class ProgressLinear extends React.Component {
11 | static displayName = 'NuProgressLinear'
12 |
13 | static defaultProps = PROGRESS_LINEAR_DEFAULT_PROPS
14 |
15 | static propTypes = PROGRESS_LINEAR_PROP_TYPES
16 |
17 | constructor(props) {
18 | super(props)
19 | this.state = { id: uid() }
20 | }
21 |
22 | get value() {
23 | const { value } = this.props
24 | return value > 100 ? 100 : value < 0 ? 0 : value
25 | }
26 |
27 | getClasses(classType) {
28 | const { dark, striped, bordered, indeterminate, fillHeight } = this.props
29 | switch (classType) {
30 | case 'progress':
31 | return getModuleClasses(
32 | styles,
33 | `
34 | nu-progress-linear
35 | nu-progress-linear--${dark ? 'dark' : 'light'}
36 | ${bordered ? 'nu-progress-linear--bordered' : ''}
37 | `
38 | )
39 | case 'bg':
40 | return getModuleClasses(
41 | styles,
42 | `
43 | nu-progress-linear--bg
44 | ${striped ? 'nu-progress-linear--striped' : ''}
45 | ${indeterminate ? 'nu-progress-linear--indeterminate' : ''}
46 | `
47 | )
48 | case 'bg-wrapper':
49 | return getModuleClasses(
50 | styles,
51 | `
52 | nu-progress-linear--bg-wrapper
53 | ${fillHeight ? 'nu-progress-linear--bg-filled' : ''}
54 | `
55 | )
56 | }
57 | }
58 |
59 | getHeightStyle(height) {
60 | const { active } = this.props
61 | return {
62 | height: `${active ? height : 0}px`,
63 | borderRadius: `${height * 2}px`
64 | }
65 | }
66 |
67 | componentDidMount() {
68 | const { color, disabled } = this.props
69 | const elem = document.getElementById(this.state.id)
70 | if (!disabled) {
71 | setCSSVariable(elem, '--selector-bg', color)
72 | }
73 | }
74 |
75 | render() {
76 | const { style, height, className, fillHeight, indeterminate } = this.props
77 | return (
78 |
100 | )
101 | }
102 | }
103 |
104 | export default ProgressLinear
105 |
--------------------------------------------------------------------------------
/src/components/list/List.module.css:
--------------------------------------------------------------------------------
1 | .nu-list {
2 | padding: 8px;
3 | min-width: 100%;
4 | }
5 |
6 | .nu-list-item {
7 | outline: none;
8 | min-width: 100%;
9 | min-height: 42px;
10 | overflow: hidden;
11 | padding: 12px 16px;
12 | border-radius: 4px;
13 | text-decoration: none;
14 | --select-color: var(--primary);
15 | transition: box-shadow 0.2s ease-in-out;
16 | --item-box-shadow: var(--box-shadow-inset);
17 | }
18 |
19 | .nu-list-item.nu-list-item--light {
20 | --bg-color: var(--light-bg);
21 | --text-color: var(--g-text-color-light);
22 | --border-color: var(--light-bg-dark-shadow);
23 | --secondary-text-color: var(--g-text-color-secondary-light);
24 | --box-shadow: -3px -3px 4px var(--light-bg-light-shadow),
25 | 2px 2px 3px var(--light-bg-dark-shadow);
26 | --box-shadow-inset: inset -2px -2px 3px var(--light-bg-light-shadow),
27 | inset 2px 2px 3px var(--light-bg-dark-shadow);
28 | }
29 |
30 | .nu-list-item.nu-list-item--dark {
31 | --bg-color: var(--dark-bg);
32 | --text-color: var(--g-text-color-dark);
33 | --border-color: var(--dark-bg-dark-shadow);
34 | --secondary-text-color: var(--g-text-color-secondary-dark);
35 | --box-shadow: 3px 3px 4px var(--dark-bg-dark-shadow),
36 | -2px -2px 3px var(--dark-bg-light-shadow);
37 | --box-shadow-inset: inset 2px 2px 3px var(--dark-bg-dark-shadow),
38 | inset -2px -2px 3px var(--dark-bg-light-shadow);
39 | }
40 |
41 | .nu-list-item.nu-list-item--raised {
42 | --item-box-shadow: var(--box-shadow);
43 | }
44 |
45 | .nu-list-item.nu-list-item--link {
46 | cursor: pointer;
47 | user-select: none;
48 | }
49 |
50 | .nu-list-item.nu-list-item--dense {
51 | padding: 8px 12px;
52 | }
53 |
54 | .nu-list-item.nu-list-item--rounded {
55 | padding: 12px 24px;
56 | border-radius: 64px;
57 | }
58 |
59 | .nu-list-item.nu-list-item--disabled {
60 | pointer-events: none !important;
61 | user-select: none !important;
62 | }
63 |
64 | .nu-list-item.nu-list-item--two-line.nu-list-item--rounded {
65 | padding-left: 30px;
66 | padding-right: 32px;
67 | border-radius: 82px;
68 | }
69 |
70 | .nu-list-item.nu-list-item--dense .nu-list-item-title,
71 | .nu-list-item.nu-list-item--dense .nu-list-item-subtitle {
72 | font-size: 13px;
73 | font-weight: 500;
74 | }
75 |
76 | .nu-list-item.nu-list-item--active,
77 | .nu-list-item.nu-list-item--link:hover {
78 | box-shadow: var(--item-box-shadow);
79 | }
80 |
81 | .nu-list-item.nu-list-item--link.nu-list-item.nu-list-item--inactive:hover {
82 | box-shadow: none !important;
83 | }
84 |
85 | .nu-list-item.nu-list-item--active .nu-list-item-title {
86 | color: var(--select-color);
87 | }
88 |
89 | .nu-list-item-title,
90 | .nu-list-item-subtitle {
91 | flex: 1 1 100%;
92 | overflow: hidden;
93 | white-space: nowrap;
94 | text-overflow: ellipsis;
95 | }
96 |
97 | .nu-list-item-title {
98 | font-size: 16px;
99 | color: var(--text-color);
100 | }
101 |
102 | .nu-list-item-subtitle {
103 | font-size: 14px;
104 | margin-top: 2px;
105 | color: var(--secondary-text-color);
106 | }
107 |
108 | .nu-list-item.nu-list-item--two-line .nu-list-item-subtitle {
109 | margin-top: 8px;
110 | white-space: normal;
111 | display: -webkit-box;
112 | -webkit-line-clamp: 2;
113 | -webkit-box-orient: vertical;
114 | }
115 |
--------------------------------------------------------------------------------