Skip to content

Commit

Permalink
Merge pull request #2 from drinking-code/v1.1.0
Browse files Browse the repository at this point in the history
Merge v1.1.0
  • Loading branch information
drinking-code committed Aug 8, 2021
2 parents eb35968 + a310c9a commit af7a09c
Show file tree
Hide file tree
Showing 17 changed files with 521 additions and 457 deletions.
3 changes: 1 addition & 2 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"presets": [
"@babel/react",
"@babel/env",
"minify"
"@babel/env"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ const App = () => {

export default App;
```
`react-round-div` will clip the `border-radius` of it is too large to prevent artifacts from appearing. You can turn this off by passing `clip={false}`.


## Caveats
This package is still in the starting blocks, so for now only single colored backgrounds and solid, uniform borders are supported.
There is support to come for gradients and image backgrounds, gradient borders, and perhaps proper `backdrop-filter` support.
This package is still in the starting blocks, so for now borders are only supported in the `solid` style and some transitions on the div may not work properly.

Moreover, children inside `RoundDiv` get cut off when are placed (partly) outside the div due to `clip-path` being used to make the smooth corners. There will probably an option in later versions to use a pseudo-element for the shape, so that children can be rendered outside.
2 changes: 1 addition & 1 deletion img/compare.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
182 changes: 63 additions & 119 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-round-div",
"version": "1.0.0",
"version": "1.1.0",
"description": "Make your rounded corners look phenomenal with g2 continuity.",
"main": "dist/main.js",
"scripts": {
Expand Down
110 changes: 49 additions & 61 deletions src/css-utils.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,63 @@
import CSS_COLOR_NAMES from "./html-colors";
import toPx from "./css-length-converter";
import CSS_COLOR_NAMES from "./external/bobspace:html-colors";
import toPx from "./external/heygrady:units:length";

function _getAttributeFromString(string, method, ...data) {
if (!string) return false
string = string.split(' ')
for (let i in string) {
const res = method(string, Number(i), ...data)
if (res) return res
}
}

function _getColor(border, i) {
const val = border[i]
/** @returns {string} */
function convertPlainColor(val) {
if (!val) return '#000'
val = val?.toLowerCase()
// color is a hex code
if (val.toLowerCase().match(/#([0-9a-f]{3}){1,2}/)) return val
if (val?.match(/#([0-9a-f]{3}){1,2}/)) return val
// color is a function (rgb, rgba, hsl, hsla)
if (val.startsWith('rgb') || val.startsWith('hsl')) {
let color = val;
if (!val.endsWith(')'))
for (let j = 1; !border[i + j - 1].endsWith(')'); j++) {
color += border[i + j]
}
if (color[3] === 'a')
color = color.replace('a', '').replace(/,[^),]+\)/, ')')
return color
}
else if (val?.match(/^(rgb|hsl)a?\(([^,]{1,3},? *){3}(\d*\.?\d+)?\)/))
return val
.replace('a', '')
.replace(/\((([\d%]{1,3}, *){2}([\d%]{1,3}))(, *\d*\.?\d+)?\)/, '($1)')
// color is a html color name
if (
CSS_COLOR_NAMES.map(color => color.toLowerCase())
.includes(val.toLowerCase())
) return val
return false
else if (CSS_COLOR_NAMES.map(color => color.toLowerCase())
.includes(val.toLowerCase()))
return val
else if (val === 'currentcolor') {
return 'currentcolor'
} else return '#000'
}

function _getOpacity(border, i) {
let val = border[i]
if (val.startsWith('rgba') || val.startsWith('hsla')) {
if (!val.endsWith(')'))
for (let j = 1; !border[i + j - 1].endsWith(')'); j++) {
val += border[i + j]
}
return val.replace(/(rgb|hsl)a?\(([^,)]+,){3}/, '').replace(/\)$/, '')
}
if (border.length - 1 === i)
return 1
/** @returns {number} */
function convertColorOpacity(val) {
if (val?.startsWith('rgba') || val?.startsWith('hsla')) {
return Number(val.match(/(\d*\.?\d+)?\)$/)[1])
} else return 1
}

const htmlLengthNotSvgError = new Error('<RoundDiv> Border lengths must be either "thin", "medium", "thick", or in one of the following units: ch, cm, em, ex, in, mm, pc, pt, px, rem, vh, vmax, vmin, vw.')

function unitCheck(length) {
if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw htmlLengthNotSvgError
return length
const htmlLengthNotSvgErrorTemplate = (a, b) => `<RoundDiv> ${a} must be ${b ? `either ${b}, or` : ''} in one of the following units: ch, cm, em, ex, in, mm, pc, pt, px, rem, vh, vmax, vmin, vw.`
const htmlBorderLengthNotSvgError =
new Error(htmlLengthNotSvgErrorTemplate('border lengths', '"thin", "medium", "thick"'))
const htmlBorderRadiusNotSvgError =
new Error(htmlLengthNotSvgErrorTemplate('border radii'))

function toNumber(length, element, err) {
if (!length) return false
if (typeof length === 'number' || !length.match(/\D+/))
return Number(length);
else if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g))
if (err) throw err
else return false
else if (length?.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/))
return toPx(element, length)
}

function _getWidth(border, i, element) {
const val = border[i]
// width is 0
if (val === '0') return 0
/** @returns {number} */
function convertBorderWidth(val, element) {
if (!val) return 0
// width is a word
if (val.toLowerCase() === 'thin') return 1
if (val.toLowerCase() === 'medium') return 3
if (val.toLowerCase() === 'thick') return 5
unitCheck(val)
if (val?.toLowerCase() === 'thin')
return 1
else if (val?.toLowerCase() === 'medium')
return 3
else if (val?.toLowerCase() === 'thick')
return 5
// width is <length>
if (val.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/))
return toPx(element, val)
return false
else
return toNumber(val, element, htmlBorderLengthNotSvgError) || 0
}

const getWidth = s => _getAttributeFromString(s, _getWidth),
getColor = s => _getAttributeFromString(s, _getColor),
getOpacity = s => _getAttributeFromString(s, _getOpacity)

export {getWidth, getColor, unitCheck, getOpacity}
export {convertPlainColor, convertColorOpacity, convertBorderWidth, toNumber, htmlBorderRadiusNotSvgError}
File renamed without changes.
8 changes: 8 additions & 0 deletions src/external/bobspace:html-colors.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
File renamed without changes.
File renamed without changes.
111 changes: 111 additions & 0 deletions src/generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @param {number} height
* @param {number} width
* @param {number | Array<number>} radius
*
* @returns {string} SVG path data
* */
export default function generateSvgSquircle(height, width, radius) {
/* from right to left top left corner upper (right half) */
const ratios = [1.528665037, 1.0884928889, 0.8684068148, 0.07491140741, 0.6314939259, 0.1690595556, 0.3728238519];
const roundToNthPlace = 1;

if (typeof radius === 'number')
radius = Array(4).fill(radius)
else if (radius.length === 2)
radius.push(radius[0])
if (radius.length === 3)
radius.push(radius[1])

height = Number(height);
width = Number(width);

const _rawRadius = [...radius].map(n => Number(n))
const max = radius.length - 1
const next = i => i === max ? 0 : i + 1
const prev = i => i === 0 ? max : i - 1
radius = _rawRadius.map((radius, i) =>
Math.min(
radius,
Math.min(
height - _rawRadius[i % 2 === 0 ? prev(i) : next(i)],
height / 2
),
Math.min(
width - _rawRadius[i % 2 === 0 ? next(i) : prev(i)],
width / 2
)
)
)

const [a0x, a1x, a2x, a3y, a3x, b1y, b1x] = Array(7)
.fill(Array(4).fill(0))
.map((a, i) => a.map((b, j) => radius[j] * ratios[i])),
[b0y, b0x] = [a3y, a3x]

if (isNaN(height)) throw new Error(`'height' must be a number`);
if (isNaN(width)) throw new Error(`'width' must be a number`);
if (radius.includes(NaN)) throw new Error(`'radius' must be a number or an array containing 2 to 4 numbers`);

const a0xF = x => Math.min(x / 2, a0x[0]),
a0xw = a0xF(width),
a0xh = a0xF(height)

/*function mapRange(number, in_min, in_max, out_min, out_max) {
return (number - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}*/

// const maxRadius = Math.max(...radius);

const yOffsetF = (x) => 0,
hyOffset = yOffsetF(height) || 0,
wyOffset = yOffsetF(width) || 0

const startPoint = `${a0xw},${wyOffset}`

return `M${startPoint}
${width / 2 < a0x[1]
? ''
: `L${width - a0xw},0`
}
C${width - a1x[1]},0,${width - a2x[1]},0,${width - a3x[1]},${a3y[1]}
C${width - b1x[1]},${b1y[1]},${width - b1y[1]},${b1x[1]},${width - b0y[1]},${b0x[1]}
C${width},${a2x[1]},${width},${a1x[1]},
${width - hyOffset},${a0xh}
${height / 2 < a0x[2]
? ''
: `L${width},${height - a0xh}`
}
C${width},${height - a1x[2]},${width},${height - a2x[2]},${width - a3y[2]},${height - a3x[2]}
C${width - b1y[2]},${height - b1x[2]},${width - b1x[2]},${height - b1y[2]},${width - b0x[2]},${height - b0y[2]}
C${width - a2x[2]},${height},${width - a1x[2]},${height},
${width - a0xw},${height - wyOffset}
${width / 2 < a0x[3]
? ''
: `L${a0xw},${height}`
}
C${a1x[3]},${height},${a2x[3]},${height},${a3x[3]},${height - a3y[3]}
C${b1x[3]},${height - b1y[3]},${b1y[3]},${height - b1x[3]},${b0y[3]},${height - b0x[3]}
C0,${height - a2x[3]},0,${height - a1x[3]},
${hyOffset},${height - a0xh}
${height / 2 < a0x[0]
? ''
: `L0,${a0xh}`
}
C0,${a1x[0]},0,${a2x[0]},${a3y[0]},${a3x[0]}
C${b1y[0]},${b1x[0]},${b1x[0]},${b1y[0]},${b0x[0]},${b0y[0]}
C${a2x[0]},0,${a1x[0]},0,${startPoint}
Z`
.replace(/[\n ]/g, '')
.replace(/NaN/g, '0')
.replace(/\d+\.\d+/g, match =>
Math.round(Number(match) * (10 ** roundToNthPlace)) / (10 ** roundToNthPlace)
)
}
Loading

0 comments on commit af7a09c

Please sign in to comment.