├── packages ├── ui │ ├── src │ │ ├── types │ │ │ ├── njsection.ts │ │ │ ├── global.d.ts │ │ │ ├── index.d.ts │ │ │ ├── ComponentName.d.ts │ │ │ ├── LibrarySettings.d.ts │ │ │ ├── ComponentSettings.d.ts │ │ │ └── vue.ts │ │ ├── styles.ts │ │ ├── plugin.js │ │ ├── components.js │ │ ├── index.ts │ │ ├── runtime │ │ │ ├── rich-text-renderer.js │ │ │ ├── init-store.js │ │ │ ├── lazysizes.js │ │ │ ├── components │ │ │ │ ├── organisms │ │ │ │ │ └── NjVideoBackground │ │ │ │ │ │ ├── _sub │ │ │ │ │ │ ├── VideoOverlay.vue │ │ │ │ │ │ ├── VideoPoster.vue │ │ │ │ │ │ └── VideoPlayer.vue │ │ │ │ │ │ ├── lib │ │ │ │ │ │ └── throttle.js │ │ │ │ │ │ ├── core │ │ │ │ │ │ ├── playerProps.js │ │ │ │ │ │ ├── props.js │ │ │ │ │ │ └── resize.js │ │ │ │ │ │ └── NjVideoBackground.vue │ │ │ │ ├── atoms │ │ │ │ │ ├── SbRichtext.vue │ │ │ │ │ ├── NjLogo.vue │ │ │ │ │ ├── NjSection.vue │ │ │ │ │ ├── NjBurger.vue │ │ │ │ │ ├── NjHeading.vue │ │ │ │ │ ├── SbImage.vue │ │ │ │ │ └── NjImage.vue │ │ │ │ └── molecules │ │ │ │ │ ├── NjSidebar │ │ │ │ │ ├── NjSidebarTitle.vue │ │ │ │ │ └── NjSidebar.vue │ │ │ │ │ ├── NjNav │ │ │ │ │ ├── NjNavItem.vue │ │ │ │ │ ├── NjNavItems.vue │ │ │ │ │ ├── NjNavDropdownItem.vue │ │ │ │ │ ├── NjSlideOverMenu.vue │ │ │ │ │ └── NjNav.vue │ │ │ │ │ ├── NjTabs │ │ │ │ │ └── NjTabs.vue │ │ │ │ │ └── SbGrid.vue │ │ │ ├── index.ts │ │ │ ├── components.ts │ │ │ ├── mixins │ │ │ │ └── nav-mixin.js │ │ │ ├── plugin.js │ │ │ ├── nav.js │ │ │ ├── sbutils.js │ │ │ └── base │ │ │ │ └── Component.js │ │ ├── css │ │ │ ├── components │ │ │ │ ├── richtext.css │ │ │ │ ├── image.css │ │ │ │ ├── sidebar.css │ │ │ │ ├── video-background.css │ │ │ │ └── burger.css │ │ │ └── nujek-ui.css │ │ └── module.ts │ ├── test │ │ ├── setup.ts │ │ ├── fixtures │ │ │ ├── assets │ │ │ │ └── tailwind.css │ │ │ ├── pages │ │ │ │ └── index.vue │ │ │ ├── components │ │ │ │ ├── BlokImage.vue │ │ │ │ └── BlokTextImage.vue │ │ │ └── nuxt.config.ts │ │ ├── e2e │ │ │ └── ssr.spec.ts │ │ ├── tsconfig.json │ │ ├── unit │ │ │ ├── NjSection.spec.ts │ │ │ ├── module.spec.ts │ │ │ └── ComponentClasses.spec.ts │ │ └── __mocks__ │ │ │ └── story.js │ ├── jest.config.js │ ├── tsconfig.json │ └── package.json ├── bundle │ ├── src │ │ ├── index.ts │ │ └── module.ts │ ├── test │ │ ├── setup.ts │ │ ├── fixtures │ │ │ ├── assets │ │ │ │ └── tailwind.css │ │ │ ├── nuxt.config.ts │ │ │ ├── pages │ │ │ │ ├── index.vue │ │ │ │ └── debug.vue │ │ │ └── components │ │ │ │ ├── bloks │ │ │ │ ├── BlokImage.vue │ │ │ │ └── BlokTextImage.vue │ │ │ │ └── content-types │ │ │ │ └── Landingpage.vue │ │ ├── tsconfig.json │ │ ├── __mocks__ │ │ │ ├── story.js │ │ │ └── story_debug.js │ │ ├── e2e │ │ │ └── blok.spec.ts │ │ └── unit │ │ │ └── module.spec.ts │ ├── babel.config.js │ ├── jest.config.js │ ├── tsconfig.json │ └── package.json ├── storyblok │ ├── test │ │ ├── fixtures │ │ │ ├── pages │ │ │ │ ├── no-content-type.vue │ │ │ │ ├── debug.vue │ │ │ │ └── index.vue │ │ │ ├── nuxt.config.ts │ │ │ └── components │ │ │ │ ├── bloks │ │ │ │ ├── BlokImage.vue │ │ │ │ └── BlokTextImage.vue │ │ │ │ └── content-types │ │ │ │ └── Landingpage.vue │ │ ├── setup.ts │ │ ├── tsconfig.json │ │ ├── unit │ │ │ └── module.spec.ts │ │ ├── e2e │ │ │ └── blok.spec.ts │ │ └── __mocks__ │ │ │ ├── story.js │ │ │ ├── story_debug.js │ │ │ └── story_miss_ct.js │ ├── src │ │ ├── index.ts │ │ ├── runtime │ │ │ ├── plugin.js │ │ │ ├── components │ │ │ │ ├── BlokDebugCodePreview.vue │ │ │ │ ├── SbQuery │ │ │ │ │ ├── InfinitePagination.vue │ │ │ │ │ ├── NumericPagination.vue │ │ │ │ │ └── SbQuery.vue │ │ │ │ ├── BlokDebugProps.vue │ │ │ │ ├── BlokDebugDetails.vue │ │ │ │ ├── Blok.vue │ │ │ │ ├── BlokDebug.vue │ │ │ │ └── BlokDebugContentType.vue │ │ │ └── images │ │ │ │ └── empty.svg │ │ └── module.ts │ ├── babel.config.js │ ├── jest.config.js │ ├── tsconfig.json │ └── package.json └── shared │ ├── src │ ├── index.ts │ └── utils │ │ ├── index.ts │ │ ├── object.ts │ │ └── cases.ts │ └── package.json ├── .npmrc ├── vercel.json ├── .eslintignore ├── images └── nujek.png ├── .eslintrc.js ├── .editorconfig ├── scripts ├── rollup.config.ts ├── utils.ts ├── test-e2e.ts ├── test-unit.ts ├── lint.ts ├── styles.js ├── packages.ts ├── clean.ts ├── build.ts ├── publish.ts └── compile-styles.js ├── tsconfig.json ├── .vscode ├── settings.json └── launch.json ├── LICENSE ├── .github └── workflows │ ├── e2e.yml │ └── unit.yml ├── package.json ├── README.md └── .gitignore /packages/ui/src/types/njsection.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/bundle/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './module' 2 | -------------------------------------------------------------------------------- /packages/storyblok/test/fixtures/pages/no-content-type.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/src/styles.ts: -------------------------------------------------------------------------------- 1 | import 'css/nujek-ui.css' 2 | -------------------------------------------------------------------------------- /packages/storyblok/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './module' 2 | -------------------------------------------------------------------------------- /packages/ui/src/plugin.js: -------------------------------------------------------------------------------- 1 | export * from './runtime/plugin' 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | shamefully-hoist=true -------------------------------------------------------------------------------- /packages/ui/src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import { } from '@nuxt/types' 2 | -------------------------------------------------------------------------------- /packages/ui/src/components.js: -------------------------------------------------------------------------------- 1 | export * from './runtime/components' 2 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "enabled": false 4 | } 5 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | packages/storyblok/src/runtime/plugin.js 2 | dist 3 | node_modules -------------------------------------------------------------------------------- /images/nujek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regenrek/nujek/HEAD/images/nujek.png -------------------------------------------------------------------------------- /packages/ui/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './module' 2 | export * from './types' 3 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils/cases' 2 | export * from './utils/object' 3 | -------------------------------------------------------------------------------- /packages/shared/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { forwardProps } from './object' 2 | export { toPascalCase } from './cases' 3 | -------------------------------------------------------------------------------- /packages/ui/test/setup.ts: -------------------------------------------------------------------------------- 1 | import consola from 'consola' 2 | 3 | consola.wrapAll() 4 | consola.mockTypes(() => jest.fn()) 5 | -------------------------------------------------------------------------------- /packages/bundle/test/setup.ts: -------------------------------------------------------------------------------- 1 | import consola from 'consola' 2 | 3 | consola.wrapAll() 4 | consola.mockTypes(() => jest.fn()) 5 | -------------------------------------------------------------------------------- /packages/storyblok/test/setup.ts: -------------------------------------------------------------------------------- 1 | import consola from 'consola' 2 | 3 | consola.wrapAll() 4 | consola.mockTypes(() => jest.fn()) 5 | -------------------------------------------------------------------------------- /packages/ui/test/fixtures/assets/tailwind.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; -------------------------------------------------------------------------------- /packages/bundle/test/fixtures/assets/tailwind.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; -------------------------------------------------------------------------------- /packages/ui/test/fixtures/pages/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /packages/bundle/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | return { 4 | presets: ['@babel/preset-env'] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/storyblok/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | return { 4 | presets: ['@babel/preset-env'] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/ui/src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import './global' 2 | 3 | export * from './ComponentName' 4 | export * from './ComponentSettings' 5 | export * from './LibrarySettings' 6 | -------------------------------------------------------------------------------- /packages/bundle/test/fixtures/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import nujekBundleModule from '../../src/module' 2 | 3 | module.exports = { 4 | buildModules: [ 5 | nujekBundleModule 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/rich-text-renderer.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRichTextRenderer from '@marvr/storyblok-rich-text-vue-renderer' 3 | 4 | Vue.use(VueRichTextRenderer) 5 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/plugin.js: -------------------------------------------------------------------------------- 1 | export default (ctx, inject) => { 2 | const debug = <%=JSON.stringify(options.debug || false) %> 3 | inject('storyblokNujek', { debug }) 4 | } -------------------------------------------------------------------------------- /packages/ui/src/runtime/init-store.js: -------------------------------------------------------------------------------- 1 | import { registerNavStore } from './nav' 2 | 3 | export default (context) => { 4 | const { store } = context 5 | registerNavStore(store) 6 | } 7 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/lazysizes.js: -------------------------------------------------------------------------------- 1 | import lazySizes from 'lazysizes' 2 | import 'lazysizes/plugins/blur-up/ls.blur-up' 3 | 4 | lazySizes.cfg.blurupMode = 'auto' 5 | 6 | export default lazySizes 7 | -------------------------------------------------------------------------------- /packages/ui/src/css/components/richtext.css: -------------------------------------------------------------------------------- 1 | /* remove p block inside list items */ 2 | .richtext li > p:first-child { 3 | display: inline; 4 | margin: 0; 5 | } 6 | .richtext.milestone-text p { 7 | margin-top: 0; 8 | } -------------------------------------------------------------------------------- /packages/ui/src/css/nujek-ui.css: -------------------------------------------------------------------------------- 1 | @import './components/burger.css'; 2 | @import './components/sidebar.css'; 3 | @import './components/image.css'; 4 | @import './components/richtext.css'; 5 | @import './components/video-background.css'; 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | '@nuxtjs', 5 | '@nuxtjs/eslint-config-typescript' 6 | ], 7 | rules: { 8 | '@typescript-eslint/no-unused-vars': ['warn'], 9 | 'node/no-callback-literal': ['warn'] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | end_of_line = auto 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/ui/src/types/ComponentName.d.ts: -------------------------------------------------------------------------------- 1 | type ComponentName = 2 | | 'NjSection' 3 | | 'NjBurger' 4 | | 'NjImage' 5 | | 'SbImage' 6 | | 'SbRichtext' 7 | | 'NjNav' 8 | | 'NjSidebar' 9 | | 'SbGrid' 10 | | 'NjVideoBackground' 11 | 12 | export default ComponentName 13 | -------------------------------------------------------------------------------- /packages/ui/src/types/LibrarySettings.d.ts: -------------------------------------------------------------------------------- 1 | import ComponentSettings, { VTComponent } from './ComponentSettings' 2 | 3 | type LibrarySettings = 4 | | { 5 | [key: string]: ComponentSettings | VTComponent 6 | } 7 | | undefined 8 | 9 | export default LibrarySettings 10 | -------------------------------------------------------------------------------- /packages/ui/test/fixtures/components/BlokImage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /packages/bundle/test/fixtures/pages/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /packages/storyblok/test/fixtures/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import nujekStoryblokModule from '../../src/module' 2 | 3 | module.exports = { 4 | components: true, 5 | buildModules: [ 6 | '@nuxtjs/composition-api/module', 7 | '@nuxtjs/tailwindcss', 8 | nujekStoryblokModule 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ui/test/fixtures/components/BlokTextImage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /packages/bundle/test/fixtures/pages/debug.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /scripts/rollup.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import postcss from 'rollup-plugin-postcss' 3 | 4 | export default { 5 | input: 'packages/ui/src/styles.ts', 6 | plugins: [ 7 | 8 | postcss({ 9 | extract: path.resolve('packages/ui/dist/nujek-ui.css') 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/storyblok/test/fixtures/pages/debug.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /packages/storyblok/test/fixtures/pages/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import fg from 'fast-glob' 2 | 3 | export async function listFunctions(dir: string, ignore: string[] = []) { 4 | return ( 5 | await fg('*', { 6 | onlyDirectories: true, 7 | cwd: dir, 8 | ignore: ['_*', 'dist', 'node_modules', ...ignore] 9 | }) 10 | ).sort() 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/_sub/VideoOverlay.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /packages/ui/test/fixtures/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import nujekUiModule from '../../src/module' 2 | 3 | module.exports = { 4 | buildModules: [ 5 | '@nuxtjs/composition-api/module', 6 | '@nuxtjs/tailwindcss', 7 | '@nujek/storyblok', 8 | nujekUiModule 9 | ], 10 | tailwindcss: { 11 | jit: true, 12 | exposeConfig: true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/bundle/test/fixtures/components/bloks/BlokImage.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /packages/storyblok/test/fixtures/components/bloks/BlokImage.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /packages/bundle/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@nuxt/test-utils', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | }, 6 | transformIgnorePatterns: [ 7 | '/node_modules/(?!@nujek)' 8 | ], 9 | collectCoverage: true, 10 | collectCoverageFrom: [ 11 | 'src/**/*.{js,vue}' 12 | ], 13 | moduleNameMapper: { 14 | '~/(.*)': '/src/$1' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/shared/src/utils/object.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Clears out all undefined properties from an object. 3 | * @param {Object} props 4 | * @returns {Object} Sanitized object with defined values. 5 | */ 6 | export function forwardProps (props) { 7 | const pure = {} 8 | for (const prop in props) { 9 | if (props[prop] !== undefined) { 10 | pure[prop] = props[prop] 11 | } 12 | } 13 | return pure 14 | } 15 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/BlokDebugCodePreview.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | -------------------------------------------------------------------------------- /packages/ui/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@nuxt/test-utils', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | }, 6 | collectCoverage: true, 7 | collectCoverageFrom: [ 8 | 'src/**/*.{js,vue}' 9 | ], 10 | moduleNameMapper: { 11 | '~nujek-ui/(.*)': '/src/runtime/$1', 12 | '~nujek-ui': '/src/runtime/index.ts', 13 | '~/(.*)': '/src/$1' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "moduleResolution": "Node", 5 | "esModuleInterop": true, 6 | "resolveJsonModule": true, 7 | "types": ["node", "jest"], 8 | "allowJs": true 9 | }, 10 | "include": ["packages"], 11 | "exclude": [ 12 | "node_modules", 13 | "**/**/*.test.ts", 14 | "**/**/*.stories.tsx", 15 | "**/**/*.md", 16 | "packages/docs" 17 | ] 18 | } -------------------------------------------------------------------------------- /scripts/test-e2e.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import path from 'path' 3 | import { activePackages } from './packages' 4 | 5 | run() 6 | 7 | function run () { 8 | for (const { name, tests } of activePackages) { 9 | if (tests) { 10 | execSync('yarn test:e2e', { 11 | stdio: 'inherit', 12 | cwd: path.join('packages', name) 13 | }) 14 | } 15 | } 16 | } 17 | 18 | export { run as testE2e } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "emeraldwalk.runonsave": { 3 | "commands": [ 4 | { 5 | "match": "\\.vue$", 6 | "cmd": "cd ../nujek-landingpage && yarn unj" 7 | } 8 | ] 9 | }, 10 | "jest.jestCommandLine": "${workspaceFolder}/node_modules/jest/bin/jest.j", 11 | "favorites.resources": [ 12 | "node_modules/@nujek", 13 | "packages/storyblok" 14 | ] 15 | } -------------------------------------------------------------------------------- /scripts/test-unit.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import path from 'path' 3 | import { activePackages } from './packages' 4 | 5 | run() 6 | 7 | function run () { 8 | for (const { name, tests } of activePackages) { 9 | if (tests) { 10 | execSync('yarn test:unit', { 11 | stdio: 'inherit', 12 | cwd: path.join('packages', name) 13 | }) 14 | } 15 | } 16 | } 17 | 18 | export { run as testUnit } 19 | -------------------------------------------------------------------------------- /packages/ui/test/e2e/ssr.spec.ts: -------------------------------------------------------------------------------- 1 | import { setupTest, createPage } from '@nuxt/test-utils' 2 | 3 | describe('browser (ssr:true)', () => { 4 | setupTest({ 5 | browser: true, 6 | rootDir: 'test/fixtures', 7 | setupTimeout: 120000 8 | }) 9 | 10 | test('should start', async () => { 11 | const page = await createPage('/') 12 | const html = await page.innerHTML('body') 13 | 14 | expect(html).toContain('Todo') 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/ui/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "DOM" 6 | ], 7 | "paths": { 8 | "~nujek-ui/*": [ 9 | "src/runtime/*" 10 | ], 11 | "~nujek-ui": [ 12 | "src/runtime" 13 | ], 14 | "~/*": [ 15 | "src/*" 16 | ] 17 | } 18 | }, 19 | } -------------------------------------------------------------------------------- /packages/bundle/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "DOM" 6 | ], 7 | "paths": { 8 | "~nujek-ui/*": [ 9 | "src/runtime/*" 10 | ], 11 | "~nujek-ui": [ 12 | "src/runtime" 13 | ], 14 | "~/*": [ 15 | "src/*" 16 | ] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /packages/bundle/test/fixtures/components/bloks/BlokTextImage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | -------------------------------------------------------------------------------- /packages/storyblok/test/fixtures/components/bloks/BlokTextImage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/_sub/VideoPoster.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | -------------------------------------------------------------------------------- /packages/shared/src/utils/cases.ts: -------------------------------------------------------------------------------- 1 | const clearLodashAndUpper = (value) => { 2 | return value.replace(/_/, '').toUpperCase() 3 | } 4 | 5 | const clearHyphenAndUpper = (value) => { 6 | return value.replace(/-/, '').toUpperCase() 7 | } 8 | 9 | export const toPascalCase = (value) => { 10 | // kebab-case 11 | value = value.replace(/(^\w|-\w)/g, clearHyphenAndUpper) 12 | // snake_case 13 | value = value.replace(/(^\w|_\w)/g, clearLodashAndUpper) 14 | return value 15 | } 16 | -------------------------------------------------------------------------------- /packages/bundle/test/fixtures/components/content-types/Landingpage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /scripts/lint.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import path from 'path' 3 | import consola from 'consola' 4 | import { activePackages } from './packages' 5 | 6 | run() 7 | 8 | function run () { 9 | for (const { name } of activePackages) { 10 | execSync('yarn lint', { 11 | stdio: 'inherit', 12 | cwd: path.join('packages', name) 13 | }) 14 | 15 | consola.success(`Package linted: @nujek/${name}`) 16 | } 17 | } 18 | 19 | export { run as lint } 20 | -------------------------------------------------------------------------------- /packages/storyblok/test/fixtures/components/content-types/Landingpage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /scripts/styles.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import builder from './compile-styles' 3 | const fs = require('fs') 4 | const uidir = resolve(__dirname, '../packages/ui/') 5 | 6 | const input = fs.readFileSync(resolve(uidir, 'src/css/nujek-ui.css'), 'utf8') 7 | 8 | builder(input, { 9 | from: resolve(uidir, 'src/css/nujek-ui.css'), 10 | to: resolve(uidir, 'dist/nujek-ui.css'), 11 | minify: false 12 | }).then((result) => { 13 | fs.writeFileSync(resolve(uidir, 'dist/nujek-ui.css'), result.css) 14 | }) 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "node", 3 | "request": "launch", 4 | "name": "Jest Tests", 5 | "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", 6 | "args": [ 7 | "-i" 8 | ], 9 | "preLaunchTask": "build", 10 | "internalConsoleOptions": "openOnSessionStart", 11 | "outFiles": [ 12 | "${workspaceRoot}/dist/**/*" 13 | ], 14 | "env": { 15 | "VUE_CLI_BABEL_TARGET_NODE": "true", 16 | "VUE_CLI_BABEL_TRANSPILE_MODULES": "true" 17 | } 18 | } -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/atoms/SbRichtext.vue: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 24 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/lib/throttle.js: -------------------------------------------------------------------------------- 1 | // Based on https://stackoverflow.com/questions/27078285/simple-throttle-in-js 2 | export default (callback, limit) => { 3 | let timeoutHandler = 'null' 4 | 5 | return (...args) => { 6 | if (timeoutHandler === 'null') { 7 | timeoutHandler = setTimeout(() => { 8 | // eslint-disable-next-line node/no-callback-literal 9 | callback(...args) 10 | timeoutHandler = 'null' 11 | }, limit) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/storyblok/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "DOM" 6 | ], 7 | "paths": { 8 | "~nujek-ui/*": [ 9 | "src/runtime/*" 10 | ], 11 | "~nujek-ui": [ 12 | "src/runtime" 13 | ], 14 | "~/*": [ 15 | "src/*" 16 | ] 17 | } 18 | }, 19 | "include": [ 20 | "node_modules/@nujek/blok/src/module.js" 21 | ] 22 | } -------------------------------------------------------------------------------- /packages/storyblok/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@nuxt/test-utils', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | }, 6 | transformIgnorePatterns: [ 7 | '/node_modules/(?!@nujek)' 8 | ], 9 | collectCoverage: true, 10 | collectCoverageFrom: [ 11 | 'src/**/*.{js,vue}', 12 | '!src/runtime/plugin.js' 13 | ], 14 | moduleNameMapper: { 15 | '~nujek-ui/(.*)': '/src/runtime/$1', 16 | '~nujek-ui': '/src/runtime/index.ts', 17 | '~/(.*)': '/src/$1' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /scripts/packages.ts: -------------------------------------------------------------------------------- 1 | export interface PackageManifest { 2 | name?: string; 3 | deprecated?: boolean; 4 | disabled?: boolean; 5 | tests?: boolean; 6 | } 7 | 8 | export const packages: PackageManifest[] = [ 9 | { 10 | name: 'shared', 11 | tests: false 12 | }, 13 | { 14 | name: 'ui', 15 | tests: true 16 | }, 17 | { 18 | name: 'storyblok', 19 | tests: true 20 | }, 21 | { 22 | name: 'bundle', 23 | tests: true 24 | } 25 | ] 26 | 27 | export const activePackages = packages.filter(i => (!i.deprecated || i.disabled)) 28 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjSidebar/NjSidebarTitle.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /packages/ui/src/css/components/image.css: -------------------------------------------------------------------------------- 1 | .blur-up { 2 | /* filter: blur(20px); */ 3 | /* transition: filter 400ms; */ 4 | opacity: 0; 5 | transition: opacity 400ms; 6 | } 7 | 8 | .blur-up.lazyloaded { 9 | /* filter: blur(0); */ 10 | opacity: 1; 11 | } 12 | 13 | figure.has-aspect-ratio { 14 | position: relative; 15 | overflow: hidden; 16 | display: block; 17 | margin: 0; 18 | img { 19 | position: absolute; 20 | top: 50%; 21 | left: 50%; 22 | width: 100%; 23 | height: 100%; 24 | transform: translate(-50%, -50%); 25 | } 26 | } -------------------------------------------------------------------------------- /packages/bundle/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": ".", 5 | "moduleResolution": "Node", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "noImplicitAny": false, 9 | "allowJs": true, 10 | "types": [ 11 | "node", 12 | "jest" 13 | ], 14 | "paths": { 15 | "~nujek-ui/*": [ 16 | "src/runtime/*" 17 | ], 18 | "~nujek-ui": [ 19 | "src/runtime" 20 | ] 21 | } 22 | }, 23 | "exclude": [ 24 | "dist" 25 | ] 26 | } -------------------------------------------------------------------------------- /packages/storyblok/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": ".", 5 | "moduleResolution": "Node", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "noImplicitAny": false, 9 | "allowJs": true, 10 | "types": [ 11 | "node", 12 | "jest" 13 | ], 14 | "paths": { 15 | "~nujek-ui/*": [ 16 | "src/runtime/*" 17 | ], 18 | "~nujek-ui": [ 19 | "src/runtime" 20 | ] 21 | } 22 | }, 23 | "exclude": [ 24 | "dist" 25 | ] 26 | } -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/core/playerProps.js: -------------------------------------------------------------------------------- 1 | export default { 2 | src: { 3 | type: String, 4 | required: true 5 | }, 6 | muted: { 7 | type: Boolean, 8 | default: true 9 | }, 10 | loop: { 11 | type: Boolean, 12 | default: true 13 | }, 14 | preload: { 15 | type: String, 16 | default: 'auto' 17 | }, 18 | objectFit: { 19 | type: String, 20 | default: 'cover' 21 | }, 22 | playsWhen: { 23 | type: String, 24 | default: 'canplay', 25 | note: 'Google HTML Video Events' 26 | }, 27 | playbackRate: { 28 | type: Number, 29 | default: 1.0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": ".", 5 | "moduleResolution": "Node", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | // "noImplicitAny": false, 9 | // "allowJs": true, 10 | "types": [ 11 | "node", 12 | "jest" 13 | ], 14 | "paths": { 15 | "~nujek-ui/*": [ 16 | "src/runtime/*" 17 | ], 18 | "~nujek-ui": [ 19 | "src/runtime" 20 | ] 21 | } 22 | }, 23 | "exclude": [ 24 | "dist" 25 | ] 26 | } -------------------------------------------------------------------------------- /scripts/clean.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import path from 'path' 3 | import consola from 'consola' 4 | import { activePackages } from './packages' 5 | 6 | execSync('yarn clean') 7 | 8 | for (const { name } of activePackages) { 9 | execSync(`rm -rf packages/${name}/node_modules`, { 10 | stdio: 'inherit', 11 | cwd: path.join('packages', name) 12 | }) 13 | 14 | execSync(`rm -rf packages/${name}/yarn.lock`, { 15 | stdio: 'inherit', 16 | cwd: path.join('packages', name) 17 | }) 18 | } 19 | 20 | // build 21 | execSync('rm -rf node_modules', { 22 | stdio: 'inherit' 23 | }) 24 | 25 | consola.success('Packages cleared') 26 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import path from 'path' 3 | import consola from 'consola' 4 | import { activePackages } from './packages' 5 | 6 | run() 7 | 8 | function run () { 9 | execSync('yarn clean', { 10 | stdio: 'inherit' 11 | }) 12 | 13 | // build 14 | execSync('siroc build', { 15 | stdio: 'inherit' 16 | }) 17 | 18 | for (const { name } of activePackages) { 19 | execSync('mkdist --src ./src/runtime --dist ./dist/runtime', { 20 | stdio: 'inherit', 21 | cwd: path.join('packages', name) 22 | }) 23 | 24 | consola.success(`Package Built: @nujek/${name}`) 25 | } 26 | } 27 | 28 | export { run as build } 29 | -------------------------------------------------------------------------------- /packages/ui/src/types/ComponentSettings.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/named 2 | import Vue, { PropOptions } from 'vue' 3 | import ComponentName from './ComponentName' 4 | 5 | export type CustomProp = { 6 | [key: string]: 7 | | undefined 8 | | string 9 | | number 10 | | boolean 11 | | Array 12 | | (() => CustomProp) 13 | | CustomProp 14 | } 15 | 16 | export type VTComponent = typeof Vue & { 17 | options?: { 18 | props?: { 19 | [key: string]: PropOptions 20 | }, 21 | name: ComponentName 22 | } 23 | } 24 | 25 | type ComponentSettings = { 26 | component: VTComponent, 27 | props: CustomProp 28 | } 29 | 30 | export default ComponentSettings 31 | -------------------------------------------------------------------------------- /packages/storyblok/test/unit/module.spec.ts: -------------------------------------------------------------------------------- 1 | import { setupTest, expectModuleToBeCalledWith } from '@nuxt/test-utils' 2 | 3 | describe('module', () => { 4 | setupTest({ 5 | rootDir: 'test/fixtures', 6 | server: true, 7 | config: { 8 | nujekStoryblok: { 9 | storyblokConfig: { 10 | accessToken: 'notexistant', 11 | cacheProvider: 'memory' 12 | }, 13 | debug: false 14 | } 15 | }, 16 | setupTimeout: 120000 17 | }) 18 | 19 | it('injects module storyblok-nuxt', () => { 20 | expectModuleToBeCalledWith('requireModule', ['storyblok-nuxt', { 21 | accessToken: 'notexistant', 22 | cacheProvider: 'memory' 23 | }]) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/index.ts: -------------------------------------------------------------------------------- 1 | import NjSection from './components/atoms/NjSection.vue' 2 | import NjBurger from './components/atoms/NjBurger.vue' 3 | import NjImage from './components/atoms/NjImage.vue' 4 | import SbImage from './components/atoms/SbImage.vue' 5 | import SbRichtext from './components/atoms/SbRichtext.vue' 6 | import NjNav from './components/molecules/NjNav/NjNav.vue' 7 | import NjSidebar from './components/molecules/NjSidebar/NjSidebar.vue' 8 | import SbGrid from './components/molecules/SbGrid.vue' 9 | import NjVideoBackground from './components/organisms/NjVideoBackground/NjVideoBackground.vue' 10 | 11 | export { 12 | NjSection, 13 | NjBurger, 14 | NjImage, 15 | SbImage, 16 | SbRichtext, 17 | NjNav, 18 | NjSidebar, 19 | SbGrid, 20 | NjVideoBackground 21 | } 22 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components.ts: -------------------------------------------------------------------------------- 1 | import NjSection from './components/atoms/NjSection.vue' 2 | import NjBurger from './components/atoms/NjBurger.vue' 3 | import NjImage from './components/atoms/NjImage.vue' 4 | import SbImage from './components/atoms/SbImage.vue' 5 | import SbRichtext from './components/atoms/SbRichtext.vue' 6 | import NjNav from './components/molecules/NjNav/NjNav.vue' 7 | import NjSidebar from './components/molecules/NjSidebar/NjSidebar.vue' 8 | import SbGrid from './components/molecules/SbGrid.vue' 9 | import NjVideoBackground from './components/organisms/NjVideoBackground/NjVideoBackground.vue' 10 | 11 | export { 12 | NjSection, 13 | NjBurger, 14 | NjImage, 15 | SbImage, 16 | SbRichtext, 17 | NjNav, 18 | NjSidebar, 19 | SbGrid, 20 | NjVideoBackground 21 | } 22 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjNav/NjNavItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 35 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/mixins/nav-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState, mapGetters, mapActions } from 'vuex' 2 | 3 | export default { 4 | computed: { 5 | ...mapState({ 6 | navOpen: state => state.nav.navOpen 7 | }), 8 | ...mapGetters({ 9 | mainNavItems: 'nav/main', 10 | mainNavSubItems: 'nav/sub', 11 | asideNavItems: 'nav/aside', 12 | mobileNavItems: 'nav/mobile', 13 | mobileSubNavItems: 'nav/mobileSub', 14 | footerNavItems: 'nav/footer' 15 | }), 16 | isOpenBurger: { 17 | get () { 18 | return this.navOpen 19 | }, 20 | set (val) { 21 | this.$store.dispatch('nav/set', val) 22 | } 23 | } 24 | }, 25 | methods: { 26 | ...mapActions({ 27 | toggleSidebar: 'nav/toggle', 28 | closeSidebar: 'nav/close' 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/SbQuery/InfinitePagination.vue: -------------------------------------------------------------------------------- 1 | 2 | en: 3 | LoadMore: "Load more" 4 | de: 5 | LoadMore: "Mehr laden" 6 | 7 | 8 | 21 | 22 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /packages/ui/src/css/components/sidebar.css: -------------------------------------------------------------------------------- 1 | @media all and (max-width: 400px) { 2 | .sidenav { 3 | width: 100% !important; 4 | } 5 | } 6 | 7 | .slide-enter-active, 8 | .slide-leave-active { 9 | transition: transform 0.2s ease; 10 | } 11 | 12 | .slide-enter, 13 | .slide-leave-to { 14 | transform: translateX(-100%); 15 | transition: all 150ms ease-in 0s; 16 | } 17 | 18 | .backdrop-enter-active { 19 | transition: all 150ms ease-out; 20 | } 21 | 22 | .backdrop-leave-active { 23 | transition: all 150ms ease-in; 24 | } 25 | 26 | .backdrop-enter, 27 | .backdrop-leave-to { 28 | opacity: 0; 29 | } 30 | 31 | .sidebar-items > li { 32 | > a { 33 | position: relative; 34 | &:after { 35 | content: ''; 36 | position: absolute; 37 | bottom: 2px; 38 | height: 2px; 39 | left: 0; 40 | right: 0; 41 | width: 100%; 42 | } 43 | } 44 | > ul > li > a { 45 | font-size: 0.9em; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nujek/shared", 3 | "version": "0.0.126", 4 | "description": "Nujek - Shared Components", 5 | "keywords": [ 6 | "nuxt", 7 | "nuxtjs", 8 | "nuxt ui", 9 | "nuxtjs ui", 10 | "nuxt storyblok", 11 | "nuxtjs storyblok" 12 | ], 13 | "homepage": "https://github.com/regenrek/nujek#readme", 14 | "bugs": { 15 | "url": "https://github.com/regenrek/nujek/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/regenrek/nujek.git" 20 | }, 21 | "license": "MIT", 22 | "author": "Kevin Regenrek ", 23 | "main": "dist/index.js", 24 | "types": "dist/types.d.ts", 25 | "files": [ 26 | "dist/*" 27 | ], 28 | "scripts": { 29 | "lint": "eslint --ext .ts --ext .vue --ext .js .", 30 | "test": "yarn lint && yarn jest --forceExit", 31 | "test:e2e": "jest test/e2e --forceExit", 32 | "test:unit": "jest test/unit --forceExit" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/atoms/NjLogo.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to deal 4 | in the Software without restriction, including without limitation the rights 5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import path from 'path' 3 | import consola from 'consola' 4 | import { activePackages } from './packages' 5 | import { build } from './build' 6 | 7 | const args = process.argv.slice(2) 8 | const releaseOnly = args.includes('--release-only') 9 | run() 10 | 11 | function releasePackage (name, releaseOnly) { 12 | execSync(`bumpp patch --commit --push --tag nujek-${name}@`, { 13 | stdio: 'inherit', 14 | cwd: path.join('packages', name) 15 | }) 16 | 17 | if (!releaseOnly) { 18 | execSync('yarn publish --access public', { 19 | stdio: 'inherit', 20 | cwd: path.join('packages', name) 21 | }) 22 | } 23 | 24 | consola.success(`Package Published: @nujek/${name}`) 25 | } 26 | 27 | function run () { 28 | build() 29 | 30 | for (const { name } of activePackages) { 31 | // specific 32 | if (args.includes(name)) { 33 | releasePackage(name, releaseOnly) 34 | } 35 | 36 | // all 37 | if (args.length === 0) { 38 | releasePackage(name, releaseOnly) 39 | } 40 | } 41 | } 42 | 43 | export { run as publish } 44 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/atoms/NjSection.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 44 | -------------------------------------------------------------------------------- /packages/ui/src/types/vue.ts: -------------------------------------------------------------------------------- 1 | import type Vue from 'vue' 2 | import type { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options' 3 | import type { ExtendedVue, VueConstructor } from 'vue/types/vue' 4 | 5 | export interface DefineMixin { 6 | (options?: ThisTypedComponentOptionsWithRecordProps): Data & Methods & Computed & Props & VueConstructor 7 | } 8 | 9 | export interface DefineComponentWithMixin { 10 | // this is currently a hack - ideally we wouldn't need to duplicate this for multiple mixins 11 | , Mixin2 extends Record>(options?: ThisTypedComponentOptionsWithRecordProps>, Methods, Computed, Props> & { mixins: [Mixin1, Mixin2] }): ExtendedVue; 12 | >(options?: ThisTypedComponentOptionsWithRecordProps>, Methods, Computed, Props> & { mixins: Mixin[] }): ExtendedVue; 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test-e2e: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | node-version: [14.x] 18 | os: [ubuntu-latest] 19 | fail-fast: false 20 | 21 | steps: 22 | - uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node }} 25 | 26 | - name: checkout 27 | uses: actions/checkout@master 28 | 29 | - name: Get yarn cache directory path 30 | id: yarn-cache-dir-path 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | 33 | - uses: actions/cache@v2 34 | id: yarn-cache # check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 35 | with: 36 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 37 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | ${{ runner.os }}-yarn- 40 | 41 | - name: Install dependencies 42 | run: yarn 43 | 44 | - name: Test 45 | run: yarn test:e2e 46 | 47 | - name: Coverage 48 | uses: codecov/codecov-action@v1 -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/BlokDebugProps.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /.github/workflows/unit.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test-unit: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | node-version: [14.x] 18 | os: [ubuntu-latest] 19 | fail-fast: false 20 | 21 | steps: 22 | - uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node }} 25 | 26 | - name: checkout 27 | uses: actions/checkout@master 28 | 29 | - name: Get yarn cache directory path 30 | id: yarn-cache-dir-path 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | 33 | - uses: actions/cache@v2 34 | id: yarn-cache # check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 35 | with: 36 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 37 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | ${{ runner.os }}-yarn- 40 | 41 | - name: Install dependencies 42 | run: yarn install 43 | 44 | - name: Lint 45 | run: yarn lint 46 | 47 | - name: Test 48 | run: yarn test:unit 49 | 50 | - name: Coverage 51 | uses: codecov/codecov-action@v1 -------------------------------------------------------------------------------- /packages/bundle/src/module.ts: -------------------------------------------------------------------------------- 1 | import defu from 'defu' 2 | 3 | import type { Module } from '@nuxt/types' 4 | 5 | const nujekBundleModule: Module = async function storyblokModule (moduleOptions) { 6 | const defaults: any = { 7 | nujekStoryblok: {}, 8 | nujekUi: {}, 9 | tailwindcss: {} 10 | } 11 | 12 | const { nuxt, requireModule } = this 13 | const options: any = defu(moduleOptions, { withConsole: !!nuxt.options.withConsole }) 14 | const withConsole = { ...(options.withConsole ? { withConsole: options.withConsole } : null) } 15 | const nujekStoryblokOptions: any = defu(moduleOptions.nujekStoryblok, nuxt.options.nujekStoryblok, defaults.nujekStoryblok) 16 | const nujekUiOptions: any = defu(moduleOptions.nujekUi, nuxt.options.nujekUi, defaults.nujekUi) 17 | const tailwindcssOptions: any = defu(moduleOptions.tailwindcss, nuxt.options.tailwindcss, defaults.tailwindcss) 18 | 19 | await requireModule('@nuxtjs/composition-api/module') 20 | await requireModule('@nuxtjs/tailwindcss', tailwindcssOptions) 21 | await requireModule('@nujek/storyblok', defu(nujekStoryblokOptions, withConsole)) 22 | await requireModule('@nujek/ui', defu(nujekUiOptions, withConsole)) 23 | } 24 | 25 | export default nujekBundleModule 26 | 27 | // eslint-disable-next-line 28 | // @ts-ignore 29 | nujekBundleModule.meta = require('../package.json') 30 | -------------------------------------------------------------------------------- /packages/ui/test/unit/NjSection.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import { shallowMount } from '@vue/test-utils' 5 | 6 | import NjSection from '~/runtime/components/atoms/NjSection.vue' 7 | 8 | describe('NjSection', () => { 9 | it('it renders njsection', () => { 10 | const wrapper = shallowMount(NjSection) 11 | 12 | expect(wrapper.get('div')).toBeTruthy() 13 | }) 14 | 15 | it('default to div tag', () => { 16 | const wrapper = shallowMount(NjSection) 17 | 18 | expect(wrapper.vm.$el.tagName).toBe('DIV') 19 | }) 20 | 21 | it('it renders the default slot content', () => { 22 | const wrapper = shallowMount(NjSection, { 23 | slots: { 24 | default: 'lorem ipsum' 25 | } 26 | }) 27 | 28 | expect(wrapper.vm.$el.children[0].innerHTML).toBe('lorem ipsum') 29 | }) 30 | 31 | it('it will return the override classes', () => { 32 | const wrapper = shallowMount(NjSection, { 33 | propsData: { 34 | fixedClasses: { 35 | wrapper: 'bg-red-200', 36 | container: 'bg-blue-100' 37 | }, 38 | variant: 'boxed' 39 | } 40 | }) 41 | 42 | expect(wrapper.classes().sort()).toEqual(['bg-red-200', 'flex', 'justify-center']) 43 | expect( 44 | wrapper.find('div > div').classes().sort()) 45 | .toEqual(['bg-blue-100', 'md:px-6', 'mx-auto', 'w-full', 'xl:px-8']) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/plugin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export const getConfig = (component, props) => { 4 | const componentProps = component?.options?.props 5 | 6 | if (!props || !componentProps) { 7 | return component 8 | } 9 | 10 | const customProps = {} 11 | 12 | Object.keys(props).forEach((customPropName) => { 13 | const defaultProp = componentProps[customPropName] 14 | 15 | if (!defaultProp) { 16 | return 17 | } 18 | const newDefaultValue = props[customPropName] 19 | customProps[customPropName] = newDefaultValue 20 | }) 21 | 22 | return customProps 23 | } 24 | 25 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 26 | const install = function installNujek (vueInstance, settings) { 27 | Vue.mixin({ 28 | // Dependency injection forces us to explicitly require that function 29 | provide: { 30 | $nujekConfig (componentName) { 31 | const componentSettings = settings[componentName || this.$options.name] 32 | if (componentSettings) { 33 | const { component, props } = componentSettings 34 | return getConfig(component, props) 35 | } 36 | } 37 | }, 38 | computed: {} 39 | }) 40 | } 41 | 42 | // Create module definition for Vue.use() 43 | const plugin = { 44 | install 45 | } 46 | 47 | // Default export is library as a whole, registered via Vue.use() 48 | export default plugin 49 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjSidebar/NjSidebar.vue: -------------------------------------------------------------------------------- 1 | 19 | 52 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/BlokDebugDetails.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /packages/ui/src/css/components/video-background.css: -------------------------------------------------------------------------------- 1 | .nj-video-background { 2 | background: none; 3 | position: relative; 4 | width: 100%; 5 | overflow: hidden; 6 | .videobg-content { 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | width: 100%; 11 | height: 100%; 12 | } 13 | .video-overlay { 14 | height: 100%; 15 | width: 100%; 16 | top: 0; 17 | left: 0; 18 | position: absolute; 19 | background-image: linear-gradient( 20 | to left top, 21 | #3a7294, 22 | #306687, 23 | #26597a, 24 | #1c4e6d, 25 | #114260, 26 | #0b3b59, 27 | #053552, 28 | #002e4b, 29 | #002a47, 30 | #002743, 31 | #00233f, 32 | #00203b 33 | ); 34 | opacity: 0.78; 35 | } 36 | .video-wrapper { 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | width: 100%; 41 | position: absolute; 42 | height: 100%; 43 | position: absolute; 44 | overflow: hidden; 45 | z-index: 0; 46 | video { 47 | visibility: visible; 48 | pointer-events: none; 49 | position: absolute; 50 | top: 50%; 51 | left: 50%; 52 | transform: translate(-50%, -50%); 53 | height: 100%; 54 | width: 100%; 55 | } 56 | } 57 | .video-buffering { 58 | width: 100%; 59 | overflow: hidden; 60 | background-size: cover; 61 | background-position: center; 62 | height: 100%; 63 | top: 0; 64 | left: 0; 65 | position: absolute; 66 | } 67 | } -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/NjVideoBackground.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 62 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/atoms/NjBurger.vue: -------------------------------------------------------------------------------- 1 | 21 | 60 | -------------------------------------------------------------------------------- /packages/ui/src/css/components/burger.css: -------------------------------------------------------------------------------- 1 | .nj-burger { 2 | --burger-x: 9px; 3 | --burger-size: 50px; 4 | --burger-thickness: 3px; 5 | } 6 | 7 | /* default */ 8 | .nj-burger > button { 9 | height: var(--burger-size); 10 | width: var(--burger-size); 11 | transition: transform 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); 12 | > span { 13 | position: absolute; 14 | top: 50%; 15 | right: var(--burger-x); 16 | left: var(--burger-x); 17 | height: var(--burger-thickness); 18 | width: auto; 19 | margin-top: -1px; 20 | transition: transform 0.6s cubic-bezier(0.165, 0.84, 0.44, 1), 21 | opacity 0.3s cubic-bezier(0.165, 0.84, 0.44, 1), 22 | background-color 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); 23 | } 24 | /* &.active > span { 25 | background-color: var(--burger-color-light); 26 | } 27 | &.dark > span { 28 | background-color: var(--burger-color-light); 29 | } */ 30 | .nj-burger-bar--1 { 31 | transform: translateY(calc(-1 * var(--burger-x))); 32 | } 33 | .nj-burger-bar--2 { 34 | transform-origin: 100% 50%; 35 | } 36 | .nj-burger-bar--3 { 37 | transform: translateY(var(--burger-x)); 38 | } 39 | } 40 | /* hover */ 41 | .nj-burger > button:hover { 42 | .nj-burger-bar--2 { 43 | transform: scaleX(1); 44 | } 45 | } 46 | /* active */ 47 | .nj-burger.active { 48 | .nj-burger-button { 49 | transform: rotate(-180deg); 50 | } 51 | .nj-burger-bar--1 { 52 | transform: rotate(45deg); 53 | } 54 | .nj-burger-bar--2 { 55 | opacity: 0; 56 | } 57 | .nj-burger-bar--3 { 58 | transform: rotate(-45deg); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nujek/monorepo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Nujek - Build Websites with Nuxt.js and Storyblok", 6 | "license": "MIT", 7 | "author": "Kevin Regenrek", 8 | "scripts": { 9 | "build": "esno ./scripts/build.ts", 10 | "publish:ci": "esno ./scripts/publish.ts", 11 | "release": "yarn publish:ci --release-only", 12 | "clean:nm": "esno ./scripts/clean.ts", 13 | "build:sb": "yarn build sb", 14 | "build:ui": "yarn build ui", 15 | "publish:ui": "yarn publish:ci ui", 16 | "publish:sb": "yarn publish:ci storyblok", 17 | "publish:bundle": "yarn publish:ci bundle", 18 | "clean": "rimraf dist types typings packages/*/dist", 19 | "types:fix": "esno ./scripts/types-fix.ts", 20 | "lint:fix": "eslint --fix --ext .js,.jsx,.tsx,.vue", 21 | "lint": "esno ./scripts/lint.ts", 22 | "test:unit": "esno ./scripts/test-unit.ts", 23 | "test:e2e": "esno ./scripts/test-e2e.ts", 24 | "compile:css": "esno ./scripts/styles.ts", 25 | "build:types": "tsc --emitDeclarationOnly && npm run types:fix" 26 | }, 27 | "workspaces": [ 28 | "packages/*" 29 | ], 30 | "husky": { 31 | "hooks": { 32 | "pre-commit": "lint-staged" 33 | } 34 | }, 35 | "lint-staged": { 36 | "*.{js,vue}": "eslint --fix" 37 | }, 38 | "devDependencies": { 39 | "bumpp": "^7.1.1", 40 | "esno": "^0.3.0", 41 | "husky": "^4.3.0", 42 | "lint-staged": "^10.4.0", 43 | "mkdist": "^0.1.1", 44 | "rimraf": "^3.0.2", 45 | "siroc": "^0.6.5" 46 | }, 47 | "engines": { 48 | "node": ">=14" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/core/props.js: -------------------------------------------------------------------------------- 1 | import playerProps from './playerProps' 2 | 3 | const sourcesProperties = ['src', 'res', 'autoplay'] 4 | 5 | const sourcesValidator = (value) => { 6 | if (!Array.isArray(value)) { 7 | return false 8 | } 9 | 10 | if (value.length === 0) { 11 | return true 12 | } 13 | 14 | return arrayContainsProps(value, sourcesProperties) 15 | } 16 | 17 | const arrayContainsProps = (array, arrayPropNames) => { 18 | if (arrayPropNames.length === 1) { 19 | return containsProp(array, arrayPropNames[0]) 20 | } 21 | 22 | return ( 23 | containsProp(array, arrayPropNames[0]) * 24 | arrayContainsProps(array, arrayPropNames.slice(1)) 25 | ) 26 | } 27 | 28 | const containsProp = (array, propName) => { 29 | for (let i = array.length - 1; i > -1; i -= 1) { 30 | const propObj = array[i] 31 | 32 | if (!isObject(propObj)) { 33 | return false 34 | } 35 | 36 | if (exists(propObj, propName)) { 37 | return true 38 | } 39 | } 40 | 41 | return false 42 | } 43 | 44 | const isObject = obj => obj != null && obj.constructor.name === 'Object' 45 | 46 | const exists = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) 47 | 48 | export default { 49 | sources: { 50 | type: Array, 51 | default () { 52 | return [] 53 | }, 54 | validator: sourcesValidator 55 | }, 56 | autoplay: { 57 | type: Boolean, 58 | default: true 59 | }, 60 | poster: { 61 | type: String, 62 | default: '' 63 | }, 64 | overlay: { 65 | type: String, 66 | default: '' 67 | }, 68 | ...playerProps 69 | } 70 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/core/resize.js: -------------------------------------------------------------------------------- 1 | import throttle from '../lib/throttle' 2 | 3 | export default { 4 | data () { 5 | return { 6 | width: 0 7 | } 8 | }, 9 | computed: { 10 | current () { 11 | if (this.sources.length === 0) { 12 | return this.default 13 | } 14 | 15 | const current = this.sources 16 | .sort((a, b) => a.res - b.res) 17 | .filter(source => source.res >= this.width) 18 | 19 | if (current.length === 0) { 20 | return this.default 21 | } 22 | 23 | return current[0] 24 | }, 25 | default () { 26 | return { 27 | src: this.src, 28 | poster: this.poster, 29 | autoplay: this.autoplay 30 | } 31 | } 32 | }, 33 | methods: { 34 | $_change_video_resolution () { 35 | this.width = this.$_innerWidth() 36 | }, 37 | $_innerWidth () { 38 | return window.innerWidth && document.documentElement.clientWidth 39 | ? Math.min(window.innerWidth, document.documentElement.clientWidth) 40 | : window.innerWidth || 41 | document.documentElement.clientWidth || 42 | document.getElementsByTagName('body')[0].clientWidth 43 | } 44 | }, 45 | beforeMount () { 46 | this.$_change_video_resolution() 47 | }, 48 | mounted () { 49 | window.addEventListener( 50 | 'resize', 51 | throttle(this.$_change_video_resolution, 250) 52 | ) 53 | }, 54 | beforeDestroy () { 55 | window.removeEventListener( 56 | 'resize', 57 | throttle(this.$_change_video_resolution, 250) 58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/atoms/NjHeading.vue: -------------------------------------------------------------------------------- 1 | 6 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Nujek Framework](images/nujek.png) 2 | 3 | Nujek is a collection of useful Vue.js components and libraries which helps you to build [Storyblok](https://www.storyblok.com/) websites and apps as easy as possible. Helping you to stay productive following and eliminating repetive 4 | tasks. 5 | 6 | - 🎲  Demos on CodeSandbox - WIP 7 | - 📖 [ Read the documentation](https://nujek-docs.vercel.app/) 8 | 9 | ## Starter Project 10 | 11 | [Nuxt Storyblok Starter Website](https://github.com/regenrek/nuxt-storyblok-starter) is an example website template to quickstart your project. 12 | 13 | ## 🚀 Features 14 | 15 | * [Docs](https://nujek-docs.vercel.app/) and [Starter Website template](https://github.com/regenrek/nuxt-storyblok-starter) 16 | * Written for vue2 17 | * Auto import components support with @nuxtjs/components 18 | * `@nujek/storyblok` Complete Nuxt.js Storyblok Integration 19 | * `@nujek/storyblok` Useful Storyblok blok viewer for development. 20 | * `@nujek/ui` Powerful tree-shakeable and reusable nuxt components for your next website project 21 | * `@nujek/ui` Component Theming with a configuration approach 22 | 23 | ## 📦 Install 24 | 25 | ``` 26 | yarn add -D @nujek/ui @nujek/storyblok @nuxtjs/composition-api @nuxtjs/tailwindcss 27 | ``` 28 | 29 | [See install Instructions](https://nujek-docs.vercel.app/documentation/getting-started/installation) 30 | 31 | ## 🧱 Contribute 32 | 33 | [See contribute Guide](https://nujek-docs.vercel.app/documentation/contribute/how-to-contribue) 34 | 35 | ## Older nujek versions 36 | 37 | Here you can find older docs and examples for version **< 2.0.30** 38 | 39 | [Interactive examples](https://nujek-storybook.vercel.app/) 40 | [Old docs](https://nujek-old-docs.vercel.app/) 41 | ## License 42 | 43 | [MIT License](./LICENSE) -------------------------------------------------------------------------------- /packages/ui/src/runtime/nav.js: -------------------------------------------------------------------------------- 1 | export const registerNavStore = (store) => { 2 | store.registerModule( 3 | 'nav', 4 | { 5 | namespaced: true, 6 | state: () => ({ 7 | navOpen: false, 8 | settings: {} 9 | }), 10 | actions: { 11 | toggle ({ commit }) { 12 | commit('toggle') 13 | }, 14 | setSettings ({ commit }, payload) { 15 | commit('setSettings', payload) 16 | }, 17 | open ({ commit }) { 18 | commit('set', true) 19 | }, 20 | close ({ commit }) { 21 | commit('set', false) 22 | }, 23 | set ({ commit }, val) { 24 | commit('set', val) 25 | } 26 | }, 27 | mutations: { 28 | toggle (state) { 29 | state.navOpen = !state.navOpen 30 | }, 31 | set (state, payload) { 32 | state.navOpen = payload 33 | }, 34 | setSettings (state, payload) { 35 | state.settings = payload 36 | } 37 | }, 38 | getters: { 39 | mobile (state) { 40 | return state.settings.mobile_navigation 41 | ? state.settings.mobile_navigation 42 | : state.settings.navigation 43 | }, 44 | mobileSub (state) { 45 | return state.settings.mobile_subnavigation 46 | }, 47 | main (state) { 48 | return state.settings.navigation 49 | }, 50 | aside (state) { 51 | return state.settings.aside 52 | }, 53 | sub (state) { 54 | return state.settings.subnavigation 55 | }, 56 | footer (state) { 57 | return state.settings.footer_navigation 58 | } 59 | } 60 | }, 61 | { preserveState: !!store.state.nav } 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /packages/storyblok/test/e2e/blok.spec.ts: -------------------------------------------------------------------------------- 1 | import { setupTest, createPage } from '@nuxt/test-utils' 2 | 3 | describe('Blok', () => { 4 | setupTest({ 5 | browser: true, 6 | rootDir: 'test/fixtures', 7 | config: { 8 | nujekStoryblok: { 9 | withConsole: false 10 | } 11 | }, 12 | setupTimeout: 120000 13 | }) 14 | 15 | it('loads bloks dynamically', async () => { 16 | const page = await createPage('/') 17 | const html = await page.innerHTML('body') 18 | 19 | // title 20 | expect(html).toContain('BlokTextImage Title') 21 | 22 | // image blok 23 | expect(html).toContain('https://example.jpg') 24 | expect(html).toContain('BlokImage Title') 25 | 26 | // Landingpage 27 | expect(html).toContain('https://example.com/x.jpg') 28 | expect(html).toContain('Landingpage Title') 29 | }) 30 | 31 | it('handles scoped slotData correctly', async () => { 32 | const page = await createPage('/') 33 | const html = await page.innerHTML('body') 34 | 35 | expect(html).toContain('parentCategory') 36 | }) 37 | }) 38 | 39 | describe('Blok Debug', () => { 40 | setupTest({ 41 | browser: true, 42 | rootDir: 'test/fixtures', 43 | config: { 44 | nujekStoryblok: { 45 | withConsole: true, 46 | debug: true 47 | } 48 | }, 49 | setupTimeout: 120000 50 | }) 51 | it('loads bloks dynamically with debug blok', async () => { 52 | const page = await createPage('/debug') 53 | const html = await page.innerHTML('body') 54 | 55 | // check if component name exists 56 | expect(html).toContain('BlokTextImage2.vue') 57 | expect(html).toContain('blokTextImage2') 58 | 59 | // check if props will be shown 60 | expect(html).toContain('BlokImage2.vue') 61 | expect(html).toContain('blok_image_2') 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /packages/ui/test/unit/module.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { setupTest, expectModuleToBeCalledWith, getNuxt } from '@nuxt/test-utils' 3 | 4 | describe('module', () => { 5 | setupTest({ 6 | rootDir: 'test/fixtures', 7 | server: true 8 | }) 9 | 10 | it('it should call a plugin', () => { 11 | expectModuleToBeCalledWith('addPlugin', { 12 | src: expect.stringContaining('runtime/sbutils.js'), 13 | fileName: 'nujek/sbutils.js', 14 | options: {} 15 | }) 16 | }) 17 | }) 18 | 19 | describe('module with nav config', () => { 20 | setupTest({ 21 | rootDir: 'test/fixtures', 22 | server: true, 23 | config: { 24 | nujekUi: { 25 | storeTemplates: { 26 | nav: true 27 | }, 28 | enableLazySizesPlugin: true 29 | } 30 | } 31 | }) 32 | 33 | it('plugins to be added', () => { 34 | const pluginsToSync = ['init-store.js', 'init-store.js', 'lazysizes.js'] 35 | for (const plugin of pluginsToSync) { 36 | expectModuleToBeCalledWith('addPlugin', { 37 | src: expect.stringContaining('runtime/' + plugin), 38 | fileName: 'nujek/' + plugin, 39 | options: {} 40 | }) 41 | } 42 | }) 43 | }) 44 | 45 | describe('tailwindcss module', () => { 46 | setupTest({ 47 | server: true, 48 | rootDir: 'test/fixtures', 49 | config: { 50 | nujekUi: { 51 | nujekCss: false 52 | }, 53 | tailwindcss: { 54 | exposeConfig: true, 55 | cssPath: resolve('test/fixtures/assets', 'tailwind.css') 56 | } 57 | } 58 | }) 59 | 60 | test('include custom tailwind.css file in project css', () => { 61 | const nuxt = getNuxt() 62 | expect(nuxt.options.css).toHaveLength(1) 63 | expect(nuxt.options.css).toContain(nuxt.options.tailwindcss.cssPath) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # vercel deploy 11 | .vercel 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # Nuxt generate 75 | #dist 76 | 77 | # vuepress build output 78 | .vuepress/dist 79 | 80 | # Serverless directories 81 | .serverless 82 | 83 | # IDE / Editor 84 | .idea 85 | 86 | # Service worker 87 | sw.* 88 | 89 | # macOS 90 | .DS_Store 91 | 92 | # Vim swap files 93 | *.swp 94 | 95 | # disable vs code counter 96 | .VSCodeCounter 97 | 98 | storybook/.nuxt-storybook 99 | storybook/storybook-static 100 | dist 101 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjNav/NjNavItems.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 76 | -------------------------------------------------------------------------------- /scripts/compile-styles.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss') 2 | const cssnano = require('cssnano') 3 | const queries = require('css-mqpacker') 4 | const perfect = require('perfectionist') 5 | const prefixer = require('autoprefixer') 6 | const atImport = require('postcss-import') 7 | const media = require('postcss-custom-media') 8 | const vars = require('postcss-css-variables') 9 | const extend = require('postcss-extend-rule') 10 | const conditionals = require('postcss-conditionals') 11 | const rmComments = require('postcss-discard-comments') 12 | const classRepeat = require('postcss-class-repeat') 13 | const calc = require('postcss-calc') 14 | const nested = require('postcss-nested') 15 | const mixins = require('postcss-mixins') 16 | const hexrgba = require('postcss-hexrgba') 17 | 18 | export const getPlugins = function (options) { 19 | options = options || {} 20 | 21 | const perfectionistOptions = options.perfectionist || { 22 | format: 'compact', 23 | trimTrailingZeros: false 24 | } 25 | 26 | const atImportOptions = options.atImport || {} 27 | 28 | const plugins = [ 29 | atImport(atImportOptions), mixins(), nested(), hexrgba(), conditionals(), media(), queries(), perfect(perfectionistOptions), 30 | prefixer(), extend() 31 | ] 32 | 33 | if (!options.preserveVariables) { 34 | plugins.splice(1, 0, calc()) 35 | plugins.splice(1, 0, vars()) 36 | } 37 | 38 | if (options.minify) { 39 | plugins.push(cssnano()) 40 | plugins.push(rmComments()) 41 | } 42 | 43 | if (options.repeat) { 44 | let repeatNum = parseInt(options.repeat) || 4 45 | 46 | if (repeatNum < 2) { 47 | repeatNum = 4 48 | } 49 | 50 | plugins.push(classRepeat({ repeat: repeatNum })) 51 | } 52 | 53 | if (options.plugins) { 54 | options.plugins.forEach(plugin => plugins.push(plugin)) 55 | } 56 | 57 | return plugins 58 | } 59 | 60 | const postcssBuild = (css, options) => { 61 | const plugins = getPlugins(options) 62 | 63 | return postcss(plugins).process(css, options) 64 | } 65 | 66 | export default postcssBuild 67 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nujek/ui", 3 | "version": "3.0.5", 4 | "description": "Nujek - UI Components", 5 | "keywords": [ 6 | "nuxt", 7 | "nuxtjs", 8 | "nuxt ui", 9 | "nuxtjs ui", 10 | "nuxt storyblok", 11 | "nuxtjs storyblok" 12 | ], 13 | "homepage": "https://github.com/regenrek/nujek#readme", 14 | "bugs": { 15 | "url": "https://github.com/regenrek/nujek/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/regenrek/nujek.git" 20 | }, 21 | "license": "MIT", 22 | "author": "Kevin Regenrek ", 23 | "main": "dist/module.js", 24 | "types": "dist/types.d.ts", 25 | "files": [ 26 | "dist/*", 27 | "css/*" 28 | ], 29 | "scripts": { 30 | "lint": "eslint --ext .ts --ext .vue --ext .js .", 31 | "test": "yarn lint && yarn jest --forceExit", 32 | "test:e2e": "jest test/e2e --forceExit", 33 | "test:unit": "jest test/unit --forceExit" 34 | }, 35 | "dependencies": { 36 | "lazysizes": "^5.3.0-beta1", 37 | "lodash.get": "^4.4.2" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.15.5", 41 | "@babel/eslint-parser": "^7.15.7", 42 | "@babel/preset-env": "^7.15.6", 43 | "@babel/preset-typescript": "^7.15.0", 44 | "@nuxt/test-utils": "^0.2.2", 45 | "@nuxt/types": "^2.15.8", 46 | "@nuxt/typescript-build": "^2.1.0", 47 | "@nuxt/typescript-runtime": "^2.1.0", 48 | "@nuxtjs/composition-api": "^0.29.2", 49 | "@nuxtjs/eslint-config-typescript": "^6.0.1", 50 | "@types/fs-extra": "^9.0.13", 51 | "@types/jest": "^27.0.2", 52 | "@types/lodash.get": "^4.4.6", 53 | "@vue/cli-plugin-babel": "^4.5.13", 54 | "@vue/cli-plugin-eslint": "^4.5.13", 55 | "@vue/cli-plugin-unit-jest": "^4.5.13", 56 | "@vue/test-utils": "^1.2.2", 57 | "codecov": "^3.8.3", 58 | "jest": "^27.2.1", 59 | "jsdom": "^17.0.0", 60 | "jsdom-global": "^3.0.2", 61 | "nuxt": "^2.15.8", 62 | "playwright": "^1.15.0", 63 | "ts-loader": "^8", 64 | "typescript": "^4.4.3", 65 | "vue-jest": "^3.0.7" 66 | }, 67 | "engines": { 68 | "node": ">=14" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/bundle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nujek/bundle", 3 | "version": "3.0.5", 4 | "description": "Nujek - Bundle Full", 5 | "keywords": [ 6 | "nuxt", 7 | "nuxtjs", 8 | "nuxt ui", 9 | "nuxtjs ui", 10 | "nuxt storyblok", 11 | "nuxtjs storyblok" 12 | ], 13 | "homepage": "https://github.com/regenrek/nujek#readme", 14 | "bugs": { 15 | "url": "https://github.com/regenrek/nujek/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/regenrek/nujek.git" 20 | }, 21 | "license": "MIT", 22 | "author": "Kevin Regenrek ", 23 | "main": "dist/module.js", 24 | "types": "dist/types.d.ts", 25 | "files": [ 26 | "dist/*" 27 | ], 28 | "scripts": { 29 | "lint": "eslint --ext .ts --ext .vue --ext .js .", 30 | "test": "yarn lint && yarn jest --forceExit", 31 | "test:e2e": "jest test/e2e --forceExit", 32 | "test:unit": "jest test/unit --forceExit" 33 | }, 34 | "dependencies": { 35 | "@marvr/storyblok-rich-text-vue-renderer": "^2.0.1", 36 | "@nujek/shared": "latest", 37 | "@nujek/storyblok": "^3", 38 | "@nujek/ui": "^3", 39 | "@nuxtjs/composition-api": "^0.29.2", 40 | "@nuxtjs/tailwindcss": "^4.2.1", 41 | "@tailwindcss/aspect-ratio": "^0.2.1", 42 | "@tailwindcss/forms": "^0.3.4" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.15.5", 46 | "@babel/eslint-parser": "^7.15.7", 47 | "@babel/preset-env": "^7.15.6", 48 | "@babel/preset-typescript": "^7.15.0", 49 | "@nuxt/test-utils": "^0.2.2", 50 | "@nuxt/types": "^2.15.8", 51 | "@nuxt/typescript-build": "^2.1.0", 52 | "@nuxt/typescript-runtime": "^2.1.0", 53 | "@nuxtjs/eslint-config-typescript": "^6.0.1", 54 | "@types/fs-extra": "^9.0.13", 55 | "@types/jest": "^27.0.2", 56 | "@types/lodash.get": "^4.4.6", 57 | "@vue/cli-plugin-babel": "^4.5.13", 58 | "@vue/cli-plugin-eslint": "^4.5.13", 59 | "@vue/cli-plugin-unit-jest": "^4.5.13", 60 | "@vue/test-utils": "^1.2.2", 61 | "codecov": "^3.8.3", 62 | "jest": "^27.2.1", 63 | "jsdom": "^17.0.0", 64 | "jsdom-global": "^3.0.2", 65 | "nuxt": "^2.15.8", 66 | "playwright": "^1.15.0", 67 | "ts-loader": "^8", 68 | "typescript": "^4.4.3", 69 | "vue-jest": "^3.0.7" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/storyblok/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nujek/storyblok", 3 | "version": "3.0.15", 4 | "description": "Nujek - Storyblok Components", 5 | "keywords": [ 6 | "nuxt", 7 | "nuxtjs", 8 | "nuxt ui", 9 | "nuxtjs ui", 10 | "nuxt storyblok", 11 | "nuxtjs storyblok" 12 | ], 13 | "homepage": "https://github.com/regenrek/nujek#readme", 14 | "bugs": { 15 | "url": "https://github.com/regenrek/nujek/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/regenrek/nujek.git" 20 | }, 21 | "license": "MIT", 22 | "author": "Kevin Regenrek ", 23 | "main": "dist/module.js", 24 | "types": "dist/types.d.ts", 25 | "files": [ 26 | "dist/*" 27 | ], 28 | "scripts": { 29 | "lint": "eslint --ext .ts --ext .vue --ext .js .", 30 | "test": "yarn lint && yarn jest --forceExit", 31 | "test:e2e": "jest test/e2e --forceExit", 32 | "test:unit": "jest test/unit --forceExit" 33 | }, 34 | "dependencies": { 35 | "@marvr/storyblok-rich-text-vue-renderer": "^2.0.1", 36 | "@nujek/shared": "latest", 37 | "@nuxtjs/i18n": "^7.2.0", 38 | "axios": "^0.21.4", 39 | "storyblok-nuxt": "^2.0.1" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.15.5", 43 | "@babel/eslint-parser": "^7.15.7", 44 | "@babel/preset-env": "^7.15.6", 45 | "@babel/preset-typescript": "^7.15.0", 46 | "@nujek/shared": "^0.0.125", 47 | "@nuxt/test-utils": "^0.2.2", 48 | "@nuxt/types": "^2.15.8", 49 | "@nuxt/typescript-build": "^2.1.0", 50 | "@nuxt/typescript-runtime": "^2.1.0", 51 | "@nuxtjs/composition-api": "^0.29.2", 52 | "@nuxtjs/eslint-config-typescript": "^6.0.1", 53 | "@nuxtjs/tailwindcss": "^4.2.1", 54 | "@types/fs-extra": "^9.0.13", 55 | "@types/jest": "^27.0.2", 56 | "@types/lodash.get": "^4.4.6", 57 | "@vue/cli-plugin-babel": "^4.5.13", 58 | "@vue/cli-plugin-eslint": "^4.5.13", 59 | "@vue/cli-plugin-unit-jest": "^4.5.13", 60 | "@vue/test-utils": "^1.2.2", 61 | "codecov": "^3.8.3", 62 | "eslint": "^7.32.0", 63 | "jest": "^27.2.1", 64 | "jsdom": "^17.0.0", 65 | "jsdom-global": "^3.0.2", 66 | "nuxt": "^2.15.8", 67 | "playwright": "^1.15.0", 68 | "ts-loader": "^8", 69 | "typescript": "^4.4.3", 70 | "vue-jest": "^3.0.7" 71 | }, 72 | "engines": { 73 | "node": ">=14" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/SbQuery/NumericPagination.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/Blok.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 94 | -------------------------------------------------------------------------------- /packages/storyblok/src/module.ts: -------------------------------------------------------------------------------- 1 | import { resolve, join } from 'path' 2 | import consola from 'consola' 3 | import defu from 'defu' 4 | 5 | import type { Module } from '@nuxt/types' 6 | 7 | const storyblokModule: Module = async function storyblokModule (moduleOptions) { 8 | const defaults: any = { 9 | storyblokConfig: { 10 | accessToken: '', 11 | cacheProvider: 'memory' 12 | }, 13 | withConsole: false, 14 | debug: false 15 | } 16 | 17 | const { nuxt, requireModule, addPlugin } = this 18 | const options: any = defu(moduleOptions, nuxt.options.nujekStoryblok, defaults) 19 | const logger = consola.withScope('@nujek/storyblok') 20 | const ROOT_DIR = 'nujek' 21 | 22 | // Transpile and alias runtime 23 | const runtimeDir = resolve(__dirname, 'runtime') 24 | nuxt.options.alias['~nujek-storyblok'] = runtimeDir 25 | nuxt.options.build.transpile.push(runtimeDir, '@nujek/storyblok') 26 | 27 | /** 28 | * add storyblok-nuxt module 29 | */ 30 | if (options.storyblokConfig) { 31 | await requireModule(['storyblok-nuxt', options.storyblokConfig]) 32 | 33 | if (options.withConsole) { 34 | logger.success({ 35 | message: 'Storyblok modules ready', 36 | additional: 'Module \'@nujek/storyblok-nuxt\' registered.', 37 | badge: true 38 | }) 39 | } 40 | } else { 41 | logger.warn('Storyblok API Configuration is empty') 42 | } 43 | 44 | addPlugin({ 45 | src: resolve(runtimeDir, 'plugin.js'), 46 | fileName: join(ROOT_DIR, 'storyblok-nujek.js'), 47 | options: { debug: options.debug } 48 | }) 49 | 50 | nuxt.hook('components:dirs', (dirs: any) => { 51 | // remove default ~/components (if exists) autoload and replace 52 | // it with prefix "" and pathPrefix false option. 53 | const index = dirs.indexOf('~/components') 54 | if (index > -1) { 55 | dirs.splice(index, 1) 56 | } 57 | 58 | dirs.push({ 59 | path: resolve(runtimeDir, 'components'), 60 | prefix: '', 61 | pathPrefix: false 62 | }, { 63 | path: '~/components', 64 | prefix: '', 65 | pathPrefix: false 66 | }) 67 | 68 | if (options.withConsole) { 69 | logger.success({ 70 | message: '@nujek/storyblok', 71 | additional: `storyblok components loaded ${resolve(runtimeDir, 'components')} and ~/components with {prefix: '', pathPrefix: false}`, 72 | badge: true 73 | }) 74 | } 75 | }) 76 | } 77 | 78 | export default storyblokModule 79 | 80 | // eslint-disable-next-line 81 | // @ts-ignore 82 | storyblokModule.meta = require('../package.json') 83 | -------------------------------------------------------------------------------- /packages/bundle/test/__mocks__/story.js: -------------------------------------------------------------------------------- 1 | export const story = 2 | { 3 | name: 'TestComponent2', 4 | created_at: '2021-09-19T12:55:21.672Z', 5 | published_at: '2021-09-19T12:56:19.661Z', 6 | alternates: [], 7 | id: 72197089, 8 | uuid: '1107862c-30e1-470d-8a43-0f46eb6b18fe', 9 | content: { 10 | _uid: '55c52c0c-497c-49a2-9900-1642f20fcbbd', 11 | bloks: [ 12 | { 13 | _uid: 'f8710bcb-f4cd-4f13-99b7-31489f5c92f7', 14 | image: '', 15 | title: 'BlokTextImage Title', 16 | buttons: [], 17 | variant: 'image_right_text_left', 18 | subtitle: '', 19 | component: 'blokTextImage', 20 | title_tag: 'h3', 21 | title_size: 'h3', 22 | description: '' 23 | }, 24 | { 25 | _uid: 'a3338ab4-c81f-438a-95fe-e3c805527b72', 26 | image: { 27 | id: 2880351, 28 | alt: '', 29 | name: '', 30 | focus: null, 31 | title: 'BlokImage Title', 32 | filename: 'https://example.jpg', 33 | copyright: '', 34 | fieldtype: 'asset' 35 | }, 36 | component: 'blok_image' 37 | } 38 | ], 39 | title: 'Landingpage Title', 40 | video: { 41 | id: null, 42 | alt: null, 43 | name: '', 44 | focus: null, 45 | title: null, 46 | filename: '', 47 | copyright: null, 48 | fieldtype: 'asset' 49 | }, 50 | metaTags: { 51 | _uid: '4a3a35a8-57d8-4da7-8f01-f491f69b370b', 52 | title: '', 53 | plugin: 'seo_metatags', 54 | og_image: '', 55 | og_title: '', 56 | description: '', 57 | twitter_image: '', 58 | twitter_title: '', 59 | og_description: '', 60 | twitter_description: '' 61 | }, 62 | component: 'Landingpage', 63 | breadcrumbs: [], 64 | featuredImage: { 65 | id: 2880114, 66 | alt: '', 67 | name: '', 68 | focus: null, 69 | title: '', 70 | filename: 'https://example.com/x.jpg', 71 | copyright: '', 72 | fieldtype: 'asset' 73 | }, 74 | imagePosition: 'center', 75 | titlePosition: 'title_in_image', 76 | category: 'parentCategory' 77 | }, 78 | slug: 'testcomponent2', 79 | full_slug: 'de/testcomponent2', 80 | default_full_slug: null, 81 | sort_by_date: null, 82 | position: -10, 83 | tag_list: [], 84 | is_startpage: false, 85 | parent_id: 60505473, 86 | meta_data: null, 87 | group_id: '366d66a4-a28b-49ac-b045-1dab1d4adc45', 88 | first_published_at: '2021-09-19T12:56:19.661Z', 89 | release_id: null, 90 | lang: 'default', 91 | path: null, 92 | translated_slugs: [] 93 | } 94 | -------------------------------------------------------------------------------- /packages/storyblok/test/__mocks__/story.js: -------------------------------------------------------------------------------- 1 | export const story = 2 | { 3 | name: 'TestComponent2', 4 | created_at: '2021-09-19T12:55:21.672Z', 5 | published_at: '2021-09-19T12:56:19.661Z', 6 | alternates: [], 7 | id: 72197089, 8 | uuid: '1107862c-30e1-470d-8a43-0f46eb6b18fe', 9 | content: { 10 | _uid: '55c52c0c-497c-49a2-9900-1642f20fcbbd', 11 | bloks: [ 12 | { 13 | _uid: 'f8710bcb-f4cd-4f13-99b7-31489f5c92f7', 14 | image: '', 15 | title: 'BlokTextImage Title', 16 | buttons: [], 17 | variant: 'image_right_text_left', 18 | subtitle: '', 19 | component: 'blokTextImage', 20 | title_tag: 'h3', 21 | title_size: 'h3', 22 | description: '' 23 | }, 24 | { 25 | _uid: 'a3338ab4-c81f-438a-95fe-e3c805527b72', 26 | image: { 27 | id: 2880351, 28 | alt: '', 29 | name: '', 30 | focus: null, 31 | title: 'BlokImage Title', 32 | filename: 'https://example.jpg', 33 | copyright: '', 34 | fieldtype: 'asset' 35 | }, 36 | component: 'blok_image' 37 | } 38 | ], 39 | title: 'Landingpage Title', 40 | video: { 41 | id: null, 42 | alt: null, 43 | name: '', 44 | focus: null, 45 | title: null, 46 | filename: '', 47 | copyright: null, 48 | fieldtype: 'asset' 49 | }, 50 | metaTags: { 51 | _uid: '4a3a35a8-57d8-4da7-8f01-f491f69b370b', 52 | title: '', 53 | plugin: 'seo_metatags', 54 | og_image: '', 55 | og_title: '', 56 | description: '', 57 | twitter_image: '', 58 | twitter_title: '', 59 | og_description: '', 60 | twitter_description: '' 61 | }, 62 | component: 'Landingpage', 63 | breadcrumbs: [], 64 | featuredImage: { 65 | id: 2880114, 66 | alt: '', 67 | name: '', 68 | focus: null, 69 | title: '', 70 | filename: 'https://example.com/x.jpg', 71 | copyright: '', 72 | fieldtype: 'asset' 73 | }, 74 | imagePosition: 'center', 75 | titlePosition: 'title_in_image', 76 | category: 'parentCategory' 77 | }, 78 | slug: 'testcomponent2', 79 | full_slug: 'de/testcomponent2', 80 | default_full_slug: null, 81 | sort_by_date: null, 82 | position: -10, 83 | tag_list: [], 84 | is_startpage: false, 85 | parent_id: 60505473, 86 | meta_data: null, 87 | group_id: '366d66a4-a28b-49ac-b045-1dab1d4adc45', 88 | first_published_at: '2021-09-19T12:56:19.661Z', 89 | release_id: null, 90 | lang: 'default', 91 | path: null, 92 | translated_slugs: [] 93 | } 94 | -------------------------------------------------------------------------------- /packages/bundle/test/__mocks__/story_debug.js: -------------------------------------------------------------------------------- 1 | export const story = 2 | { 3 | name: 'TestComponent2', 4 | created_at: '2021-09-19T12:55:21.672Z', 5 | published_at: '2021-09-19T12:56:19.661Z', 6 | alternates: [], 7 | id: 72197089, 8 | uuid: '1107862c-30e1-470d-8a43-0f46eb6b18fe', 9 | content: { 10 | _uid: '55c52c0c-497c-49a2-9900-1642f20fcbbd', 11 | bloks: [ 12 | { 13 | _uid: 'f8710bcb-f4cd-4f13-99b7-31489f5c92f7', 14 | image: '', 15 | title: 'BlokTextImage Title', 16 | buttons: [], 17 | variant: 'image_right_text_left', 18 | subtitle: '', 19 | component: 'blokTextImage2', 20 | title_tag: 'h3', 21 | title_size: 'h3', 22 | description: '' 23 | }, 24 | { 25 | _uid: 'a3338ab4-c81f-438a-95fe-e3c805527b72', 26 | image: { 27 | id: 2880351, 28 | alt: '', 29 | name: '', 30 | focus: null, 31 | title: 'BlokImage Title', 32 | filename: 'https://example.jpg', 33 | copyright: '', 34 | fieldtype: 'asset' 35 | }, 36 | component: 'blok_image_2' 37 | } 38 | ], 39 | title: 'Landingpage Title', 40 | video: { 41 | id: null, 42 | alt: null, 43 | name: '', 44 | focus: null, 45 | title: null, 46 | filename: '', 47 | copyright: null, 48 | fieldtype: 'asset' 49 | }, 50 | metaTags: { 51 | _uid: '4a3a35a8-57d8-4da7-8f01-f491f69b370b', 52 | title: '', 53 | plugin: 'seo_metatags', 54 | og_image: '', 55 | og_title: '', 56 | description: '', 57 | twitter_image: '', 58 | twitter_title: '', 59 | og_description: '', 60 | twitter_description: '' 61 | }, 62 | component: 'Landingpage', 63 | breadcrumbs: [], 64 | featuredImage: { 65 | id: 2880114, 66 | alt: '', 67 | name: '', 68 | focus: null, 69 | title: '', 70 | filename: 'https://example.com/x.jpg', 71 | copyright: '', 72 | fieldtype: 'asset' 73 | }, 74 | imagePosition: 'center', 75 | titlePosition: 'title_in_image', 76 | category: 'parentCategory' 77 | }, 78 | slug: 'testcomponent2', 79 | full_slug: 'de/testcomponent2', 80 | default_full_slug: null, 81 | sort_by_date: null, 82 | position: -10, 83 | tag_list: [], 84 | is_startpage: false, 85 | parent_id: 60505473, 86 | meta_data: null, 87 | group_id: '366d66a4-a28b-49ac-b045-1dab1d4adc45', 88 | first_published_at: '2021-09-19T12:56:19.661Z', 89 | release_id: null, 90 | lang: 'default', 91 | path: null, 92 | translated_slugs: [] 93 | } 94 | -------------------------------------------------------------------------------- /packages/bundle/test/e2e/blok.spec.ts: -------------------------------------------------------------------------------- 1 | import { setupTest, createPage } from '@nuxt/test-utils' 2 | 3 | describe('Blok', () => { 4 | setupTest({ 5 | browser: true, 6 | rootDir: 'test/fixtures', 7 | config: { 8 | nujekStoryblok: { 9 | withConsole: false 10 | } 11 | }, 12 | setupTimeout: 120000 13 | }) 14 | 15 | it('loads bloks dynamically', async () => { 16 | const page = await createPage('/') 17 | const html = await page.innerHTML('body') 18 | 19 | // title 20 | expect(html).toContain('BlokTextImage Title') 21 | 22 | // image blok 23 | expect(html).toContain('https://example.jpg') 24 | expect(html).toContain('BlokImage Title') 25 | 26 | // Landingpage 27 | expect(html).toContain('https://example.com/x.jpg') 28 | expect(html).toContain('Landingpage Title') 29 | }) 30 | 31 | it('handles scoped slotData correctly', async () => { 32 | const page = await createPage('/') 33 | const html = await page.innerHTML('body') 34 | 35 | expect(html).toContain('parentCategory') 36 | }) 37 | }) 38 | 39 | describe('Blok Debug', () => { 40 | setupTest({ 41 | browser: true, 42 | rootDir: 'test/fixtures', 43 | config: { 44 | nujekStoryblok: { 45 | withConsole: true, 46 | debug: true 47 | } 48 | }, 49 | setupTimeout: 120000 50 | }) 51 | it('loads bloks dynamically with debug blok', async () => { 52 | const page = await createPage('/debug') 53 | const html = await page.innerHTML('body') 54 | 55 | // check if component name exists 56 | expect(html).toContain('blok_image_2') 57 | expect(html).toContain('blokTextImage2') 58 | 59 | // check if props will be shown 60 | expect(html).toContain('BlokTextImage Title') 61 | expect(html).toContain('title_tag') 62 | 63 | expect(html).toContain('BlokImage Title') 64 | expect(html).toContain('image') 65 | }) 66 | }) 67 | 68 | describe('Blok Debug', () => { 69 | setupTest({ 70 | browser: true, 71 | rootDir: 'test/fixtures', 72 | config: { 73 | nujekStoryblok: { 74 | withConsole: true 75 | }, 76 | nujekUi: { 77 | withConsole: true 78 | }, 79 | tailwindcss: { 80 | cssPath: '~/assets/tailwind.css' 81 | } 82 | }, 83 | setupTimeout: 120000 84 | }) 85 | 86 | const requests: string[] = [] 87 | 88 | it('loads bloks dynamically with debug blok', async () => { 89 | await createPage('/') 90 | 91 | const positiveRequest = requests.find(request => request.match('/_ipx/s_300x200/2000px-Aconcagua2016.jpg')) 92 | expect(positiveRequest).toBeTruthy() 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /packages/storyblok/test/__mocks__/story_debug.js: -------------------------------------------------------------------------------- 1 | export const story = 2 | { 3 | name: 'TestComponent2', 4 | created_at: '2021-09-19T12:55:21.672Z', 5 | published_at: '2021-09-19T12:56:19.661Z', 6 | alternates: [], 7 | id: 72197089, 8 | uuid: '1107862c-30e1-470d-8a43-0f46eb6b18fe', 9 | content: { 10 | _uid: '55c52c0c-497c-49a2-9900-1642f20fcbbd', 11 | bloks: [ 12 | { 13 | _uid: 'f8710bcb-f4cd-4f13-99b7-31489f5c92f7', 14 | image: '', 15 | title: 'BlokTextImage Title', 16 | buttons: [], 17 | variant: 'image_right_text_left', 18 | subtitle: '', 19 | component: 'blokTextImage2', 20 | title_tag: 'h3', 21 | title_size: 'h3', 22 | description: '' 23 | }, 24 | { 25 | _uid: 'a3338ab4-c81f-438a-95fe-e3c805527b72', 26 | image: { 27 | id: 2880351, 28 | alt: '', 29 | name: '', 30 | focus: null, 31 | title: 'BlokImage Title', 32 | filename: 'https://example.jpg', 33 | copyright: '', 34 | fieldtype: 'asset' 35 | }, 36 | component: 'blok_image_2' 37 | } 38 | ], 39 | title: 'Landingpage Title', 40 | video: { 41 | id: null, 42 | alt: null, 43 | name: '', 44 | focus: null, 45 | title: null, 46 | filename: '', 47 | copyright: null, 48 | fieldtype: 'asset' 49 | }, 50 | metaTags: { 51 | _uid: '4a3a35a8-57d8-4da7-8f01-f491f69b370b', 52 | title: '', 53 | plugin: 'seo_metatags', 54 | og_image: '', 55 | og_title: '', 56 | description: '', 57 | twitter_image: '', 58 | twitter_title: '', 59 | og_description: '', 60 | twitter_description: '' 61 | }, 62 | component: 'Landingpage', 63 | breadcrumbs: [], 64 | featuredImage: { 65 | id: 2880114, 66 | alt: '', 67 | name: '', 68 | focus: null, 69 | title: '', 70 | filename: 'https://example.com/x.jpg', 71 | copyright: '', 72 | fieldtype: 'asset' 73 | }, 74 | imagePosition: 'center', 75 | titlePosition: 'title_in_image', 76 | category: 'parentCategory' 77 | }, 78 | slug: 'testcomponent2', 79 | full_slug: 'de/testcomponent2', 80 | default_full_slug: null, 81 | sort_by_date: null, 82 | position: -10, 83 | tag_list: [], 84 | is_startpage: false, 85 | parent_id: 60505473, 86 | meta_data: null, 87 | group_id: '366d66a4-a28b-49ac-b045-1dab1d4adc45', 88 | first_published_at: '2021-09-19T12:56:19.661Z', 89 | release_id: null, 90 | lang: 'default', 91 | path: null, 92 | translated_slugs: [] 93 | } 94 | -------------------------------------------------------------------------------- /packages/ui/test/__mocks__/story.js: -------------------------------------------------------------------------------- 1 | export const story = 2 | { 3 | name: 'TestComponent2', 4 | created_at: '2021-09-19T12:55:21.672Z', 5 | published_at: '2021-09-19T12:56:19.661Z', 6 | alternates: [], 7 | id: 72197089, 8 | uuid: '1107862c-30e1-470d-8a43-0f46eb6b18fe', 9 | content: { 10 | _uid: '55c52c0c-497c-49a2-9900-1642f20fcbbd', 11 | bloks: [ 12 | { 13 | _uid: 'f8710bcb-f4cd-4f13-99b7-31489f5c92f7', 14 | image: '', 15 | title: '', 16 | buttons: [], 17 | variant: 'image_right_text_left', 18 | subtitle: '', 19 | component: 'blok_text_image', 20 | title_tag: 'h3', 21 | title_size: 'h3', 22 | description: '', 23 | yellowWaveLine: false 24 | }, 25 | { 26 | _uid: 'a3338ab4-c81f-438a-95fe-e3c805527b72', 27 | image: { 28 | id: 2880351, 29 | alt: '', 30 | name: '', 31 | focus: null, 32 | title: '', 33 | filename: 'https://a.storyblok.com/f/118340/1181x1772/75b59aa681/klettern7-c-hamari-kraxlpark.jpg', 34 | copyright: '', 35 | fieldtype: 'asset' 36 | }, 37 | component: 'blok_image' 38 | } 39 | ], 40 | title: 'Demo', 41 | video: { 42 | id: null, 43 | alt: null, 44 | name: '', 45 | focus: null, 46 | title: null, 47 | filename: '', 48 | copyright: null, 49 | fieldtype: 'asset' 50 | }, 51 | metaTags: { 52 | _uid: '4a3a35a8-57d8-4da7-8f01-f491f69b370b', 53 | title: '', 54 | plugin: 'seo_metatags', 55 | og_image: '', 56 | og_title: '', 57 | description: '', 58 | twitter_image: '', 59 | twitter_title: '', 60 | og_description: '', 61 | twitter_description: '' 62 | }, 63 | component: 'Landingpage', 64 | breadcrumbs: [], 65 | featuredImage: { 66 | id: 2880114, 67 | alt: '', 68 | name: '', 69 | focus: null, 70 | title: '', 71 | filename: 'https://a.storyblok.com/f/118340/5760x3840/7e9d9e88ee/ausflugsfreude-57-c-schischaukel-moenichkirchen-mariensee-gmbh-fotografundfee.jpg', 72 | copyright: '', 73 | fieldtype: 'asset' 74 | }, 75 | imagePosition: 'center', 76 | titlePosition: 'title_in_image' 77 | }, 78 | slug: 'testcomponent2', 79 | full_slug: 'de/testcomponent2', 80 | default_full_slug: null, 81 | sort_by_date: null, 82 | position: -10, 83 | tag_list: [], 84 | is_startpage: false, 85 | parent_id: 60505473, 86 | meta_data: null, 87 | group_id: '366d66a4-a28b-49ac-b045-1dab1d4adc45', 88 | first_published_at: '2021-09-19T12:56:19.661Z', 89 | release_id: null, 90 | lang: 'default', 91 | path: null, 92 | translated_slugs: [] 93 | } 94 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/atoms/SbImage.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 98 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/organisms/NjVideoBackground/_sub/VideoPlayer.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 103 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjTabs/NjTabs.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 82 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/atoms/NjImage.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 117 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjNav/NjNavDropdownItem.vue: -------------------------------------------------------------------------------- 1 | 19 | 122 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjNav/NjSlideOverMenu.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 103 | 104 | 107 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/NjNav/NjNav.vue: -------------------------------------------------------------------------------- 1 | 49 | 128 | -------------------------------------------------------------------------------- /packages/ui/src/module.ts: -------------------------------------------------------------------------------- 1 | import { join, resolve } from 'path' 2 | import consola from 'consola' 3 | import defu from 'defu' 4 | 5 | import type { Module } from '@nuxt/types' 6 | 7 | const uiModule: Module = function uiModule (moduleOptions) { 8 | const defaults: any = { 9 | withConsole: false, 10 | nujekCss: false, 11 | storeTemplates: { 12 | nav: false 13 | }, 14 | autoloadComponents: true, 15 | enableSbUtils: true, 16 | enableLazySizesPlugin: false, 17 | enableRichTextRenderer: false 18 | } 19 | 20 | const { nuxt, addPlugin, addTemplate } = this 21 | const options: any = defu(moduleOptions, nuxt.options.nujekUi, defaults) 22 | const logger = consola.withScope('@nujek/ui') 23 | const ROOT_DIR = 'nujek' 24 | 25 | // Transpile and alias runtime 26 | const runtimeDir = resolve(__dirname, 'runtime') 27 | nuxt.options.alias['~nujek-ui'] = runtimeDir 28 | nuxt.options.build.transpile.push(runtimeDir, '@nujek/ui') 29 | 30 | nuxt.hook('build:before', () => { 31 | if (options.nujekCss) { 32 | options.css.unshift('@nujek/ui/dist/nujek-ui.css') 33 | } 34 | }) 35 | 36 | if (options.storeTemplates.nav) { 37 | addTemplate({ 38 | src: resolve(runtimeDir, 'nav.js'), 39 | fileName: join(ROOT_DIR, 'nav.js'), 40 | options: {} 41 | }) 42 | 43 | addPlugin({ 44 | src: resolve(runtimeDir, 'init-store.js'), 45 | fileName: join(ROOT_DIR, 'init-store.js'), 46 | options: {} 47 | }) 48 | 49 | if (options.withConsole) { 50 | logger.success({ 51 | message: '@nujek/ui', 52 | additional: `store initialzed ${resolve(__dirname, 'init-store.js')}`, 53 | badge: true 54 | }) 55 | } 56 | } 57 | 58 | if (options.enableSbUtils) { 59 | addPlugin({ 60 | src: resolve(runtimeDir, 'sbutils.js'), 61 | fileName: join(ROOT_DIR, 'sbutils.js'), 62 | options: {} 63 | }) 64 | } 65 | 66 | if (options.enableLazySizesPlugin) { 67 | addPlugin({ 68 | src: resolve(runtimeDir, 'lazysizes.js'), 69 | fileName: join(ROOT_DIR, 'lazysizes.js'), 70 | options: {} 71 | }) 72 | 73 | if (options.withConsole) { 74 | logger.success({ 75 | message: '@nujek/ui', 76 | additional: `lazysizes initialzed ${resolve( 77 | __dirname, 78 | 'lazysizes.js' 79 | )}`, 80 | badge: true 81 | }) 82 | } 83 | } 84 | 85 | if (options.autoloadComponents) { 86 | nuxt.hook('components:dirs', (dirs: any) => { 87 | // Add ./components dir to the list 88 | dirs.push({ 89 | path: resolve(runtimeDir, 'components'), 90 | prefix: '', 91 | pathPrefix: false 92 | }) 93 | 94 | if (options.withConsole) { 95 | logger.success({ 96 | message: '@nujek/ui', 97 | additional: `ui components loaded ${resolve(runtimeDir, 'components')}`, 98 | badge: true 99 | }) 100 | } 101 | }) 102 | } 103 | 104 | // Add rich text renderer 105 | if (options.enableRichTextRenderer) { 106 | addPlugin({ 107 | src: resolve(runtimeDir, 'rich-text-renderer.js'), 108 | fileName: join(ROOT_DIR, 'rich-text-renderer.js'), 109 | options: {} 110 | }) 111 | nuxt.options.build.transpile.push('@marvr/storyblok-rich-text-vue-renderer') 112 | 113 | if (options.withConsole) { 114 | logger.success({ 115 | message: '@nujek/ui', 116 | additional: 'rich-text-renderer added', 117 | badge: true 118 | }) 119 | } 120 | } 121 | } 122 | 123 | export default uiModule 124 | 125 | // eslint-disable-next-line 126 | // @ts-ignore 127 | uiModule.meta = require('../package.json') 128 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/sbutils.js: -------------------------------------------------------------------------------- 1 | export default (context, inject) => { 2 | // check if absolute (http/s) -> or relative / -> 3 | const isAbsolute = url => /^(?:[a-z]+:)?\/\//i.test(url) 4 | 5 | const arrayCheck = (field) => { 6 | return Array.isArray(field) ? field[0] : field 7 | } 8 | 9 | const tag = (field) => { 10 | field = arrayCheck(field) 11 | 12 | switch (field?.link?.linktype) { 13 | case 'story': 14 | // @TODO If span is sent back there is something missing story linking 15 | return (typeof field.link.story === 'object' && 16 | Object.keys(field.link.story).length && 'nuxt-link') || 'span' 17 | case 'url': 18 | return isAbsolute(field.link.url) ? 'a' : 'nuxt-link' 19 | default: 20 | return 'span' 21 | } 22 | } 23 | 24 | const label = (field) => { 25 | field = arrayCheck(field) 26 | return field?.label || field?.name || 'undefined on storyblok' 27 | } 28 | 29 | const description = (field) => { 30 | field = arrayCheck(field) 31 | 32 | return field?.description || '' 33 | } 34 | 35 | const fullSlug = (field) => { 36 | field = arrayCheck(field) 37 | if (field?.link?.linktype === 'url') { return field.link.url || '' } 38 | if (field?.link?.linktype === 'story') { return field.link.story?.fullSlug ? `/${field.link.story.fullSlug}` : '' } 39 | return '' 40 | } 41 | 42 | const linkTo = (field) => { 43 | field = arrayCheck(field) 44 | 45 | switch (field?.link?.linktype) { 46 | case 'url': 47 | return isAbsolute(field?.link?.url) 48 | ? { href: field.link.url || '#', target: '_blank' } 49 | : toNuxtLink(field.link.url) 50 | case 'story': 51 | if (field.link.story && 52 | typeof field.link.story === 'object' && 53 | Object.keys(field.link.story).length) { 54 | return toNuxtLink(field.link.story.fullSlug || field.link.story.full_slug) 55 | } else { 56 | // eslint-disable-next-line no-console 57 | console.warn(`No url/story for type Link (${field.label}) found. Try publish the linked story first`) 58 | return '' 59 | } 60 | default: 61 | return {} 62 | } 63 | } 64 | 65 | const toNuxtLink = (link) => { 66 | if (!link) { 67 | return {} 68 | } 69 | 70 | return { 71 | // remove first slash if exists 72 | to: '/' + (link.replace(/^\//, '') || '') 73 | } 74 | } 75 | 76 | const getSliderItemLinkType = (item) => { 77 | switch (item.linktype) { 78 | case 'url': 79 | return item.url ? 'a' : 'div' 80 | case 'story': 81 | return item.story && 82 | typeof item.story === 'object' && 83 | Object.keys(item.story).length 84 | ? 'nuxt-link' 85 | : 'div' 86 | default: 87 | return 'div' 88 | } 89 | } 90 | 91 | const getSliderItemLink = (field) => { 92 | switch (field.linktype) { 93 | case 'url': 94 | return field.url ? { href: field.url || '#', target: '_blank' } : {} 95 | case 'story': 96 | return field.story && 97 | typeof field.story === 'object' && 98 | Object.keys(field.story).length && 99 | toNuxtLink(field.story.fullSlug || field.story.full_slug) 100 | default: 101 | return {} 102 | } 103 | } 104 | 105 | const truncate = (text, length = 80) => { 106 | if (text.length > length) { 107 | return `${text.split('').splice(0, length).join('')}...` 108 | } else { 109 | return text 110 | } 111 | } 112 | 113 | const utils = { 114 | linkTo, 115 | tag, 116 | label, 117 | description, 118 | fullSlug, 119 | getSliderItemLinkType, 120 | getSliderItemLink, 121 | truncate, 122 | toNuxtLink 123 | } 124 | 125 | inject('sbutils', utils) 126 | context.$sbutils = utils 127 | } 128 | -------------------------------------------------------------------------------- /packages/bundle/test/unit/module.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { setupTest, getNuxt, expectModuleToBeCalledWith } from '@nuxt/test-utils' 3 | 4 | describe('module with default options', () => { 5 | setupTest({ 6 | rootDir: 'test/fixtures', 7 | server: true, 8 | setupTimeout: 120000 9 | }) 10 | 11 | it('injects all modules', () => { 12 | const buildModules = [{ 13 | name: '@nuxtjs/composition-api/module' 14 | }, { 15 | name: '@nuxtjs/tailwindcss', 16 | options: {} 17 | }, { 18 | name: '@nujek/storyblok', 19 | options: {} 20 | }, { 21 | name: '@nujek/ui', 22 | options: {} 23 | }] 24 | 25 | for (const module of buildModules) { 26 | if (module.options) { 27 | expectModuleToBeCalledWith('requireModule', module.name, module.options) 28 | } else { 29 | expectModuleToBeCalledWith('requireModule', module.name) 30 | } 31 | } 32 | }) 33 | }) 34 | 35 | describe('@nujek/ui with options', () => { 36 | setupTest({ 37 | rootDir: 'test/fixtures', 38 | server: true, 39 | config: { 40 | nujekUi: { 41 | withConsole: true 42 | } 43 | } 44 | }) 45 | 46 | it('injects module @nujek/ui', () => { 47 | expectModuleToBeCalledWith('requireModule', '@nujek/ui', { 48 | withConsole: true 49 | }) 50 | }) 51 | }) 52 | 53 | describe('@nujek/storyblok with options', () => { 54 | setupTest({ 55 | rootDir: 'test/fixtures', 56 | server: true, 57 | config: { 58 | nujekStoryblok: { 59 | storyblokConfig: { 60 | accessToken: 'notexistant', 61 | cacheProvider: 'memory' 62 | } 63 | } 64 | } 65 | }) 66 | 67 | it('injects module @nujek/storyblok', () => { 68 | expectModuleToBeCalledWith('requireModule', '@nujek/storyblok', { 69 | storyblokConfig: { 70 | accessToken: 'notexistant', 71 | cacheProvider: 'memory' 72 | } 73 | }) 74 | }) 75 | }) 76 | 77 | describe('withConsole override global', () => { 78 | setupTest({ 79 | rootDir: 'test/fixtures', 80 | server: true, 81 | config: { 82 | nujekStoryblok: { 83 | withConsole: false 84 | }, 85 | nujekUi: { 86 | withConsole: true 87 | }, 88 | withConsole: true 89 | } 90 | }) 91 | 92 | it('injects module storyblok-nuxt', () => { 93 | const buildModules = [{ 94 | name: '@nujek/storyblok', 95 | options: { withConsole: false } 96 | }, { 97 | name: '@nujek/ui', 98 | options: { withConsole: true } 99 | }] 100 | 101 | for (const module of buildModules) { 102 | expectModuleToBeCalledWith('requireModule', module.name, module.options) 103 | } 104 | }) 105 | }) 106 | 107 | describe('withConsole override global 2', () => { 108 | setupTest({ 109 | rootDir: 'test/fixtures', 110 | server: true, 111 | config: { 112 | nujekUi: { 113 | withConsole: false 114 | }, 115 | withConsole: true 116 | } 117 | }) 118 | 119 | it('injects module storyblok-nuxt', () => { 120 | const buildModules = [{ 121 | name: '@nujek/storyblok', 122 | options: { withConsole: true } 123 | }, { 124 | name: '@nujek/ui', 125 | options: { withConsole: false } 126 | }] 127 | 128 | for (const module of buildModules) { 129 | expectModuleToBeCalledWith('requireModule', module.name, module.options) 130 | } 131 | }) 132 | }) 133 | 134 | describe('tailwindcss module', () => { 135 | setupTest({ 136 | server: true, 137 | rootDir: 'test/fixtures', 138 | config: { 139 | tailwindcss: { 140 | cssPath: resolve('test/fixtures/assets', 'tailwind.css') 141 | }, 142 | nujekUi: { 143 | nujekCss: false 144 | } 145 | } 146 | }) 147 | 148 | test('include custom tailwind.css file in project css', () => { 149 | const nuxt = getNuxt() 150 | expect(nuxt.options.css).toHaveLength(1) 151 | expect(nuxt.options.css).toContain(nuxt.options.tailwindcss.cssPath) 152 | }) 153 | }) 154 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/BlokDebug.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 118 | 124 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/images/empty.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /packages/storyblok/test/__mocks__/story_miss_ct.js: -------------------------------------------------------------------------------- 1 | export const story = 2 | { 3 | name: 'TestComponent2', 4 | created_at: '2021-09-19T12:55:21.672Z', 5 | published_at: '2021-09-19T12:56:19.661Z', 6 | alternates: [], 7 | id: 72197089, 8 | uuid: '1107862c-30e1-470d-8a43-0f46eb6b18fe', 9 | content: { 10 | _uid: '55c52c0c-497c-49a2-9900-1642f20fcbbd', 11 | bloks: [ 12 | { 13 | component: 'Foo', 14 | description: 'This is totally foo!', 15 | category: 'vue', 16 | bars: [ 17 | { 18 | component: 'Bar', 19 | description: 'This is a bar' 20 | }, 21 | { 22 | component: 'Bar', 23 | description: 'This bar is somewhat different' 24 | }, 25 | { 26 | component: 'Bar', 27 | description: 'Another bar hanging with the cool kids' 28 | } 29 | ] 30 | }, 31 | { 32 | _uid: 'f8710bcb-f4cd-4f13-99b7-31489f5c92f7', 33 | image: '', 34 | title: 'BlokTextImage Title', 35 | buttons: [], 36 | variant: 'image_right_text_left', 37 | subtitle: '', 38 | component: 'BlokTextImage2', 39 | title_tag: 'h3', 40 | title_size: 'h3', 41 | description: '' 42 | }, 43 | { 44 | _uid: 'f8710bcb-f4cd-4f13-99b7-31489f5c92f7', 45 | image: '', 46 | title: 'BlokTextImage Title', 47 | buttons: [], 48 | variant: 'image_right_text_left', 49 | subtitle: '', 50 | component: 'BlokTextImage2', 51 | title_tag: 'h3', 52 | title_size: 'h3', 53 | description: '' 54 | }, 55 | { 56 | _uid: 'a3338ab4-c81f-438a-95fe-e3c805527b72', 57 | image: { 58 | id: 2880351, 59 | alt: '', 60 | name: '', 61 | focus: null, 62 | title: 'BlokImage Title', 63 | filename: 'https://example.jpg', 64 | copyright: '', 65 | fieldtype: 'asset' 66 | }, 67 | component: 'blok_image_2' 68 | }, 69 | { 70 | _uid: '9ffdc58e-34c5-4a96-a6c3-90c3ad91a1c0', 71 | image: { 72 | id: 2513234, 73 | alt: null, 74 | name: '', 75 | focus: null, 76 | title: null, 77 | filename: 'https://a.storyblok.com/f/118342/1920x1278/7053c4a0db/vs_wexltrails_lores_080817_044.jpg', 78 | copyright: null, 79 | fieldtype: 'asset' 80 | }, 81 | title: 'titel', 82 | button: [], 83 | variant: 'image_left_text_right', 84 | component: 'blok_text_image', 85 | aboveTitle: 'subtitle', 86 | imageTitle: '', 87 | description: { 88 | type: 'doc', 89 | content: [ 90 | { 91 | type: 'paragraph', 92 | content: [ 93 | { 94 | text: 'desc', 95 | type: 'text' 96 | } 97 | ] 98 | } 99 | ] 100 | }, 101 | yellowWaveLine: false 102 | }, 103 | { 104 | _uid: 'e3d26421-01ea-4f18-9132-78547128bfc0', 105 | width: '', 106 | height: '', 107 | source: '', 108 | component: 'blok_iframe' 109 | } 110 | ], 111 | title: 'Landingpage Title', 112 | video: { 113 | id: null, 114 | alt: null, 115 | name: '', 116 | focus: null, 117 | title: null, 118 | filename: '', 119 | copyright: null, 120 | fieldtype: 'asset' 121 | }, 122 | metaTags: { 123 | _uid: '4a3a35a8-57d8-4da7-8f01-f491f69b370b', 124 | title: '', 125 | plugin: 'seo_metatags', 126 | og_image: '', 127 | og_title: '', 128 | description: '', 129 | twitter_image: '', 130 | twitter_title: '', 131 | og_description: '', 132 | twitter_description: '' 133 | }, 134 | component: 'NotExists', 135 | breadcrumbs: [], 136 | featuredImage: { 137 | id: 2880114, 138 | alt: '', 139 | name: '', 140 | focus: null, 141 | title: '', 142 | filename: 'https://example.com/x.jpg', 143 | copyright: '', 144 | fieldtype: 'asset' 145 | }, 146 | imagePosition: 'center', 147 | titlePosition: 'title_in_image', 148 | category: 'parentCategory' 149 | }, 150 | slug: 'testcomponent2', 151 | full_slug: 'de/testcomponent2', 152 | default_full_slug: null, 153 | sort_by_date: null, 154 | position: -10, 155 | tag_list: [], 156 | is_startpage: false, 157 | parent_id: 60505473, 158 | meta_data: null, 159 | group_id: '366d66a4-a28b-49ac-b045-1dab1d4adc45', 160 | first_published_at: '2021-09-19T12:56:19.661Z', 161 | release_id: null, 162 | lang: 'default', 163 | path: null, 164 | translated_slugs: [] 165 | } 166 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/base/Component.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import get from 'lodash.get' 3 | 4 | const merge = (objFrom, objTo) => Object.keys(objFrom) 5 | .reduce( 6 | (merged, key) => { 7 | merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key]) 8 | ? merge(objFrom[key], merged[key] ?? {}) 9 | : objFrom[key] 10 | return merged 11 | }, { ...objTo } 12 | ) 13 | 14 | const mergeClasses = (classesA, classesB) => { 15 | let a = classesA 16 | let b = classesB 17 | 18 | // Convert array of string classes to a single string 19 | if ( 20 | Array.isArray(classesA) && 21 | classesA.every(className => typeof className === 'string' || !!className) 22 | ) { 23 | a = classesA.filter(className => !!className).join(' ') 24 | } 25 | 26 | // Convert array of string classes to a single string 27 | if ( 28 | Array.isArray(classesB) && 29 | classesB.every(className => typeof className === 'string' || !!className) 30 | ) { 31 | b = classesB.filter(className => !!className).join(' ') 32 | } 33 | 34 | if (typeof a === 'string' && typeof b === 'string') { 35 | return `${a} ${b}` 36 | } 37 | 38 | if (typeof a === 'string' && Array.isArray(b)) { 39 | return [a].concat(b) 40 | } 41 | 42 | if (typeof b === 'string' && Array.isArray(a)) { 43 | return a.concat([b]) 44 | } 45 | 46 | if (Array.isArray(a) && Array.isArray(b)) { 47 | return a.concat(b) 48 | } 49 | 50 | return [a, b] 51 | } 52 | 53 | const Component = Vue.extend({ 54 | inject: { 55 | nujekConfig: { 56 | from: '$nujekConfig', 57 | default: () => { 58 | return function () { 59 | return { 60 | variants: null, 61 | classes: null, 62 | fixedClasses: null 63 | } 64 | } 65 | } 66 | } 67 | }, 68 | props: { 69 | classes: { 70 | type: [String, Array, Object], 71 | default: undefined 72 | }, 73 | fixedClasses: { 74 | type: [String, Array, Object], 75 | default: undefined 76 | }, 77 | variants: { 78 | type: Object, 79 | default: undefined 80 | }, 81 | variant: { 82 | type: [String, Object], 83 | default: undefined 84 | } 85 | }, 86 | computed: { 87 | componentClass () { 88 | return this.getElementCssClass() 89 | }, 90 | activeVariant () { 91 | if (!this.variant) { 92 | return undefined 93 | } 94 | 95 | if (typeof this.variant === 'object') { 96 | const truthyVariant = Object.keys(this.variant).find( 97 | variant => !!this.variant[variant] 98 | ) 99 | return truthyVariant || undefined 100 | } 101 | 102 | return this.variant 103 | }, 104 | getClasses () { 105 | return this.getClassProp('classes') 106 | }, 107 | getVariants () { 108 | return this.getClassProp('variants') 109 | }, 110 | getFixedClasses () { 111 | return this.getClassProp('fixedClasses') 112 | } 113 | }, 114 | methods: { 115 | getClassProp (prop) { 116 | return this.hasOverrideProp(prop) 117 | ? merge(this[prop], this.nujekConfig()?.[prop] || {}) 118 | : merge(this.nujekConfig()?.[prop] || {}, this[prop]) 119 | }, 120 | hasOverrideProp (prop) { 121 | return !!this.$options.propsData[prop] 122 | }, 123 | getThemeClass (elementName, overrideDefaultClasses = '') { 124 | let classes 125 | 126 | if (elementName) { 127 | if (this.activeVariant) { 128 | const elementVariant = get( 129 | this.getVariants, 130 | `${this.activeVariant}.${elementName}` 131 | ) 132 | 133 | // If the variant exists but not for the element fallback to the default 134 | if ( 135 | elementVariant === undefined && 136 | get(this.getVariants, this.activeVariant) !== undefined 137 | ) { 138 | classes = get(this.getClasses, elementName, overrideDefaultClasses) 139 | } else { 140 | classes = elementVariant === undefined 141 | ? overrideDefaultClasses 142 | : elementVariant 143 | } 144 | } else { 145 | classes = get(this.getClasses, elementName, overrideDefaultClasses) 146 | } 147 | 148 | const fixedClasses = get(this.getFixedClasses, elementName) 149 | 150 | if (fixedClasses) { 151 | return mergeClasses(fixedClasses, classes) 152 | } 153 | 154 | return classes 155 | } 156 | 157 | if (this.activeVariant) { 158 | classes = get(this.variants, this.activeVariant, overrideDefaultClasses) 159 | } else { 160 | classes = this.getClasses === undefined 161 | ? overrideDefaultClasses 162 | : this.getClasses 163 | } 164 | 165 | if (this.getFixedClasses) { 166 | return mergeClasses(this.getFixedClasses, classes) 167 | } 168 | 169 | return classes 170 | } 171 | } 172 | }) 173 | 174 | export default Component 175 | -------------------------------------------------------------------------------- /packages/ui/test/unit/ComponentClasses.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import { shallowMount, Wrapper } from '@vue/test-utils' 6 | 7 | import type Vue from 'vue' 8 | import NjSection from '~/runtime/components/atoms/NjSection.vue' 9 | 10 | describe('ComponentClasses', () => { 11 | let wrapper: Wrapper 12 | 13 | it('it will return the override classes', () => { 14 | wrapper = shallowMount(NjSection, { 15 | propsData: { 16 | classes: { 17 | wrapper: 'bg-red-200', 18 | container: 'bg-blue-100' 19 | } 20 | } 21 | }) 22 | 23 | expect(wrapper.classes()).toContain('bg-red-200') 24 | expect(wrapper.find('div > div').classes()).toContain('bg-blue-100') 25 | }) 26 | 27 | it('it will return variant classes', () => { 28 | const wrapper = shallowMount(NjSection, { 29 | propsData: { 30 | variant: 'boxed' 31 | } 32 | }) 33 | 34 | expect(wrapper.classes()).toContain('flex') 35 | // container 36 | expect(wrapper.find('div > div').classes()) 37 | .toContain('mx-auto') 38 | }) 39 | 40 | it('variants: it will return override component classes', () => { 41 | const wrapper = shallowMount(NjSection, { 42 | propsData: { 43 | classes: { 44 | wrapper: 'bg-red-200', 45 | container: 'bg-yellow-200' 46 | }, 47 | variant: 'boxed' 48 | } 49 | }) 50 | 51 | // wrapper 52 | // expect(wrapper.classes()).toContain('flex') 53 | expect(wrapper.classes()).toContain('flex') 54 | // container 55 | expect(wrapper.find('div > div').classes()) 56 | .toContain('mx-auto') 57 | }) 58 | 59 | it('variants: it will return override components classes with defining a custom variant', () => { 60 | const wrapper = shallowMount(NjSection, { 61 | propsData: { 62 | classes: { 63 | wrapper: 'bg-red-200' 64 | }, 65 | variants: { 66 | demo: { 67 | wrapper: 'bg-blue-200' 68 | } 69 | }, 70 | variant: 'demo' 71 | } 72 | }) 73 | 74 | // wrapper 75 | // expect(wrapper.classes()).toContain('flex') 76 | expect(wrapper.classes()).toContain('bg-blue-200') 77 | }) 78 | 79 | it('switch active variant and default classes', async () => { 80 | const wrapper = shallowMount(NjSection, { 81 | propsData: { 82 | classes: { 83 | wrapper: 'bg-red-200' 84 | }, 85 | variants: { 86 | demo: { 87 | wrapper: 'bg-blue-200', 88 | container: 'bg-yellow-200' 89 | }, 90 | demo2: { 91 | wrapper: 'bg-yellow-200', 92 | container: 'bg-blue-200' 93 | } 94 | }, 95 | variant: 'demo' 96 | } 97 | }) 98 | 99 | // wrapper 100 | expect(wrapper.classes()).toContain('bg-blue-200') 101 | // container 102 | expect(wrapper.find('div > div').classes()) 103 | .toContain('bg-yellow-200') 104 | 105 | wrapper.setProps({ variant: 'demo2' }) 106 | 107 | await wrapper.vm.$nextTick() 108 | 109 | // wrapper 110 | expect(wrapper.classes()).toContain('bg-yellow-200') 111 | // container 112 | expect(wrapper.find('div > div').classes()) 113 | .toContain('bg-blue-200') 114 | }) 115 | 116 | it('doesnt return classes if invalid variant', () => { 117 | const wrapper = shallowMount(NjSection, { 118 | propsData: { 119 | classes: { wrapper: 'bg-red-200' }, 120 | variants: { 121 | v1: { wrapper: 'bg-blue-100' }, 122 | v2: { wrapper: 'bg-yellow-100' } 123 | }, 124 | variant: 'invalid' 125 | } 126 | }) 127 | 128 | expect(wrapper.vm.$el.className).toBe('') 129 | }) 130 | 131 | it('will include both fixed and default classes always', () => { 132 | const wrapper = shallowMount(NjSection, { 133 | propsData: { 134 | fixedClasses: { wrapper: 'transition' }, 135 | classes: { wrapper: 'bg-red-200' } 136 | } 137 | }) 138 | 139 | const { className } = wrapper.vm.$el 140 | 141 | expect(className.split(' ').sort()).toEqual(['bg-red-200', 'transition']) 142 | }) 143 | 144 | it('will include both fixed and default classes merging array and string', () => { 145 | const wrapper = shallowMount(NjSection, { 146 | propsData: { 147 | fixedClasses: { wrapper: ['transition'] }, 148 | classes: { wrapper: 'bg-red-200' } 149 | } 150 | }) 151 | 152 | const { className } = wrapper.vm.$el 153 | 154 | expect(className.split(' ').sort()).toEqual(['bg-red-200', 'transition']) 155 | }) 156 | 157 | it('will include both fixed and default classes merging string and array', () => { 158 | const wrapper = shallowMount(NjSection, { 159 | propsData: { 160 | fixedClasses: { wrapper: 'transition' }, 161 | classes: { wrapper: ['bg-red-200'] } 162 | } 163 | }) 164 | 165 | const { className } = wrapper.vm.$el 166 | 167 | expect(className.split(' ').sort()).toEqual(['bg-red-200', 'transition']) 168 | }) 169 | }) 170 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/BlokDebugContentType.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 123 | 129 | -------------------------------------------------------------------------------- /packages/storyblok/src/runtime/components/SbQuery/SbQuery.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 45 | 46 | 190 | -------------------------------------------------------------------------------- /packages/ui/src/runtime/components/molecules/SbGrid.vue: -------------------------------------------------------------------------------- 1 | 48 | 200 | --------------------------------------------------------------------------------