Skip to content

Commit

Permalink
Merge pull request #71 from arcticicestudio/feature/gh-70-core-atom-h…
Browse files Browse the repository at this point in the history
…tml-element-a

Core Atom HTML Element: A
  • Loading branch information
arcticicestudio authored Dec 6, 2018
2 parents 208c9ac + e396fb4 commit 75d0e49
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 4 deletions.
21 changes: 21 additions & 0 deletions src/components/atoms/core/HTMLElements/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

/**
* @file Provides components that represents basic HTML elements.
* @author Arctic Ice Studio <[email protected]>
* @author Sven Greb <[email protected]>
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element
* @since 0.3.0
*/

import { A } from "./inlineTextSemantics";

/* eslint-disable-next-line import/prefer-default-export */
export { A };
69 changes: 69 additions & 0 deletions src/components/atoms/core/HTMLElements/inlineTextSemantics/A.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

/**
* @author Arctic Ice Studio <[email protected]>
* @author Sven Greb <[email protected]>
* @since 0.3.0
*/

import React from "react";
import PropTypes from "prop-types";
import { Link } from "gatsby";
import styled from "styled-components";

import { isRouteInternal } from "utils";

const BaseComponent = styled.a`
color: inherit;
cursor: pointer;
text-decoration: none;
&:active,
&:focus,
&:hover,
&:visited {
outline: none;
}
`;

/**
* A dynamic and failsafe component which either renders to a base HTML link `<a>` (anchor) or a "Gatsby Link" based on
* the target/URL type, internal or external, passed to the `to` and `href` props.
*
* @example <caption>Usage</caption>
* <!-- The target is external so both will render to `<a>` with the `href` prop. -->
* <A href="https://arcticicestudio.github.io/nord">Nord</A>
* <A to="https://arcticicestudio.github.io/nord">Nord</A>
* <!-- The target is internal so both will render to `<Link>` with the `to` prop. -->
* <A to="/blog">Blog</A>
* <A href="/blog">Blog</A>
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
* @see https://www.gatsbyjs.org/docs/gatsby-link
* @since 0.3.0
*/
const A = ({ children, href, to, ...passProps }) =>
isRouteInternal(to) || isRouteInternal(href) ? (
<BaseComponent as={Link} to={to || href} {...passProps}>
{children}
</BaseComponent>
) : (
<BaseComponent href={href || to} {...passProps}>
{children}
</BaseComponent>
);

A.propTypes = {
children: PropTypes.node.isRequired,
to: PropTypes.string
};

A.defaultProps = { to: "" };

export default A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

/**
* @file Provides components that represent basic HTML elements with inline text semantics functionality.
* @author Arctic Ice Studio <[email protected]>
* @author Sven Greb <[email protected]>
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Inline_text_semantics
* @since 0.3.0
*/

import A from "./A";

/* eslint-disable-next-line import/prefer-default-export */
export { A };
18 changes: 18 additions & 0 deletions src/config/routes/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ const ROOT = pathSeparator;
*/
const BLOG = "blog";

/**
* The route name of the "community" page.
*
* @constant {string}
* @since 0.3.0
*/
const COMMUNITY = "community";

/**
* The route name of the "docs" page.
*
Expand All @@ -67,10 +75,20 @@ const DOCS = "docs";
*/
const LANDING = "landing";

/**
* The route name of the port projects page.
*
* @constant {string}
* @since 0.3.0
*/
const PORTS = "ports";

module.exports = {
BASE_PUBLIC_URL,
BLOG,
COMMUNITY,
DOCS,
LANDING,
PORTS,
ROOT
};
24 changes: 21 additions & 3 deletions src/config/routes/mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* @since 0.1.0
*/

const { ROOT, BLOG, DOCS, LANDING } = require("./constants");
const { BLOG, COMMUNITY, DOCS, LANDING, PORTS, ROOT } = require("./constants");

/**
* The root route mapping.
Expand All @@ -32,6 +32,14 @@ const ROUTE_ROOT = ROOT;
*/
const ROUTE_BLOG = ROUTE_ROOT + BLOG;

/**
* The route mapping for the "community" page.
*
* @constant {string}
* @since 0.3.0
*/
const ROUTE_COMMUNITY = ROUTE_ROOT + COMMUNITY;

/**
* The route mapping for the "docs" page.
*
Expand All @@ -49,9 +57,19 @@ const ROUTE_DOCS = ROUTE_ROOT + DOCS;
*/
const ROUTE_LANDING = ROUTE_ROOT + LANDING;

/**
* The route mapping for the port projects page.
*
* @constant {string}
* @since 0.3.0
*/
const ROUTE_PORTS = ROUTE_ROOT + PORTS;

module.exports = {
ROUTE_ROOT,
ROUTE_BLOG,
ROUTE_COMMUNITY,
ROUTE_DOCS,
ROUTE_LANDING
ROUTE_LANDING,
ROUTE_PORTS,
ROUTE_ROOT
};
4 changes: 3 additions & 1 deletion src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* @since 0.2.0
*/

import isRouteInternal from "./isRouteInternal";
import isRoutePartiallyMatch from "./isRoutePartiallyMatch";
import { readSessionCache, writeSessionCache } from "./sessionCache";

export { readSessionCache, writeSessionCache };
export { isRouteInternal, isRoutePartiallyMatch, readSessionCache, writeSessionCache };
24 changes: 24 additions & 0 deletions src/utils/isRouteInternal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

/* eslint-disable no-useless-escape */

/**
* Validates if the given route is internal.
* Matches exactly one slash or hash, anything else is external including relative routes starting with two slahes.
* The hash allows to link to anchors within the same document.
*
* @method isRouteInternal
* @param {string} route The route to validate.
* @return {Boolean} `true` if the given route is internal, `false` otherwise.
* @since 0.3.0
*/
const isRouteInternal = route => /^[\/#](?!\/)/.test(route);

export default isRouteInternal;
37 changes: 37 additions & 0 deletions src/utils/isRoutePartiallyMatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

import isRouteInternal from "./isRouteInternal";

/**
* Validates if the given path element partially matches the route.
*
* @method isRoutePartiallyMatch
* @param {string} route The route to check.
* @param {string} pathElement The path element to check against the route.
* @return {Boolean} `true` if the given path element is partially matching, `false` otherwise.
* @since 0.3.0
*/
const isRoutePartiallyMatch = (route, pathElement) => {
/* Don't match exact and external routes. */
if (route === pathElement) return false;
if (!isRouteInternal(pathElement)) return false;

/* Split into path elements and filter out leading and pending slashes. */
const routeTokens = route.split("/").filter(t => t.length);
const pathElementTokens = pathElement.split("/").filter(t => t.length);

const isMatch = pathElementTokens.every((t, idx) => routeTokens[idx] === t);
/* Prevent false-positive match by only allowing the path element as exact root when current route is not the root. */
const isPathElementExactRoot = pathElement === "/" && route !== "/";

return isPathElementExactRoot ? false : isMatch;
};

export default isRoutePartiallyMatch;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

import React from "react";
import { render } from "react-testing-library";

import { A } from "atoms/core/HTMLElements";
import { ROUTE_DOCS } from "config/routes/mappings";
import { metadataNordDocs } from "data/project";

describe("snapshot", () => {
test("renders inernal URLs with `to` prop", () => {
const { container } = render(<A to={ROUTE_DOCS}>Docs</A>);
expect(container).toMatchSnapshot();
});

test("renders inernal URLs with `href` prop", () => {
const { container } = render(<A href={ROUTE_DOCS}>Docs</A>);
expect(container).toMatchSnapshot();
});

test("renders external URLs with `href` prop", () => {
const { container } = render(<A href={metadataNordDocs.homepage}>Docs</A>);
expect(container).toMatchSnapshot();
});

test("renders external URLs with `to` prop", () => {
const { container } = render(<A to={metadataNordDocs.homepage}>Docs</A>);
expect(container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`snapshot renders external URLs with \`href\` prop 1`] = `
<div>
<a
class="sc-bdVaJa ccNLbx"
href="https://nordtheme.com"
>
Docs
</a>
</div>
`;

exports[`snapshot renders external URLs with \`to\` prop 1`] = `
<div>
<a
class="sc-bdVaJa ccNLbx"
href="https://nordtheme.com"
>
Docs
</a>
</div>
`;

exports[`snapshot renders inernal URLs with \`href\` prop 1`] = `
<div>
<a
class="sc-bdVaJa ccNLbx"
href="/docs"
>
Docs
</a>
</div>
`;

exports[`snapshot renders inernal URLs with \`to\` prop 1`] = `
<div>
<a
class="sc-bdVaJa ccNLbx"
href="/docs"
>
Docs
</a>
</div>
`;
41 changes: 41 additions & 0 deletions test/utils/isRouteInternal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
* Copyright (C) 2018-present Sven Greb <[email protected]>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

import { isRouteInternal } from "utils";
import { ROUTE_BLOG, ROUTE_DOCS, ROUTE_COMMUNITY, ROUTE_PORTS, ROUTE_ROOT } from "config/routes/mappings";
import { metadataNordDocs } from "data/project";

describe("internal routes are", () => {
test("matching", () => {
[
"#",
ROUTE_ROOT,
`${ROUTE_ROOT}#`,
`${ROUTE_ROOT}?port=atom`,
ROUTE_BLOG,
ROUTE_DOCS,
ROUTE_COMMUNITY,
ROUTE_PORTS
].forEach(route => expect(isRouteInternal(route)).toBeTruthy());
});

test("not matching", () => {
[
`${metadataNordDocs.homepage}`,
`${metadataNordDocs.repository.url}`,
"https://github.com/arcticicestudio",
"https://www.nordtheme.com",
"https://nordtheme.com",
"https://nordtheme.com",
"//nordtheme.com",
"file:///etc/hosts",
"mailto:[email protected]"
].forEach(route => expect(isRouteInternal(route)).toBeFalsy());
});
});
Loading

0 comments on commit 75d0e49

Please sign in to comment.