elements
9 | *
10 | * Required props:
11 | * - label: string. Sets the text for the input's label
12 | * - children: node. Series of );
85 | }
86 |
87 | return (
88 |
89 |
90 |
91 | {errorMessage}
92 |
93 |
101 |
102 | );
103 | }
104 | }
105 |
106 | Dropdown.propTypes = {
107 | id: PropTypes.string,
108 | label: PropTypes.string.isRequired,
109 | children: PropTypes.node.isRequired,
110 | required: PropTypes.bool,
111 | hasError: PropTypes.bool,
112 | errorMessage: PropTypes.string,
113 | value: PropTypes.string
114 | };
115 |
116 | Dropdown.defaultProps = {
117 | required: false,
118 | hasError: false
119 | };
120 |
--------------------------------------------------------------------------------
/styleguide/containers/ButtonsContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Label from '../../lib/components/Label';
3 | import { DocsPage, DocsSection, DocsExample, DocsPreview } from './Docs.jsx';
4 | import Highlight from 'react-highlight';
5 | import { Table, TableRow, TableHeaderCell, TableCell } from '../../lib/components/Tables';
6 | import PrimaryButton from "../../lib/components/PrimaryButton";
7 |
8 | /**
9 | * Buttons content
10 | * @returns {node} node
11 | */
12 | export default function ButtonsContainer () {
13 | return (
14 |
15 |
16 | {/* Importing */}
17 |
18 |
19 | {`import PrimaryButton from "../lib/components/PrimaryButton";`}
20 |
21 |
22 |
23 | {/* Props */}
24 |
25 |
26 |
27 | text
28 | string
29 |
30 | ...
31 |
32 |
33 | disabled
34 | bool
35 | false
36 | If true, makes button disabled
37 |
38 |
39 | onClick
40 | func
41 |
42 | ...
43 |
44 |
45 | size
46 | oneOf ['', 'PrimaryButton.SIZE_BIG']
47 | ''
48 | ...
49 |
50 |
51 | status
52 | oneOf [''', PrimaryButton.STATUS_ACTIVE, PrimaryButton.STATUS_HOVER']
53 | ''
54 | ...
55 |
56 |
57 |
58 |
59 | {/* Usage */}
60 |
61 |
62 |
63 | Primary Buttons
64 |
69 | Disabled Button
70 |
73 | Big Button
74 |
77 |
78 |
79 | {`Primary Buttons
80 |
85 | Disabled Button
86 |
89 | Big Button
90 | `}
93 |
94 |
95 |
96 |
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/lib/components/OfficialSiteBanner.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | /**
4 | * Renders the standard official site banner indicating that this site is an
5 | * official government site.
6 | *
7 | * This component is usually rendered in the common {@link Header}. You should
8 | * only need to add this manually if you are implementing your own header.
9 | * In that case, this should be the element that is at the very top of the
10 | * window.
11 | *
12 | * For more information, please consult the
13 | * [U.S. Web Design Standards](https://standards.usa.gov).
14 | */
15 | export default class OfficialSiteBanner extends React.Component
16 | {
17 | /**
18 | * Constructor.
19 | *
20 | * @param {Object} props The props that will be applied to this component.
21 | */
22 | constructor(props)
23 | {
24 | super(props);
25 | this.state = {
26 | contentVisible: false
27 | };
28 | }
29 |
30 | /**
31 | * Toggles the visiblity of the content section.
32 | */
33 | toggleDetails()
34 | {
35 | this.setState({ contentVisible: !this.state.contentVisible });
36 | }
37 |
38 | /**
39 | * Renders the content of the accordion that expands when the user clicks
40 | * on the "Here's how you know" link.
41 | *
42 | * @returns {Node|String} The rendered DOM node or an empty string.
43 | */
44 | renderContent()
45 | {
46 | if (!this.state.contentVisible) {
47 | return "";
48 | }
49 | return (
50 |
51 |
52 |

53 |
54 |
55 | The .gov means it's official.
56 |
57 | Federal government websites always use a .gov or .mil domain.
58 | Before sharing sensitive information online, make sure you’re
59 | on a .gov or .mil site by inspecting your browser’s address
60 | (or “location”) bar.
61 |
62 |
63 |
64 |
65 |

66 |
67 |
68 | This site is also protected by an SSL (Secure Sockets Layer)
69 | certificate that’s been signed by the U.S. government.
70 | The https:// means all transmitted data is
71 | encrypted — in other words, any information or browsing
72 | history that you provide is transmitted securely.
73 |
74 |
75 |
76 |
77 | );
78 | }
79 |
80 | /**
81 | * Renders the component.
82 | *
83 | * @returns {Node|null} The rendered DOM node.
84 | */
85 | render()
86 | {
87 | return (
88 |
89 |
90 |
101 | {this.renderContent()}
102 |
103 |
104 | );
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uswds-react",
3 | "version": "0.0.1",
4 | "description": "U.S. Web Design Standard components written in React.",
5 | "scripts": {
6 | "start": "cross-env NODE_PATH=./styleguide concurrently --kill-others \"nodemon --watch . --ext js,jsx server\" \"node support/webpack.server.js\" \"gulp css:watch\"",
7 | "lint": "eslint . --ext .jsx --ext .js",
8 | "test": "jest",
9 | "test:ci": "cross-env NODE_ENV=test cross-env NODE_PATH=./styleguide mocha --compilers js:babel-register -r app/spec/support/setup.mocha.js --recursive 'app/spec/**/*.test.js'",
10 | "webpack": "webpack --config support/webpack.config.js",
11 | "build": "rimraf dist/ && npm run webpack && gulp build",
12 | "postinstall": "npm run build"
13 | },
14 | "engines": {
15 | "node": "6.1.0",
16 | "npm": "3.8.6"
17 | },
18 | "license": "MIT",
19 | "dependencies": {
20 | "assets-webpack-plugin": "^3.2.0",
21 | "babel-core": "^6.3.15",
22 | "babel-loader": "^6.2.0",
23 | "babel-plugin-transform-object-rest-spread": "^6.22.0",
24 | "babel-polyfill": "^6.3.14",
25 | "babel-preset-es2015": "^6.22.0",
26 | "babel-preset-react": "^6.3.13",
27 | "babel-preset-stage-0": "^6.22.0",
28 | "babel-register": "^6.5.1",
29 | "bluebird": "^2.10.0",
30 | "bourbon": "^4.2.6",
31 | "bourbon-neat": "github:thoughtbot/neat#neat-1.8.0-node-sass",
32 | "compression": "^1.6.0",
33 | "concurrently": "^2.0.0",
34 | "cross-env": "^1.0.7",
35 | "dotenv": "^4.0.0",
36 | "ejs": "^2.5.0",
37 | "express": "^4.15.0",
38 | "gulp": "^3.9.0",
39 | "gulp-autoprefixer": "^3.1.0",
40 | "gulp-filter": "^3.0.1",
41 | "gulp-load-plugins": "^1.2.0",
42 | "gulp-rename": "^1.2.2",
43 | "gulp-rev": "^6.0.1",
44 | "gulp-rev-all": "^0.8.24",
45 | "gulp-rev-replace": "^0.4.3",
46 | "gulp-sass": "^2.1.1",
47 | "gulp-sourcemaps": "^1.6.0",
48 | "history": "^1.17.0",
49 | "humps": "^0.6.0",
50 | "immutable": "^3.8.1",
51 | "isomorphic-fetch": "^2.1.1",
52 | "lodash": "^3.10.1",
53 | "node-sass": "^3.4.2",
54 | "normalize.css": "^3.0.3",
55 | "normalizr": "^1.0.0",
56 | "react": "^15.4.2",
57 | "react-dom": "^15.4.2",
58 | "react-helmet": "^4.0.0",
59 | "react-redux": "^5.0.0",
60 | "react-highlight": "^0.9.0",
61 | "react-router": "^3.0.0",
62 | "redux": "^3.0.0",
63 | "redux-logger": "^2.8.0",
64 | "redux-thunk": "^2.2.0",
65 | "rewire": "^2.3.4",
66 | "rimraf": "^2.6.0",
67 | "run-sequence": "^1.2.2",
68 | "superagent": "^1.4.0",
69 | "webpack": "^2.2.0"
70 | },
71 | "devDependencies": {
72 | "babel-eslint": "^7.1.1",
73 | "babel-jest": "^19.0.0",
74 | "babel-plugin-react-transform": "^2.0.0",
75 | "babel-plugin-rewire": "^1.0.0-beta-5",
76 | "babel-polyfill": "^6.23.0",
77 | "babel-preset-react-hmre": "^1.0.1",
78 | "babel-preset-stage-0": "^6.22.0",
79 | "chai": "^3.3.0",
80 | "chai-as-promised": "^5.1.0",
81 | "css-loader": "^0.26.2",
82 | "enzyme": "^2.7.1",
83 | "eslint": "^3.16.1",
84 | "eslint-loader": "^1.6.3",
85 | "eslint-plugin-react": "^6.10.0",
86 | "jest": "^19.0.2",
87 | "jsdom": "^7.0.1",
88 | "mocha": "^2.4.5",
89 | "nock": "^2.17.0",
90 | "nodemon": "^1.6.0",
91 | "react-addons-test-utils": "^15.4.2",
92 | "react-hot-loader": "^1.3.1",
93 | "react-transform-catch-errors": "^1.0.0",
94 | "react-transform-hmr": "^1.0.2",
95 | "rewire-webpack": "^1.0.0",
96 | "sinon": "^1.17.1",
97 | "sinon-chai": "^2.8.0",
98 | "style-loader": "^0.13.2",
99 | "webpack-dev-server": "^2.4.0"
100 | },
101 | "jest": {
102 | "verbose": true,
103 | "collectCoverage": true,
104 | "collectCoverageFrom": [
105 | "**/*.{js,jsx}",
106 | "!**/node_modules/**",
107 | "!gulpfile.js",
108 | "!**/coverage/**",
109 | "!**/dist/**",
110 | "!**/server/**",
111 | "!**/styleguide/**",
112 | "!**/support/**"
113 | ]
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/__tests__/lib/components/Dropdown-test.js:
--------------------------------------------------------------------------------
1 | jest.unmock('../../../lib/components/Dropdown.jsx');
2 |
3 | import React from 'react';
4 | import { mount } from 'enzyme';
5 |
6 | import Dropdown from '../../../lib/components/Dropdown.jsx';
7 |
8 | describe('Dropdown', () => {
9 |
10 | let wrapper = null;
11 | const labelText = 'label goes here';
12 |
13 |
14 | beforeEach(function() {
15 | wrapper = mount(
16 |
17 |
18 |
19 |
20 | );
21 | });
22 |
23 | it('is defined', () => {
24 | expect(wrapper).toBeDefined();
25 | });
26 |
27 | it('renders the label text correctly', () => {
28 | const labelElement = wrapper.find('label');
29 | expect(labelElement.text()).toBe(labelText);
30 | });
31 |
32 | it('has an id attribute', () => {
33 | expect(typeof wrapper.find('select').props().id).toBe('string');
34 | });
35 |
36 | it('has a matching id and htmlFor attributes', () => {
37 | const id = wrapper.find('select').props().id;
38 | const htmlFor = wrapper.find('label').props().htmlFor;
39 | expect(htmlFor).toBe(id);
40 | });
41 |
42 | it('adds a placeholder if there\'s no value', () => {
43 | expect(wrapper.find('option').first().text()).toBe('Select ...');
44 | });
45 |
46 | it('accepts a value property', () => {
47 | const testValue = 'testing';
48 | wrapper = mount(
49 |
50 |
51 |
52 | );
53 | const value = wrapper.find('select').props().value;
54 | expect(value).toBe(testValue);
55 | });
56 |
57 | it('accepts the required proprty', () => {
58 | wrapper = mount(
59 |
60 |
61 |
62 | );
63 | expect(wrapper.find('label').hasClass('usa-input-required')).toBe(true);
64 | });
65 |
66 | it('displays the error message', () => {
67 | const message = 'error message';
68 | wrapper = mount(
69 |
70 |
71 |
72 | );
73 | expect(wrapper.find('.usa-input-error-message').text()).toBe(message);
74 | });
75 |
76 | it('has the error class when there\'s an error', () => {
77 | const message = 'error message';
78 | wrapper = mount(
79 |
80 |
81 |
82 | );
83 | expect(wrapper.hasClass('usa-input-error')).toBe(true);
84 | });
85 |
86 | it('handles onChange event', () => {
87 | const testValue = 'value3';
88 | wrapper = mount(
89 |
90 |
91 |
92 | );
93 | wrapper.find('select').simulate('change', {target: { value : testValue}});
94 | expect(wrapper.find('select').props().value).toBe(testValue);
95 | });
96 |
97 | it('updates when props change to include an error message', () => {
98 | const message = 'error message';
99 | const Parent = React.createClass({
100 | getInitialState() {
101 | return {errorMessage: null};
102 | },
103 | render() {
104 | return (
105 |
106 |
107 |
108 | );
109 | }
110 | });
111 | const wrapper = mount();
112 | wrapper.setState({errorMessage: message});
113 | expect(wrapper.find('.usa-input-error-message').text()).toBe(message);
114 | });
115 | });
116 |
--------------------------------------------------------------------------------
/lib/styles/core/_utilities.scss:
--------------------------------------------------------------------------------
1 | // Mobile-first media query helper
2 | @mixin media($bp) {
3 | @media screen and (min-width: #{$bp}) {
4 | @content;
5 | }
6 | }
7 |
8 | // Screen reader only helper
9 | @mixin sr-only() {
10 | position: absolute;
11 | left: -999em;
12 | }
13 |
14 | .usa-sr-only {
15 | @include sr-only();
16 | }
17 |
18 | // Aria hidden helper
19 | @mixin accessibly-hidden() {
20 | &[aria-hidden=true] {
21 | display: none;
22 | }
23 | }
24 |
25 | // Unstyled list helper
26 | @mixin unstyled-list() {
27 | @include margin(0 null);
28 | list-style-type: none;
29 | padding-left: 0;
30 |
31 | > li {
32 | margin-bottom: 0;
33 | }
34 | }
35 |
36 | // Font smoothing mixin
37 | // Only use for light text on dark background
38 | // TODO: Remove after adding PostCSS
39 | @mixin font-smoothing {
40 | -moz-osx-font-smoothing: grayscale;
41 | -webkit-font-smoothing: antialiased;
42 | }
43 |
44 | // Content size helpers
45 | @mixin allow-layout-classes {
46 | @include margin(null auto);
47 |
48 | &.width-one-half {
49 | @include media($medium-screen) {
50 | width: 50%;
51 | }
52 | }
53 |
54 | &.width-one-third {
55 | @include media($medium-screen) {
56 | width: 33%;
57 | }
58 | }
59 |
60 | &.width-two-thirds {
61 | @include media($medium-screen) {
62 | width: 67%;
63 | }
64 | }
65 |
66 | &.width-one-fourth {
67 | @include media($medium-screen) {
68 | width: 25%;
69 | }
70 | }
71 |
72 | &.width-three-fourths {
73 | @include media($medium-screen) {
74 | width: 75%;
75 | }
76 | }
77 |
78 | &.align-left {
79 | @include media($medium-screen) {
80 | float: left;
81 | margin-right: 2em;
82 | margin-top: 0.5em;
83 | }
84 | }
85 |
86 | &.align-right {
87 | @include media($medium-screen) {
88 | float: right;
89 | margin-left: 2em;
90 | margin-top: 0.5em;
91 | }
92 | }
93 | }
94 |
95 | @mixin display-icon($icon, $direction, $size, $margin, $hover) {
96 | &::#{$direction} {
97 | background-image: url(#{$image-path}/#{$icon}.png);
98 | background-image: url(#{$image-path}/#{$icon}.svg);
99 | background-size: 100%;
100 | content: '';
101 | display: inline-block;
102 | height: $size;
103 | width: $size;
104 |
105 | /* stylelint-disable block-closing-brace-newline-after, at-rule-empty-line-before */
106 | @if $direction == 'after' {
107 | margin-left: $margin;
108 | } @else {
109 | margin-right: $margin;
110 | }
111 | /* stylelint-enable */
112 | }
113 |
114 | @if $hover == 'hover' {
115 | &:hover::#{$direction} {
116 | background-image: url(#{$image-path}/#{$icon}-hover.png);
117 | background-image: url(#{$image-path}/#{$icon}-hover.svg);
118 | }
119 | }
120 | }
121 |
122 | @mixin usa-sidenav-list {
123 | @include unstyled-list();
124 |
125 | > li {
126 | background-color: transparent;
127 | border-top: 1px solid $color-gray;
128 | font-size: $h4-font-size;
129 |
130 | &:first-child {
131 | border-top: none;
132 | }
133 | }
134 |
135 | a {
136 | border: none;
137 | color: $color-base;
138 | display: block;
139 | font-family: $font-sans;
140 | line-height: 1.3;
141 | padding: 0.85rem 1rem 0.85rem 1.8rem;
142 | text-decoration: none;
143 |
144 | &:hover {
145 | background-color: $color-gray-lightest;
146 | color: $color-primary;
147 | text-decoration: none;
148 | }
149 |
150 | &:focus {
151 | position: relative;
152 | z-index: 1;
153 | }
154 |
155 | &.usa-current { /* stylelint-disable-line selector-no-qualifying-type */
156 | border-left: 4px solid $color-primary;
157 | color: $color-primary;
158 | font-weight: $font-bold;
159 | padding-left: 1.4rem;
160 | }
161 | }
162 | }
163 |
164 | @mixin usa-sidenav-sublist {
165 | @include unstyled-list();
166 | margin: 0;
167 | width: 100%;
168 |
169 | li {
170 | border: none;
171 | font-size: $h5-font-size;
172 | }
173 |
174 | a {
175 | padding-left: 2.8rem;
176 | line-height: $heading-line-height;
177 |
178 | &:hover,
179 | &.usa-current { /* stylelint-disable-line selector-no-qualifying-type */
180 | border: none;
181 | padding-left: 2.8rem;
182 | }
183 | }
184 |
185 | .usa-sidenav-sub_list {
186 | a {
187 | padding-left: 3.8rem;
188 |
189 | &:hover {
190 | padding-left: 3.8rem;
191 | }
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/lib/styles/elements/_buttons.scss:
--------------------------------------------------------------------------------
1 | // Buttons variables
2 |
3 | $button-stroke: inset 0 0 0 2px;
4 |
5 | // Buttons
6 |
7 | /* stylelint-disable selector-no-qualifying-type */
8 | .usa-button,
9 | .usa-button-primary,
10 | .usa-button:visited,
11 | .usa-button-primary:visited,
12 | button,
13 | [type=button],
14 | [type=submit],
15 | [type=reset],
16 | [type=image] {
17 | @include font-smoothing;
18 | @include margin(0.5em 0.5em 0.5em null);
19 | appearance: none;
20 | background-color: $color-primary;
21 | border: 0;
22 | border-radius: $border-radius;
23 | color: $color-white;
24 | cursor: pointer;
25 | display: inline-block;
26 | font-family: $font-sans;
27 | font-size: $base-font-size;
28 | font-weight: $font-bold;
29 | line-height: 1;
30 | outline: none;
31 | padding: 1rem 2rem;
32 | text-align: center;
33 | text-decoration: none;
34 | width: 100%;
35 |
36 | @include media($small-screen) {
37 | width: auto;
38 | }
39 |
40 | &:hover,
41 | &.usa-button-hover {
42 | background-color: $color-primary-darker;
43 | border-bottom: 0;
44 | color: $color-white;
45 | text-decoration: none;
46 | }
47 |
48 | &:focus,
49 | &.usa-button-focus {
50 | box-shadow: $focus-shadow;
51 | }
52 |
53 | &:active,
54 | &.usa-button-active {
55 | background-color: $color-primary-darkest;
56 | }
57 |
58 | &.usa-button-primary-alt {
59 | background-color: $color-primary-alt;
60 | color: $color-base;
61 |
62 | &:hover,
63 | &.usa-button-hover {
64 | background-color: $color-primary-alt-dark;
65 | }
66 |
67 | &:active,
68 | &.usa-button-active {
69 | background-color: $color-primary-alt-darkest;
70 | color: $color-white;
71 | }
72 | }
73 |
74 | &.usa-button-secondary {
75 | background-color: $color-secondary;
76 |
77 | &:hover,
78 | &.usa-button-hover {
79 | background-color: $color-secondary-dark;
80 | }
81 |
82 | &:active,
83 | &.usa-button-active {
84 | background-color: $color-secondary-darkest;
85 | }
86 | }
87 |
88 | &.usa-button-gray {
89 | background-color: $color-gray;
90 |
91 | &:hover,
92 | &.usa-button-hover {
93 | background-color: $color-gray-dark;
94 | }
95 |
96 | &:active,
97 | &.usa-button-active {
98 | background-color: $color-base;
99 | }
100 | }
101 |
102 | &.usa-button-outline {
103 | background-color: $color-white;
104 | box-shadow: $button-stroke $color-primary;
105 | color: $color-primary;
106 |
107 | &:hover,
108 | &.usa-button-hover {
109 | box-shadow: $button-stroke $color-primary-darker;
110 | color: $color-primary-darker;
111 | }
112 |
113 | &:active,
114 | &.usa-button-active {
115 | box-shadow: $button-stroke $color-primary-darkest;
116 | color: $color-primary-darkest;
117 | }
118 |
119 | &:focus,
120 | &.usa-button-focus {
121 | box-shadow: $button-stroke $color-primary-darkest, $focus-shadow;
122 | }
123 | }
124 |
125 | &.usa-button-outline-inverse {
126 | background: transparent;
127 | box-shadow: $button-stroke $color-white;
128 | color: $color-white;
129 |
130 | &:hover,
131 | &.usa-button-hover {
132 | box-shadow: $button-stroke $color-gray-lighter;
133 | color: $color-gray-lighter;
134 | }
135 |
136 | &:active,
137 | &.usa-button-active {
138 | box-shadow: $button-stroke $color-gray-light;
139 | color: $color-gray-lighter;
140 | }
141 |
142 | &:focus,
143 | &.usa-button-focus {
144 | box-shadow: $button-stroke $color-gray-light, $focus-shadow;
145 | }
146 | }
147 |
148 | &.usa-button-big {
149 | font-size: 1.9rem;
150 | padding: 1.5rem 3rem;
151 | }
152 | }
153 | /* stylelint-disable */
154 |
155 | .usa-button:disabled,
156 | .usa-button-disabled {
157 | background-color: $color-gray-lighter;
158 | color: $color-gray-dark;
159 | pointer-events: none;
160 |
161 | &:hover,
162 | &.usa-button-hover,
163 | &:active,
164 | &.usa-button-active,
165 | &:focus {
166 | background-color: $color-gray-lighter;
167 | border: 0;
168 | box-shadow: none;
169 | color: $color-gray-dark;
170 | }
171 | }
172 |
173 | @mixin button-unstyled {
174 | background-color: transparent;
175 | border: 0;
176 | border-radius: 0;
177 | font-weight: $font-normal;
178 | margin: 0;
179 | outline: 0;
180 | padding: 0;
181 | text-align: left;
182 | -webkit-font-smoothing: auto;
183 |
184 | &:hover {
185 | background-color: transparent;
186 | }
187 | }
188 |
189 | .usa-button-unstyled {
190 | @include button-unstyled;
191 | }
192 |
--------------------------------------------------------------------------------
/lib/styles/components/OfficialSiteBanner.scss:
--------------------------------------------------------------------------------
1 | .usa-banner {
2 | background-color: $color-gray-lightest;
3 | padding-bottom: 0.7rem;
4 |
5 | @include media($small-screen) {
6 | font-size: $h6-font-size;
7 | padding-bottom: 0;
8 | }
9 | }
10 |
11 | .usa-banner-content {
12 | @include padding(null $site-margins-mobile 3px $site-margins-mobile);
13 | background-color: transparent;
14 | font-size: $h5-font-size;
15 | padding-top: 2rem;
16 | width: 100%;
17 |
18 | @include media($medium-screen) {
19 | padding-bottom: 2.3rem;
20 | padding-top: 4rem;
21 | }
22 |
23 | @include media($nav-width) {
24 | @include padding(null $site-margins);
25 | }
26 |
27 | p {
28 | &:first-child {
29 | margin-top: 1rem;
30 |
31 | @include media($medium-screen) {
32 | margin-top: 0;
33 | }
34 | }
35 | }
36 | }
37 |
38 | .usa-banner-inner {
39 | @include outer-container();
40 | @include padding(null $site-margins-mobile);
41 | max-width: $site-max-width;
42 |
43 | @include media($nav-width) {
44 | @include padding(null $site-margins);
45 | }
46 | }
47 |
48 | .usa-banner-header {
49 | @include padding(0.55rem null);
50 | font-size: 1.2rem;
51 | font-weight: $font-normal;
52 |
53 | @include media($small-screen) {
54 | @include padding(0 null);
55 | }
56 |
57 | p {
58 | @include margin(0 null);
59 | display: inline;
60 | overflow: hidden;
61 | vertical-align: middle;
62 |
63 | @include media($small-screen) {
64 | @include margin(2px null);
65 | display: inline-block;
66 | }
67 | }
68 |
69 | img {
70 | float: left;
71 | margin-right: 1rem;
72 | margin-top: 1px;
73 | width: 2.4rem;
74 |
75 | @include media($small-screen) {
76 | margin-right: 0.7rem;
77 | width: 2rem;
78 | }
79 | }
80 | }
81 |
82 | .usa-banner-header-expanded {
83 | align-items: center;
84 | border-bottom: 1px solid $color-gray-light;
85 | display: flex;
86 | font-size: $h5-font-size;
87 | min-height: 5.6rem;
88 | padding-right: 3rem;
89 |
90 | @include media($small-screen) {
91 | border-bottom: none;
92 | display: block;
93 | font-size: 1.2rem;
94 | font-weight: $font-normal;
95 | min-height: 0;
96 | padding-right: 0;
97 | }
98 |
99 | > .usa-banner-inner {
100 | margin-left: 0;
101 |
102 | @include media($small-screen) {
103 | margin-left: auto;
104 | }
105 | }
106 |
107 | img {
108 | margin-right: 2.4rem;
109 |
110 | @include media($small-screen) {
111 | margin-right: 0.7rem;
112 | }
113 | }
114 |
115 | p {
116 | line-height: $heading-line-height;
117 | vertical-align: top;
118 |
119 | @include media($small-screen) {
120 | line-height: $base-line-height;
121 | vertical-align: middle;
122 | }
123 | }
124 | }
125 |
126 | .usa-banner-button {
127 | @include button-unstyled;
128 | @include padding(1.3rem null null 4.8rem);
129 | background-position-x: right;
130 | color: $color-primary;
131 | display: block;
132 | font-size: 1.2rem;
133 | height: 4.3rem;
134 | left: 0;
135 | position: absolute;
136 | text-decoration: underline;
137 | top: 0;
138 | width: 100%;
139 |
140 | @include media($small-screen) {
141 | @include padding(0 null null 0);
142 | /* stylelint-disable declaration-block-no-ignored-properties */
143 | display: inline;
144 | height: initial;
145 | margin-left: 3px;
146 | position: relative;
147 | vertical-align: middle;
148 | width: initial;
149 | /* stylelint-enable */
150 | }
151 |
152 | &:hover {
153 | color: $color-primary-darker;
154 | text-decoration: underline;
155 | }
156 |
157 | &[aria-expanded=false] {
158 | @include display-icon(angle-arrow-down-primary, after, 1rem, 0, hover);
159 | background-image: none;
160 |
161 | &::after {
162 | background-position-y: 1px;
163 | }
164 | }
165 |
166 | &[aria-expanded=true] {
167 | @include display-icon(close, after, 1.3rem, 0, no-hover);
168 | background-image: none;
169 | height: 5.6rem;
170 |
171 | @include media($small-screen) {
172 | @include display-icon(angle-arrow-up-primary, after, 1rem, 0, hover);
173 | height: initial;
174 | padding: 0;
175 | position: relative;
176 |
177 | &::after {
178 | background-position-y: 1px;
179 | }
180 | }
181 |
182 | &::after {
183 | position: absolute;
184 | right: 1.5rem;
185 | top: 2.15rem;
186 |
187 | @include media($small-screen) {
188 | position: static;
189 | }
190 | }
191 |
192 | .usa-banner-button-text {
193 | display: none;
194 |
195 | @include media($small-screen) {
196 | display: inline;
197 | }
198 | }
199 | }
200 | }
201 |
202 | .usa-banner-icon {
203 | width: 3.8rem;
204 | }
205 |
--------------------------------------------------------------------------------
/lib/components/Tables.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | /**
4 | * Class representing a label and a input of type="text"
5 | *
6 | * Required props:
7 | * - n
8 | *
9 | * Optional props:
10 | * - borderless: bool, defaults to false -- if true, removes verticale cell borders
11 | * - caption: string -- if passed, adds a caption element
12 | * - columns: array -- the titles of the columns,
13 | * - data: array -- table boy's row and column data
14 | * - children: node -- If data is empty, then children will be the rows and cells
15 | */
16 | export class Table extends Component {
17 | static propTypes = {
18 | columns: PropTypes.array.isRequired,
19 | borderless: PropTypes.bool,
20 | caption: PropTypes.string,
21 | data: PropTypes.array,
22 | children: PropTypes.node
23 | };
24 |
25 | static defaultProps = {
26 | borderless: false
27 | };
28 |
29 | /**
30 | * constructor
31 | * @param {object} props The component props
32 | */
33 | constructor(props) {
34 | super(props);
35 | }
36 |
37 | /**
38 | * Generates the table header cells
39 | * @returns {node} elements
40 | */
41 | _renderHeaders() {
42 | // Loop over the props.columns array
43 | return this.props.columns.map((column, index) => {
44 | // if each item in the array is a string, then use that inside the |
45 | if (typeof column === 'string') {
46 | return {column};
47 | // else expect an object.
48 | // Use object.displayName for the | text or colId is displayName is not provided
49 | } else {
50 | let {colId, displayName} = column;
51 | return {displayName || colId};
52 | }
53 | });
54 | }
55 |
56 | /**
57 | * Renders the table rows if data is passed in
58 | * @param {array} data from props.data
59 | * @returns {node} rendered DOM node ( | | ... |
)
60 | */
61 | _renderRows(data) {
62 | // Loop over the data array
63 | return data.map((datum, rowKey) => {
64 | // Loop over columns array to get the colIds
65 | const cells = this.props.columns.map(({colId}, key) => {
66 | // if it's the first cell in the row, make it a
67 | if (key === 0) {
68 | return (
69 |
70 | {datum[colId]}
71 |
72 | );
73 | // else make it a |
74 | } else {
75 | return (
76 |
77 | {datum[colId]}
78 |
79 | );
80 | }
81 | });
82 | // return a row with all the cells for each item in columns array
83 | return {cells};
84 | });
85 | }
86 |
87 | /**
88 | * @returns {node} DOM node (table)
89 | */
90 | render() {
91 | return (
92 |
93 | {/* If a caption was provided, render it */}
94 | {this.props.caption
95 | ? {this.props.caption}
96 | : null}
97 |
98 |
99 | {this._renderHeaders()}
100 |
101 |
102 |
103 | {/* If data was passed, use it to render rows. Else use children */}
104 | {this.props.data
105 | ? this._renderRows(this.props.data)
106 | : this.props.children}
107 |
108 |
109 | );
110 | }
111 | }
112 |
113 | /**
114 | * A table row element
115 | *
116 | * @returns {node} the rendered DOM node
117 | * @param {object} props The children
118 | */
119 | export function TableRow (props) {
120 | let {children, ...otherProps} = props;
121 | return (
122 | |
123 | {children}
124 |
125 | );
126 | }
127 |
128 | TableRow.propTypes = {
129 | children: PropTypes.node.isRequired
130 | };
131 |
132 |
133 | /**
134 | * A table header cell element
135 | *
136 | * @returns {node} the rendered DOM node
137 | * @param {object} children The children
138 | */
139 | export function TableHeaderCell ({children, ...otherProps}) {
140 | return (
141 |
142 | {children}
143 | |
144 | );
145 | }
146 |
147 | TableHeaderCell.propTypes = {
148 | scope: PropTypes.oneOf([
149 | 'col',
150 | 'row'
151 | ]).isRequired,
152 | children: PropTypes.node.isRequired
153 | };
154 |
155 | TableHeaderCell.defaultProps = {
156 | scope: 'row'
157 | };
158 |
159 |
160 | /**
161 | * A table cell element
162 | *
163 | * @returns {node} the rendered DOM node
164 | * @param {object} props The props
165 | */
166 | export function TableCell (props) {
167 | let {children, ...otherProps} = props;
168 | return (
169 |
170 | {children}
171 | |
172 | );
173 | }
174 |
175 | TableCell.propTypes = {
176 | children: PropTypes.node.isRequired
177 | };
178 |
--------------------------------------------------------------------------------
/lib/components/TextArea.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import InputLabel from './InputLabel';
3 | import Utilities from '../helpers/utilities';
4 |
5 | /**
6 | * Class representing a label and a textarea element
7 | *
8 | * Required props:
9 | * - label: Sets the text for the input's label
10 | *
11 | * Optional props:
12 | * - id: sets the id attribute for the input and the