diff --git a/packages/abtest/README.md b/packages/abtest/README.md
new file mode 100644
index 0000000000..dc1520814e
--- /dev/null
+++ b/packages/abtest/README.md
@@ -0,0 +1,66 @@
+# ndla-abtest
+
+WIP
+
+## Installation
+
+```sh
+$ yarn add --save ndla-abtest
+```
+
+```sh
+$ npm i --save ndla-abtest
+```
+
+## Usage
+
+```js
+import { ExperimentsContext, Experiment, Variant } from '@ndla/abtest';
+
+// clean experiments returned from Experiments service
+const cleanExperiments = [
+ {
+ id: '6bklbienTOuNQs9JwMrvog', // Experiment 1 ID
+ variant: {
+ // Use variant with index: 0
+ index: 0,
+ name: '',
+ },
+ },
+ {
+ id: 'gKKvagBlQ5SyhxWP4TqK0g', // Experiment 2 ID
+ variant: {
+ // Use variant with index: 2
+ index: 2,
+ name: '',
+ },
+ },
+];
+
+const experimentId = 'gKKvagBlQ5SyhxWP4TqK0g';
+
+
+
+ Testing button title in app
+
+ {({ experiments }) => (
+ {
+ console.log('render details', variantData);
+ }}>
+
+ Test 1
+
+ Test 2
+ Test 3
+
+ )}
+
+
+;
+```
diff --git a/packages/abtest/package.json b/packages/abtest/package.json
new file mode 100644
index 0000000000..0aec1dfdaa
--- /dev/null
+++ b/packages/abtest/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@ndla/abtest",
+ "version": "0.0.1",
+ "description": "AB-test Context",
+ "license": "GPL-3.0",
+ "main": "lib/index.js",
+ "module": "es/index.js",
+ "sideEffects": false,
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/NDLANO/frontend-packages.git/ndla-abtest/"
+ },
+ "keywords": [
+ "ndla",
+ "AB-test"
+ ],
+ "author": "ndla@knowit.no",
+ "files": [
+ "lib",
+ "es"
+ ],
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/abtest/src/Context.ts b/packages/abtest/src/Context.ts
new file mode 100644
index 0000000000..eaa72f8d7b
--- /dev/null
+++ b/packages/abtest/src/Context.ts
@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2019-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import React from 'react';
+
+export const ExperimentsContext = React.createContext({});
diff --git a/packages/abtest/src/Experiment.tsx b/packages/abtest/src/Experiment.tsx
new file mode 100644
index 0000000000..d2bb192b6b
--- /dev/null
+++ b/packages/abtest/src/Experiment.tsx
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2019-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import React from 'react';
+
+import { ExperimentsContext } from './Context';
+
+export interface VariationsShape {
+ index?: number;
+ name?: string;
+ weight?: number;
+}
+
+export interface ExperimentShape {
+ id: string;
+ variant: VariationsShape;
+}
+
+interface Props {
+ experiments: ExperimentShape[];
+ id: string;
+ onVariantMount?: void;
+ children: React.ReactNode[];
+}
+
+export const Experiment: React.FC = ({
+ experiments,
+ id: experimentId,
+ onVariantMount,
+ children,
+}) => {
+ const { Provider } = ExperimentsContext;
+ const useVariant = experiments.find(
+ experiment =>
+ experiment.id.localeCompare(experimentId, undefined, {
+ sensitivity: 'base',
+ }) === 0,
+ );
+ return (
+
+ {children}
+
+ );
+};
+
+interface fetchVariantIndexShape {
+ id: string;
+ experiments: ExperimentShape[];
+}
+
+export const fetchVariantIndex = ({
+ experiments,
+ id: experimentId,
+}: fetchVariantIndexShape) => {
+ const useVariant = experiments.find(
+ experiment =>
+ experiment.id.localeCompare(experimentId, undefined, {
+ sensitivity: 'base',
+ }) === 0,
+ );
+ return useVariant ? useVariant.variant : {};
+};
+
+export const isValidExperiment = ({
+ experiments,
+ id,
+}: fetchVariantIndexShape) => {
+ return experiments && experiments.find(ex => ex.id === id);
+};
diff --git a/packages/abtest/src/Variant.tsx b/packages/abtest/src/Variant.tsx
new file mode 100644
index 0000000000..443a5cb9dc
--- /dev/null
+++ b/packages/abtest/src/Variant.tsx
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2019-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import React from 'react';
+import { ExperimentsContext } from './Context';
+import { VariationsShape } from './Experiment';
+
+interface Props {
+ variantIndex: number;
+ original?: boolean;
+ onVariantMount?: void;
+ children: React.ReactNode[];
+}
+
+interface ValueShape {
+ experimentId?: string;
+ variant?: VariationsShape;
+}
+
+export class Variant extends React.Component {
+ componentDidMount() {
+ const { experimentId, variant, onVariantMount } = this.context;
+ const isActiveExperiment = this.isActive(this.context);
+ if (isActiveExperiment && onVariantMount) {
+ onVariantMount({
+ expId: experimentId,
+ expVar: variant.index,
+ isActiveExperiment,
+ });
+ }
+ }
+ isActive(value: ValueShape) {
+ const { variantIndex, original } = this.props;
+ return (
+ (!value.variant && original) ||
+ (value.variant && value.variant.index === variantIndex)
+ );
+ }
+ render() {
+ const { Consumer } = ExperimentsContext;
+ return (
+
+ {(value: ValueShape) =>
+ this.isActive(value) ? this.props.children : null
+ }
+
+ );
+ }
+}
+
+Variant.contextType = ExperimentsContext;
diff --git a/packages/abtest/src/cleanupExperiments.ts b/packages/abtest/src/cleanupExperiments.ts
new file mode 100644
index 0000000000..2ea344a782
--- /dev/null
+++ b/packages/abtest/src/cleanupExperiments.ts
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2019-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { ExperimentShape, VariationsShape } from './Experiment';
+
+export interface ExperimentShapeClean {
+ id: string;
+ variations: VariationsShape[];
+};
+
+export function cleanupExperiments(experiments: ExperimentShapeClean[], cookieExperiments: ExperimentShape[]) {
+ return experiments.map(experiment => {
+ const {
+ id,
+ variations,
+ } = experiment;
+
+ if (cookieExperiments) {
+ const experimentInCookie = cookieExperiments.find((cookieExperiments: ExperimentShape) => cookieExperiments.id === id);
+ if (experimentInCookie) {
+ return experimentInCookie;
+ }
+ }
+
+ const pickVariant = Math.random();
+ let variationsWeightCounter = 0;
+ const variationsTotal = variations.length - 1;
+ const winner = variations.find((variation: VariationsShape, index: number) => {
+ if (variationsWeightCounter + (variation.weight || 0) > pickVariant || index === variationsTotal) {
+ return true;
+ } else {
+ variationsWeightCounter += (variation.weight || 0);
+ return false;
+ }
+ });
+
+ if (typeof winner === 'object') {
+ winner.index = variations.findIndex(variant => variant.name === winner.name);
+ return {
+ id,
+ variant: winner,
+ }
+ }
+ return null;
+ }).filter(experiment => experiment);
+};
\ No newline at end of file
diff --git a/packages/abtest/src/index.ts b/packages/abtest/src/index.ts
new file mode 100644
index 0000000000..b17c373527
--- /dev/null
+++ b/packages/abtest/src/index.ts
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2019-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+export { ExperimentsContext } from './Context';
+export { Experiment, fetchVariantIndex, isValidExperiment } from './Experiment';
+export { Variant } from './Variant';
+export { cleanupExperiments } from './cleanupExperiments';
diff --git a/packages/abtest/tsconfig.build.json b/packages/abtest/tsconfig.build.json
new file mode 100644
index 0000000000..bdb104388c
--- /dev/null
+++ b/packages/abtest/tsconfig.build.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.build.json",
+ "compilerOptions": {
+ "baseUrl": "./",
+ "declarationDir": "./lib",
+ "rootDir": "./src"
+ },
+ "include": ["./src"]
+}
diff --git a/packages/designmanual/stories/LanguageWrapper/LanguageWrapper.jsx b/packages/designmanual/stories/LanguageWrapper/LanguageWrapper.jsx
index c768983e8a..a4a5e1b7f7 100644
--- a/packages/designmanual/stories/LanguageWrapper/LanguageWrapper.jsx
+++ b/packages/designmanual/stories/LanguageWrapper/LanguageWrapper.jsx
@@ -16,6 +16,7 @@ const messages = {
nn: formatNestedMessages(messagesNN),
en: formatNestedMessages(messagesEN),
};
+
export const LanguageContext = React.createContext();
class LanguageWrapperProvider extends Component {
diff --git a/packages/ndla-icons/src/common/Hamburger.js b/packages/ndla-icons/src/common/Hamburger.js
new file mode 100644
index 0000000000..ebaa12b546
--- /dev/null
+++ b/packages/ndla-icons/src/common/Hamburger.js
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2019-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+// N.B! AUTOGENERATED FILE. DO NOT EDIT
+import React from 'react';
+import Icon from '../Icon';
+
+const Hamburger = props => (
+
+
+
+
+
+);
+
+export default Hamburger;
diff --git a/packages/ndla-icons/src/common/index.js b/packages/ndla-icons/src/common/index.js
index cf2a674ed9..367e2a3bf8 100644
--- a/packages/ndla-icons/src/common/index.js
+++ b/packages/ndla-icons/src/common/index.js
@@ -28,6 +28,7 @@ export { default as FileDownloadOutline } from './FileDownloadOutline';
export { default as Forward } from './Forward';
export { default as Fullscreen } from './Fullscreen';
export { default as Grid } from './Grid';
+export { default as Hamburger } from './Hamburger';
export { default as HelpCircle } from './HelpCircle';
export { default as HelpCircleDual } from './HelpCircleDual';
export { default as Home } from './Home';
diff --git a/packages/ndla-icons/svg/common/Hamburger.svg b/packages/ndla-icons/svg/common/Hamburger.svg
new file mode 100644
index 0000000000..c50de1d5f8
--- /dev/null
+++ b/packages/ndla-icons/svg/common/Hamburger.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/ndla-modal/src/Modal.js b/packages/ndla-modal/src/Modal.js
index 0b4e10376a..2595884afe 100644
--- a/packages/ndla-modal/src/Modal.js
+++ b/packages/ndla-modal/src/Modal.js
@@ -454,6 +454,7 @@ class Modal extends React.Component {
this.containerRef = React.createRef();
this.scrollPosition = null;
this.el = null;
+ this.wasOpen = false;
this.uuid = uuid();
}
@@ -489,8 +490,9 @@ class Modal extends React.Component {
},
this.removedModal,
);
- } else if (this.state.animateIn && this.state.isOpen) {
+ } else if (this.state.animateIn && this.state.isOpen && !this.wasOpen) {
this.el = document.body.querySelector(`[data-modal='${this.uuid}']`);
+ this.wasOpen = true;
if (this.props.onOpen) {
this.props.onOpen();
}
@@ -531,6 +533,7 @@ class Modal extends React.Component {
removedModal() {
this.scrollPosition = 0;
+ this.wasOpen = false;
if (uuidList.indexOf(this.uuid) !== -1) {
noScroll(false, this.uuid);
uuidList.splice(uuidList.indexOf(this.uuid), 1);
diff --git a/packages/ndla-ui/src/TopicMenu/TopicMenuButton.jsx b/packages/ndla-ui/src/TopicMenu/TopicMenuButton.jsx
index 62c633662a..fe8efffffe 100644
--- a/packages/ndla-ui/src/TopicMenu/TopicMenuButton.jsx
+++ b/packages/ndla-ui/src/TopicMenu/TopicMenuButton.jsx
@@ -40,15 +40,20 @@ const style = css`
}
`;
-const TopicMenuButton = ({ ndlaFilm, children, ...rest }) => (
+const TopicMenuButton = ({ ndlaFilm, children, Icon, ...rest }) => (
);
TopicMenuButton.propTypes = {
children: PropTypes.node.isRequired,
ndlaFilm: PropTypes.bool,
+ Icon: PropTypes.node,
+};
+
+TopicMenuButton.defaultProps = {
+ Icon: ,
};
export default TopicMenuButton;
diff --git a/packages/ndla-ui/src/locale/messages-en.js b/packages/ndla-ui/src/locale/messages-en.js
index 72887131e0..710cc79f0f 100644
--- a/packages/ndla-ui/src/locale/messages-en.js
+++ b/packages/ndla-ui/src/locale/messages-en.js
@@ -479,6 +479,15 @@ const messages = {
newGroupTitle: 'What shall we call the new movie group?',
},
},
+ abTests: {
+ masthead: {
+ menu: {
+ topics: 'Topics',
+ overview: 'Overview',
+ subjectOverview: 'Subject overview',
+ },
+ },
+ },
};
export default messages;
diff --git a/packages/ndla-ui/src/locale/messages-nb.js b/packages/ndla-ui/src/locale/messages-nb.js
index 09af1a670e..4741b244d5 100644
--- a/packages/ndla-ui/src/locale/messages-nb.js
+++ b/packages/ndla-ui/src/locale/messages-nb.js
@@ -494,6 +494,15 @@ const messages = {
newGroupTitle: 'Hva skal gruppen hete?',
},
},
+ abTests: {
+ masthead: {
+ menu: {
+ topics: 'Emner',
+ overview: 'Oversikt',
+ subjectOverview: 'Fagoversikt',
+ },
+ },
+ },
};
export default messages;
diff --git a/packages/ndla-ui/src/locale/messages-nn.js b/packages/ndla-ui/src/locale/messages-nn.js
index 95a4a6c295..7f0391766f 100644
--- a/packages/ndla-ui/src/locale/messages-nn.js
+++ b/packages/ndla-ui/src/locale/messages-nn.js
@@ -478,6 +478,15 @@ const messages = {
newGroupTitle: 'Kva skal gruppen hete?',
},
},
+ abTests: {
+ masthead: {
+ menu: {
+ topics: 'Emner',
+ overview: 'Oversikt',
+ subjectOverview: 'Fagoversikt',
+ },
+ },
+ },
};
export default messages;