Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

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

Auto-Updating Preview

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

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                #App
//- Not only skeleton Screens UI 
//- More importantly, the demo is for reusable react component.
              
            
!

CSS

              
                // -----Global Variables and Mixin--------
$light-gray: #e6e6e6;
$medium-gray: #cacaca;
$dark-gray: #8a8a8a;
$white: #fefefe;
$black: #0a0a0a;
$primary-color: #507590;
$secondary-color: #FFC107;

$iphone6: 414px;
$shadow1: 0 1px 3px rgba($black, .12), 0 1px 2px rgba($black, .24);
$radius: 3px;
$sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
$serif: Palatino, Palatino Linotype, Palatino LT STD, Book Antiqua, Georgia, serif;
html, body {color: $primary-color; font-family: $serif;}
	
@mixin image-ratio($x, $y, $selector: img) {
	position: relative;
	padding: 0 0 percentage($y / $x);
	height: 0;
	#{$selector} {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
  }
}

// --------------END---------------


@mixin skeleton-screens {
	$from: $medium-gray;
	$to: scale-color($from, $lightness: -10%);
	height: 100%;
	width: 100%;
	background-image: linear-gradient(-90deg, $white 0%, $light-gray 50%, $white 100%);
	background-size: 400% 400%;
	animation: pulse 1.2s ease-in-out infinite;
	@keyframes pulse {
		0% {
			background-position: 0% 0%;
		}
		100% {
			background-position: -135% 0%;
		}
	}
}
@mixin stroll-card($height) {
	perspective: 300px;
	perspective-origin: 50% 50%;
	overflow-x: hidden;
	overflow-y: scroll;
	height: $height;
	li {
		transition: all 600ms ease;
		transform-origin: 100% 50%;
	}
	.past {
		transform: translate3d(0, -100px, -100px) rotateX(-90deg);
	}
	.future {
		transform: translate3d(0, 100px, -100px) rotateX(90deg);
	}
}
.skelecon-screen {
	margin: 2% auto;
	width: $iphone6;
	padding: 1em .5em;
	border-radius: $radius;
	background-color: $light-gray;
	header {
		padding: 1em;
		border-radius: $radius;
		box-shadow: $shadow1;
		background-color: $white;
	}
	[type='text'] {
		margin: 0;
	}
	.button {
		margin: 1em 0 0;
	}
}
// [Start] Skeleton-screens
.user {
	// @include stroll-card(632px);
	list-style-type: none;
	margin: 3px 0 0;
	height: 475px;
	overflow-x: hidden;
	overflow-y: scroll;
}
.waiting-on-data {
	.img-wrapper {
		@include skeleton-screens;
		border-radius: 50%;
		img {opacity: 0;}
	}
	.name,
	.gender {
		@include skeleton-screens;
		width: 95%;
		height: 20px;
		margin-top: 15px;
		border-radius: $radius;
	}
}
// [End] Skeleton-screens

.ui-item {
	display: flex;
	width: 100%;
	margin-top: 1em;
	background-color: $white;
	border-radius: $radius;
	box-shadow: $shadow1;
	padding: 1em;
	.profile {
		flex: 0 0 30%;
		.img-wrapper {
			@include image-ratio(128, 128);
		}
		img {
			border-radius: 50%;
			padding: .2em;
			background-color: $secondary-color;
		}
	}
	.user-info {
		flex: 0 0 70%;
		margin-left: 1em;
		.name {
			position: relative;
			color: $secondary-color;
			font-size: 1.5em;
			text-transform: capitalize;
			font-weight: 600;
			font-family: $serif;
		}
		.gender,
		.email,
		.phone {
			font-size: .9em;
			font-family: $sans-serif;
			color: $dark-gray;
		}
		.gender {
			text-transform: uppercase;
		}
	}
}
              
            
!

JS

              
                const Redux = window.Redux;
const ReactRedux = window.ReactRedux;
const ReduxThunk = window.ReduxThunk;

const { Provider, connect } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;

// API
const getPersonInfo = () =>
	axios.get('https://randomuser.me/api', {
		params: {
			results: 50,
			inc: 'name,gender,phone,email,picture,login'
		}
	})
	.then(response => {
		if(response.status !== 200) {
			throw new Error('randomuser server is down!');
		}
		const userList = response.data.results.map(data => ({
			profile: data.picture.large,
			fName: data.name.first,
			lName: data.name.last,
			email: data.email,
			userGender: data.gender,
			phone: data.phone,
			md5: data.login.md5
		}));
		return userList;
	})
	.catch(error=>{
		throw new Error('catch failed: ', error);
	});
// Action
const actions = {
	fetchUserStart: () => ({type: 'FETCH_USER_START'}),
	receiveDataSuccess: userList => ({
		type: 'RECEIVE_DATA',
		payload: userList
	}),

	receiveDataFail: error => ({
		type: 'FETCH_ERROR',
		payload: error
	}),

	setSearchText: searchText => ({
		type: 'SET_SEARCH_TEXT',
		payload: searchText
	}),
	getRandomAction: () =>
		dispatch => {
			dispatch(actions.fetchUserStart());
			getPersonInfo()
			.then( respond => {
				dispatch(actions.receiveDataSuccess(respond));
			})
			.catch(error => {
				dispatch(actions.receiveDataFail(error));
			});
		}
};
// Reducer
const initialState = {
	isLoading: false,
	errorMessage: null,
	user: [],
	searchText: ''
};

const reducer = (state = initialState, action) => {
	switch(action.type) {
		case "FETCH_USER_START":
			return Object.assign({}, state, {
        isLoading: true,
				errorMessage: null,
				user: []
      });
		case "RECEIVE_DATA":
			return Object.assign({}, state, {
				isLoading: false,
				errorMessage: null,
				user: action.payload
			});
		case "SET_SEARCH_TEXT":
			return Object.assign({}, state, {
				searchText: action.payload
			});
		case 'FETCH_ERROR':
			return Object,assign({}, state, {
				isLoading: false,
				user: [],
				errorMessage: action.payload
			});
		default:
			return state;
	}
}

// React Main Component
class App extends React.Component {
  constructor(props) {
    super(props);
  }
  componentDidMount() {
    this.props.fetchData();
  }
  handleChange(e) {
    this.props.handleSearchText(e.target.value);
  }
  handleReFetch() {
    this.props.fetchData();
  }

  render() {
    const {isLoading, user=[], errorMessage, searchText} = this.props;
    const matchesFilter = new RegExp(searchText, 'i');
    const filtereduser = user.filter( u => !searchText || matchesFilter.test(u.fName) || matchesFilter.test(u.lName));
    // Set dynamic class name
    const ConditionalClass = classNames({
      'user waiting-on-data': isLoading,
      'user': !isLoading
    });
    // Set rendering result
    const renderUserList = () => {
      if(!isLoading && errorMessage !== null) {
        return(<h1>{errorMessage}</h1>);
      } else if (isLoading) {
        // Generate a sequence of ID
        const asyncUI = new Array(10).fill(1).map(i => Math.random().toFixed(5));
        return asyncUI.map(value => <UserCard key={value}/>);
      }
      return filtereduser.map(value => <UserCard key={value.md5} {...value}/>);
    };
    return (
      <div className='skelecon-screen'>
        <header>
          <Input inputType='text' inputValue={searchText} onChangeValue={this.handleChange.bind(this)} inputPlaceHolder='Search by name'/>
          <button className="button expanded" onClick={this.handleReFetch.bind(this)}>CHANGE USER GROUP</button>
        </header>
        <ul className={ConditionalClass}>
          {renderUserList()}
        </ul>
      </div>
    );
  }
};

// Using React-Redux to connect the main component
const mapStateToProps = state => state;
const mapDispatchToProps = dispatch => ({
  fetchData: () => dispatch(actions.getRandomAction()),
  handleSearchText: text => dispatch(actions.setSearchText(text))
});
const WrappedApp = connect(mapStateToProps, mapDispatchToProps)(App);

// Type checking for main component
App.propsType = {
	fetchData: React.PropTypes.func.isRequired,
	handleSearchText: React.PropTypes.func.isRequired,
	user: React.PropTypes.array.isRequired
}

// Reusable component 01, User Card , Skeleton Screen has happend here.
const UserCard = props => {
  const {profile, fName, lName, userGender, phone, md5, email} = props;
  return (
    <li className="ui-item" id={md5}>
      <div className="profile">
        <div className="img-wrapper">
          <Image imageURL={profile} imageAlt={fName}/>
        </div>
      </div>
      <UserInfo firstName={fName} lastName={lName} gender={userGender} email={email} phone={phone}/>
    </li>
  );
};

// Reusable component 02
const Input = props => {
  const {onChangeValue, inputType, inputValue, inputPlaceHolder} = props;
  const onChangeValueFunc = target => onChangeValue(target);
  return (<input type={inputType} value={inputValue} onChange={onChangeValueFunc} placeholder={inputPlaceHolder}/>);
};
Input.propsType = {
  inputType: React.PropTypes.oneOf(['text', 'checkbox', 'radio', 'number', 'email', 'password', 'submit']).isRequired,
  onChangeValue: React.PropTypes.func.isRequired,
  inputPlaceHolder: React.PropTypes.string,
  inputValue: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number
  ]).isRequired
};
// Reusable component 03
const Image = props => {
  const {imageURL, imageAlt} = props;
  if (typeof imageURL === 'string') {
    return <img src={imageURL} alt={imageAlt}/>;
  }
  return <img src='' alt="placeholder"/>;
};
Image.propTypes = {
  imageURL: React.PropTypes.string,
  imageAlt: React.PropTypes.string
};
// Reusable component 04
const UserInfo = props => {
  const {firstName, lastName, gender, email, phone} = props;
  return (
    <div className="user-info">
      <div className="name">{firstName} {lastName}</div>
      <div className="gender">{gender}</div>
      <div className="email">{email}</div>
      <div className="phone">{phone}</div>
    </div>
  );
};
UserInfo.propTypes = {
  firstName: React.PropTypes.string,
  lastName: React.PropTypes.string,
  gender: React.PropTypes.string,
  email: React.PropTypes.string,
  phone: React.PropTypes.string
};


// Redux Store
const Store = Redux.createStore(reducer, applyMiddleware(thunk));

// Mount the component to the DOM
const element = document.getElementById('App');
ReactDOM.render(
  <Provider store={Store}>
    <WrappedApp />
  </Provider>,
  element, 0
);


              
            
!
999px

Console