├── FitterHappierText.js
├── README.md
├── bundle.js
├── demo
├── Ad.jsx
├── Demo.jsx
├── TweetButton.jsx
├── base.css
└── entry.js
├── index.html
├── package.json
├── src
└── FitterHappierText.jsx
└── webpack.config.js
/FitterHappierText.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 |
7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8 |
9 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
10 |
11 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
12 |
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
14 |
15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
16 |
17 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
18 |
19 | var _react = require('react');
20 |
21 | var _react2 = _interopRequireDefault(_react);
22 |
23 | var _lodash = require('lodash');
24 |
25 | var FitterHappierText = (function (_React$Component) {
26 | _inherits(FitterHappierText, _React$Component);
27 |
28 | function FitterHappierText() {
29 | _classCallCheck(this, FitterHappierText);
30 |
31 | _get(Object.getPrototypeOf(FitterHappierText.prototype), 'constructor', this).call(this);
32 | this.resize = (0, _lodash.debounce)(this.resize.bind(this));
33 | this.state = {
34 | width: 256,
35 | height: 24
36 | };
37 | }
38 |
39 | _createClass(FitterHappierText, [{
40 | key: 'resize',
41 | value: function resize() {
42 | var el = this.refs.text;
43 | var state = this.state;
44 | var width = el.offsetWidth || el.getComputedTextLength();
45 | var height = el.offsetHeight | 24;
46 | if (state.width !== width || state.height !== height) {
47 | this.setState({
48 | width: width,
49 | height: height
50 | });
51 | }
52 | }
53 | }, {
54 | key: 'componentDidMount',
55 | value: function componentDidMount() {
56 | this.resize();
57 | }
58 | }, {
59 | key: 'componentWillReceiveProps',
60 | value: function componentWillReceiveProps() {
61 | this.resize();
62 | }
63 | }, {
64 | key: 'render',
65 | value: function render() {
66 | var styles = {
67 | svg: {
68 | width: '100%',
69 | maxHeight: '100%',
70 | fill: 'currentcolor',
71 | overflow: 'visible'
72 | },
73 | text: {
74 | fontFamily: 'inherit',
75 | fontSize: '1rem',
76 | fontWeight: 'inherit',
77 | textAnchor: 'middle'
78 | }
79 | };
80 | var viewBox = [0, 0, this.state.width, this.state.height].join(' ');
81 |
82 | return _react2['default'].createElement(
83 | 'svg',
84 | _extends({}, this.props, {
85 | viewBox: viewBox,
86 | style: styles.svg }),
87 | _react2['default'].createElement(
88 | 'text',
89 | {
90 | ref: 'text',
91 | x: '50%',
92 | y: this.props.baseline,
93 | style: styles.text },
94 | this.props.text
95 | )
96 | );
97 | }
98 | }]);
99 |
100 | return FitterHappierText;
101 | })(_react2['default'].Component);
102 |
103 | FitterHappierText.defaultProps = {
104 | baseline: 16,
105 | paddingY: 0
106 | };
107 |
108 | FitterHappierText.propTypes = {
109 | text: _react2['default'].PropTypes.string,
110 | baseline: _react2['default'].PropTypes.number,
111 | paddingY: _react2['default'].PropTypes.number
112 | };
113 |
114 | exports['default'] = FitterHappierText;
115 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-fitter-happier-text
2 |
3 | React component for fully fluid headings
4 |
5 | *Note:* Requires `react >= 0.14.0`
6 |
7 | ## Demo
8 |
9 | http://jxnblk.com/react-fitter-happier-text
10 |
11 | ## Usage
12 |
13 | ```bash
14 | npm i react-fitter-happier-text
15 | ```
16 |
17 | ```js
18 | var React = require('react')
19 | var FitterHappierText = require('react-fitter-happier-text')
20 |
21 | React.render(, document.querySelector('#hello-world'))
22 | ```
23 |
24 | Note: this component relies on `element.offsetWidth` and only works in client-side contexts.
25 |
26 | ## Related
27 |
28 | http://jxnblk.com/fitter-happier-text
29 |
30 | MIT License
31 |
32 |
--------------------------------------------------------------------------------
/demo/Ad.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | class Ad extends React.Component {
5 |
6 | render () {
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | }
15 |
16 | export default Ad
17 |
18 |
--------------------------------------------------------------------------------
/demo/Demo.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import FitterHappierText from '../FitterHappierText'
4 | import { MinimalHeader, Header, Footer } from 'blk'
5 |
6 | class Demo extends React.Component {
7 |
8 | constructor () {
9 | super ()
10 | this.state = {
11 | text: 'Live Demo',
12 | caps: true
13 | }
14 | this.handleChange = this.handleChange.bind(this)
15 | this.toggleCaps = this.toggleCaps.bind(this)
16 | }
17 |
18 | handleChange (e) {
19 | this.setState({ text: e.target.value })
20 | }
21 |
22 | toggleCaps () {
23 | var caps = !this.state.caps
24 | this.setState({ caps: caps })
25 | }
26 |
27 | render () {
28 | var classNames = {
29 | demo: (this.state.caps ? 'caps' : '') + ' avenir'
30 | }
31 | return (
32 |
33 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
52 |
53 |
54 |
57 |
58 |
59 |
87 |
88 |
Usage
89 |
npm i react-fitter-happier-text
90 |
91 | {`var FitterHappierText = require('react-fitter-happier-text')
92 | React.render(, document.querySelector('#hello-world'))`}
93 |
94 |
Note: this component relies on element.offsetWidth
and only works in client-side contexts.
95 |
96 |
102 |
103 |
104 | )
105 | }
106 |
107 | }
108 |
109 | export default Demo
110 |
111 |
112 |
--------------------------------------------------------------------------------
/demo/TweetButton.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | class TweetButton extends React.Component {
5 |
6 | render () {
7 | var script = {
8 | __html: '!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?"http":"https";if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document, "script", "twitter-wjs");'
9 | }
10 | var text = this.props.text
11 | return (
12 |
22 | )
23 | }
24 |
25 | }
26 |
27 | export default TweetButton
28 |
29 |
--------------------------------------------------------------------------------
/demo/base.css:
--------------------------------------------------------------------------------
1 |
2 | @import 'blk';
3 |
4 | body {
5 | margin: 0;
6 | padding: 1em 1.5em;
7 | min-height: 100vh;
8 | color: #fff;
9 | background-color: var(--blue);
10 | }
11 |
12 | .avenir {
13 | font-family: 'Avenir Next', 'Helvetica Neue', Helvetica, sans-serif;
14 | font-weight: 600;
15 | line-height: 1;
16 | }
17 |
18 |
19 | .input {
20 | box-sizing: border-box;
21 | height: 2em;
22 | display: inline-block;
23 | font-family: inherit;
24 | font-size: inherit;
25 | line-height: 1;
26 | margin: 0;
27 | padding: .5em .5em;
28 | vertical-align: middle;
29 | color: inherit;
30 | border: 1px solid transparent;
31 | border-radius: 3px;
32 | -webkit-appearance: none;
33 | }
34 |
35 | .input-light {
36 | background-color: color(#fff a(.25));
37 | /*
38 | border-color: color(#000 a(.25));
39 | */
40 | }
41 |
42 | .input-light:focus {
43 | outline: 5px solid var(--darken-2);
44 | }
45 |
46 | .btn-dark {
47 | background-color: color(#000 a(.25));
48 | border-color: color(#000 a(.5));
49 | }
50 |
51 |
52 | #carbonads {
53 | font-size: 14px;
54 | font-weight: normal;
55 | line-height: 1.25;
56 | text-align: left;
57 | text-transform: none;
58 |
59 | padding: .5em;
60 | background-color: color(#fff a(.9));
61 | }
62 |
63 | #tweet-button {
64 | margin-top: 1em;
65 | }
66 |
67 | @media (min-width: 32em) {
68 | #carbonads {
69 | position: fixed;
70 | right: 0;
71 | bottom: 0;
72 | max-width: 320px;
73 | margin: .5em;
74 | }
75 | #tweet-button {
76 | position: fixed;
77 | left: 0;
78 | bottom: 0;
79 | padding: .5em;
80 | }
81 | }
82 |
83 | #carbonads a,
84 | #carbonads a:hover {
85 | color: #222;
86 | text-decoration: none;
87 | background: none;
88 | }
89 | #carbonads span { display: block }
90 | #carbonads > span::before,
91 | #carbonads > span::after {
92 | content: '';
93 | display: table;
94 | }
95 | #carbonads > span::after {
96 | clear: both;
97 | }
98 | .carbon-img {
99 | float: left;
100 | margin-right: .5em;
101 | }
102 | .carbon-img > img { display: block }
103 | .carbon-text { overflow: hidden }
104 | .carbon-poweredby {
105 | float: left;
106 | margin-top: .25em;
107 | opacity: 0.75;
108 | }
109 |
110 |
111 | /*
112 | @import 'gravitons';
113 | @import 'flex-object';
114 | */
115 |
116 | :root {
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/demo/entry.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import Demo from './Demo.jsx'
4 | import css from './base.css'
5 |
6 | React.render(React.createElement(Demo), document.querySelector('#demo'))
7 |
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Fitter Happier Text Demo
6 |
7 |
8 |
9 |
10 | JavaScript required for this demo
11 |
12 |
13 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-fitter-happier-text",
3 | "version": "1.0.2",
4 | "description": "React component for fully fluid headings",
5 | "main": "FitterHappierText.js",
6 | "scripts": {
7 | "babel": "babel src --out-dir .",
8 | "babel:watch": "babel -w src --out-dir .",
9 | "webpack": "webpack --progress --colors",
10 | "dev": "webpack-dev-server --progress --colors",
11 | "prod": "webpack -p --progress --colors",
12 | "start": "npm run babel:watch & npm run dev"
13 | },
14 | "keywords": [
15 | "react",
16 | "react-component",
17 | "svg",
18 | "headings"
19 | ],
20 | "author": "Brent Jackson",
21 | "license": "MIT",
22 | "devDependencies": {
23 | "babel": "^5.5.8",
24 | "babel-core": "^5.5.8",
25 | "babel-loader": "^5.1.4",
26 | "blk": "^3.0.4",
27 | "css-loader": "^0.15.1",
28 | "cssnext-loader": "^1.0.1",
29 | "flex-object": "^1.1.3",
30 | "gravitons": "^1.0.0",
31 | "node-libs-browser": "^0.5.2",
32 | "react": "^0.13.3",
33 | "style-loader": "^0.12.3",
34 | "webpack": "^1.9.11",
35 | "webpack-dev-server": "^1.9.0"
36 | },
37 | "dependencies": {
38 | "lodash": "^4.6.1",
39 | "react": "^0.14.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/FitterHappierText.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { debounce } from 'lodash'
4 |
5 | class FitterHappierText extends React.Component {
6 |
7 | constructor () {
8 | super ()
9 | this.resize = debounce(this.resize.bind(this))
10 | this.state = {
11 | width: 256,
12 | height: 24
13 | }
14 | }
15 |
16 | resize () {
17 | let el = this.refs.text;
18 | let state = this.state
19 | let width = el.offsetWidth || el.getComputedTextLength()
20 | let height = el.offsetHeight || 24
21 | if (state.width !== width || state.height !== height) {
22 | this.setState({
23 | width: width,
24 | height: height
25 | })
26 | }
27 | }
28 |
29 | componentDidMount () {
30 | this.resize()
31 | }
32 |
33 | componentWillReceiveProps () {
34 | this.resize()
35 | }
36 |
37 | render () {
38 | let styles = {
39 | svg: {
40 | width: '100%',
41 | maxHeight: '100%',
42 | fill: 'currentcolor',
43 | overflow: 'visible'
44 | },
45 | text: {
46 | fontFamily: 'inherit',
47 | fontSize: '1rem',
48 | fontWeight: 'inherit',
49 | textAnchor: 'middle'
50 | }
51 | }
52 | let viewBox = [
53 | 0, 0,
54 | this.state.width,
55 | this.state.height
56 | ].join(' ')
57 |
58 | return (
59 |
70 | )
71 | }
72 |
73 | }
74 |
75 | FitterHappierText.defaultProps = {
76 | baseline: 16,
77 | paddingY: 0,
78 | }
79 |
80 | FitterHappierText.propTypes = {
81 | text: React.PropTypes.string,
82 | baseline: React.PropTypes.number,
83 | paddingY: React.PropTypes.number,
84 | }
85 |
86 | export default FitterHappierText
87 |
88 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | entry: './demo/entry.js',
4 |
5 | output: {
6 | filename: 'bundle.js',
7 | path: __dirname
8 | },
9 |
10 | module: {
11 | loaders: [
12 | { test: /(\.js$|\.jsx$)/, exclude: /node_modules/, loader: 'babel-loader' },
13 | { test: /\.css$/, loader: 'style-loader!css-loader!cssnext-loader' }
14 | ]
15 | },
16 |
17 | cssnext: {
18 | compress: true,
19 | features: {
20 | }
21 | }
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------