Skip to content

Commit

Permalink
Copy <AppIcon> to stripes-core
Browse files Browse the repository at this point in the history
  • Loading branch information
MaksymDryha authored Feb 1, 2019
1 parent 87f19c2 commit cb5a26b
Show file tree
Hide file tree
Showing 14 changed files with 532 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Stripes Core

Copyright (C) 2016-2018 The Open Library Foundation
Copyright (C) 2016-2019 The Open Library Foundation

This software is distributed under the terms of the Apache License,
Version 2.0. See the file "[LICENSE](LICENSE)" for more information.
Expand Down
78 changes: 78 additions & 0 deletions src/components/AppIcon/AppIcon.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* AppIcon
*/

@import "@folio/stripes-components/lib/variables";

/**
* Icon
*/

.appIcon {
display: inline-flex;
align-items: center;
}

/**
* Label
*/

.label {
margin: 0 0 0 0.35em;
}

[dir="rtl"] .label {
margin: 0 0.35em 0 0;
}

/**
* Icon
*/
.icon {
display: inline-block;
line-height: 1;
position: relative;
opacity: 1;
transition: none;
border-radius: 25%;
background-color: var(--color-icon) !important;

&::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
border-radius: 25%;
}

& img {
width: 100%;
border-radius: 25%;
height: auto;
vertical-align: top;
}
}

/**
* Sizes
*/
.large .icon {
height: 48px;
min-width: 48px;
width: 48px;
}

.medium .icon {
width: 24px;
min-width: 24px;
height: 24px;
}

.small .icon {
width: 14px;
min-width: 14px;
height: 14px;
}
140 changes: 140 additions & 0 deletions src/components/AppIcon/AppIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* App Icon
*
* Used to display an app's icon
* in various places across the application
*/

import React from 'react';
import PropTypes from 'prop-types';
import { result } from 'lodash';
import classNames from 'classnames';
import { withStripes } from '../../StripesContext';
import css from './AppIcon.css';

const AppIcon = ({
iconAriaHidden,
size,
icon,
alt,
src,
style,
children,
className,
tag,
app,
iconKey,
stripes,
}) => {
const getIcon = () => {
let appIconProps;

/**
* Icon from context
*
* We get the icons from the metadata which is passed down via context.
* The default app icon has an iconKey of "app".
*
* If no icon is found we display a placeholder.
*
*/
const appIcon = result(stripes, `metadata.${app}.icons.${iconKey}`);
if (appIcon && appIcon.src) {
appIconProps = {
src: appIcon.src,
alt: appIcon.alt,
};

// Use PNGs (if available) for small app icons on non-retina screens
const isRetina = window.matchMedia(`
(-webkit-min-device-pixel-ratio: 2),
(min-device-pixel-ratio: 2),
(min-resolution: 192dpi)
`).matches;

// Ignoring next block in tests since it can't be tested consistently
// istanbul ignore next
if (!isRetina && size === 'small' && appIcon.low && appIcon.low.src) {
appIconProps.src = appIcon.low.src;
}
}

/* If we have an image passed as an object */
if (typeof icon === 'object') {
appIconProps = {
src: icon.src,
alt: icon.alt,
};
}

// No image props - return nothing and the placeholder will be active
if (!appIconProps) {
return null;
}

return (
<img
src={typeof src !== 'undefined' ? src : appIconProps.src}
alt={typeof alt !== 'undefined' ? alt : appIconProps.alt}
/>
);
};

/**
* Root CSS styles
*/
const rootStyles = classNames(
/* Base app icon styling */
css.appIcon,
/* Icon size */
css[size],
/* Custom ClassName */
className,
);

/**
* Element - changeable by prop
*/
const Element = tag;

/**
* Render
*/
return (
<Element className={rootStyles} style={style}>
<span className={css.icon} aria-hidden={iconAriaHidden}>
{getIcon()}
</span>
{ children && <span className={css.label}>{children}</span> }
</Element>
);
};

AppIcon.propTypes = {
alt: PropTypes.string,
app: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string,
icon: PropTypes.shape({
alt: PropTypes.string,
src: PropTypes.string.isRequired,
}),
iconAriaHidden: PropTypes.bool,
iconKey: PropTypes.string,
size: PropTypes.oneOf(['small', 'medium', 'large']),
src: PropTypes.string,
stripes: PropTypes.shape({
metadata: PropTypes.object,
}),
style: PropTypes.object,
tag: PropTypes.string,
};

AppIcon.defaultProps = {
iconKey: 'app',
size: 'medium',
tag: 'span',
iconAriaHidden: true,
};

export default withStripes(AppIcon);
1 change: 1 addition & 0 deletions src/components/AppIcon/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './AppIcon';
58 changes: 58 additions & 0 deletions src/components/AppIcon/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# AppIcon

Displays an app's icon in various sizes.

## Usage
AppIcon supports different ways of loading icons.

***1. Use context (recommended)***
```js
import { AppIcon } from '@folio/stripes-core/src/components';

// Note: Make sure that the AppIcon has "stripes" in context as it relies on stripes.metadata.
<AppIcon app="users" size="small" />
```
Optional: You can supply an iconKey if you need a specific icon within an app. If the specific icon isn't bundled with the app it will simply render a placeholder.
```js
<AppIcon app="inventory" iconKey="holdings" />
```

***2. Supply an object to the icon prop***
```js
const icon = {
src: '/static/some-icon.png',
alt: 'My icon',
};

<AppIcon icon={icon} />
```

***3. Pass src and alt as props***
```js
<AppIcon
src="/static/some-icon.png"
alt="My Icon"
/>
```

**Add a label to the icon by passing it as a child**
```js
<AppIcon>
Users
</AppIcon>
```

## Props
Name | Type | Description | default
-- | -- | -- | --
alt | string | Adds an 'alt'-attribute on the img-tag. | undefined
app | string | The lowercased name of an app, e.g. "users" or "inventory". It will get the icon from metadata located in the stripes-object which should be available in React Context. Read more [here](https://github.com/folio-org/stripes-core/blob/master/doc/app-metadata.md#icons). | undefined
children | node | Add content next to the icon - e.g. a label | undefined
className | string | For adding custom class to component | undefined
icon | object | Icon in form of an object. E.g. { src, alt } | undefined
iconAriaHidden | bool | Applies aria-hidden to the icon element. Since `<AppIcon>`'s mostly are rendered in proximity of a label or inside an element with a label (e.g. a button), we set aria-hidden to true per default to avoid screen readers reading the alt attribute of the icon img | true
iconKey | string | A specific icon-key for apps with multiple icons. Defaults to "app" which corresponds to the required default app-icon of an app. | app
size | string | Determines the size of the icon. (small, medium, large) | medium
src | string | Manually set the 'src'-attribute on the img-tag | undefined
style | object | For adding custom style to component | undefined
tag | string | Changes the rendered root HTML-element | span
3 changes: 2 additions & 1 deletion src/components/MainNav/AppList/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import AppIcon from '@folio/stripes-components/lib/AppIcon';
import NavListItem from '@folio/stripes-components/lib/NavListItem';
import NavListItemStyles from '@folio/stripes-components/lib/NavListItem/NavListItem.css';

import AppIcon from '../../AppIcon';

import css from './AppList.css';

const List = React.forwardRef(({ apps, onItemClick }, ref) => {
Expand Down
4 changes: 3 additions & 1 deletion src/components/MainNav/NavButton/NavButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Link from 'react-router-dom/Link';
import AppIcon from '@folio/stripes-components/lib/AppIcon';
import Badge from '@folio/stripes-components/lib/Badge';

import AppIcon from '../../AppIcon';

import css from './NavButton.css';

const propTypes = {
Expand Down
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as About } from './About';
export { default as AppIcon } from './AppIcon';
export { default as AuthErrorsContainer } from './AuthErrorsContainer';
export { default as CreateResetPassword } from './CreateResetPassword';
export { default as HandlerManager } from './HandlerManager';
Expand Down
43 changes: 43 additions & 0 deletions test/bigtest/helpers/Harness.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { IntlProvider } from 'react-intl';
import { reducer as formReducer } from 'redux-form';
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';

import translations from '@folio/stripes-components/translations/stripes-components/en';

const reducers = {
form: formReducer,
};

const reducer = combineReducers(reducers);

const store = createStore(reducer);

// mimics the StripesTranslationPlugin in @folio/stripes-core
function prefixKeys(obj) {
const res = {};
for (const key of Object.keys(obj)) {
res[`stripes-components.${key}`] = obj[key];
}
return res;
}

class Harness extends React.Component {
render() {
return (
<Provider store={store}>
<IntlProvider locale="en" key="en" timeZone="UTC" messages={prefixKeys(translations)}>
{this.props.children}
</IntlProvider>
</Provider>
);
}
}

Harness.propTypes = {
children: PropTypes.node,
};

export default Harness;
Loading

0 comments on commit cb5a26b

Please sign in to comment.