├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build └── generate-exports.js ├── package.json ├── public ├── avatar_example.gif ├── close-icon.svg ├── flatpickr.css ├── github-logo.svg ├── global.css ├── index.html ├── menu-icon.svg ├── prism-tomorrow.css └── sveltekit-logo.svg ├── src ├── app │ ├── App.svelte │ ├── CodeBlock.svelte │ ├── ComponentViewer.svelte │ ├── Example.svelte │ ├── GettingStarted.svelte │ ├── Home.svelte │ ├── Nav.svelte │ ├── PropEditor.svelte │ ├── ThemingSample.svelte │ └── stores.js ├── components │ ├── Alert │ │ ├── Alert.spec.js │ │ ├── Alert.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ └── LongMessage.svelte │ │ └── options.js │ ├── Avatar │ │ ├── Avatar.spec.js │ │ ├── Avatar.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ ├── Sizes.svelte │ │ │ ├── WithComponent.svelte │ │ │ └── WithImage.svelte │ │ └── options.js │ ├── Button │ │ ├── Button.spec.js │ │ ├── Button.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ ├── Icons.svelte │ │ │ ├── Options.svelte │ │ │ ├── SpinningLoader.svelte │ │ │ ├── Styling.svelte │ │ │ └── Variations.svelte │ │ └── options.js │ ├── Card │ │ ├── Card.spec.js │ │ ├── Card.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ ├── Clickable.svelte │ │ │ └── Levels.svelte │ │ └── options.js │ ├── Checkbox │ │ ├── Checkbox.spec.js │ │ ├── Checkbox.svelte │ │ └── examples │ │ │ ├── Basic.svelte │ │ │ └── Disabled.svelte │ ├── Chip │ │ ├── Chip.spec.js │ │ ├── Chip.svelte │ │ └── examples │ │ │ ├── Basic.svelte │ │ │ ├── IsWaiting.svelte │ │ │ ├── Removable.svelte │ │ │ └── WithAvatar.svelte │ ├── ContentSwitcher │ │ ├── ContentSwitcher.spec.js │ │ ├── ContentSwitcher.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── DatePicker │ │ ├── DatePicker.spec.js │ │ ├── DatePicker.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ ├── DateFormat.svelte │ │ │ ├── DayNavigator.svelte │ │ │ ├── Inline.svelte │ │ │ ├── MinMax.svelte │ │ │ ├── Range.svelte │ │ │ └── TextOnly.svelte │ │ └── flatpickr.css │ ├── Dropdown │ │ ├── Dropdown.spec.js │ │ ├── Dropdown.svelte │ │ ├── _DropdownMenu.svelte │ │ ├── _DropdownMenuDivider.svelte │ │ ├── _DropdownMenuItem.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ ├── Custom.svelte │ │ │ ├── Hover.svelte │ │ │ ├── IsBlock.svelte │ │ │ ├── IsMulti.svelte │ │ │ ├── IsSearchable.svelte │ │ │ ├── ItemKey.svelte │ │ │ ├── MaxHeight.svelte │ │ │ ├── SelectedItem.svelte │ │ │ ├── SelectedItems.svelte │ │ │ ├── TotallyCustom.svelte │ │ │ ├── Width.svelte │ │ │ ├── _CustomDropdown.svelte │ │ │ └── _CustomList.svelte │ │ └── options.js │ ├── Form │ │ ├── Form.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── Icons │ │ ├── Add.svelte │ │ ├── Alert.svelte │ │ ├── ArrowDown.svelte │ │ ├── ArrowUp.svelte │ │ ├── Car.svelte │ │ ├── Check.svelte │ │ ├── ChevronLeft.svelte │ │ ├── ChevronRight.svelte │ │ ├── Clock.svelte │ │ ├── Close.svelte │ │ ├── Copy.svelte │ │ ├── Date.svelte │ │ ├── Heart.svelte │ │ ├── Hotel.svelte │ │ ├── Icons.svelte │ │ ├── Information.svelte │ │ ├── Lock.svelte │ │ ├── More.svelte │ │ ├── Plane.svelte │ │ ├── Remove.svelte │ │ ├── Search.svelte │ │ └── examples │ │ │ └── AllIcons.svelte │ ├── Modal │ │ ├── Modal.spec.js │ │ ├── Modal.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ └── Props.svelte │ │ └── modal.js │ ├── Notification │ │ ├── Notification.spec.js │ │ ├── Notification.svelte │ │ ├── examples │ │ │ └── Basic.svelte │ │ ├── notification.js │ │ └── options.js │ ├── NumberInput │ │ ├── NumberInput.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── Pagination │ │ ├── Pagination.spec.js │ │ ├── Pagination.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── ProgressBar │ │ ├── ProgressBar.spec.js │ │ ├── ProgressBar.svelte │ │ ├── examples │ │ │ └── Basic.svelte │ │ └── options.js │ ├── Radio │ │ ├── Radio.spec.js │ │ ├── Radio.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── RangeSlider │ │ ├── RangeSlider.svelte │ │ ├── examples │ │ │ ├── Basic.svelte │ │ │ ├── RangeSliderHandles.svelte │ │ │ ├── RangeSliderSteps.svelte │ │ │ ├── RangeSliderTooltips.svelte │ │ │ └── RangeSliderUpdateWhenSliding.svelte │ │ └── nouislider.css │ ├── Search │ │ ├── Search.spec.js │ │ ├── Search.svelte │ │ └── examples │ │ │ ├── Basic.svelte │ │ │ ├── Debounce.svelte │ │ │ └── Placeholder.svelte │ ├── Spinner │ │ ├── Spinner.spec.js │ │ ├── Spinner.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── Switch │ │ ├── Switch.spec.js │ │ ├── Switch.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── Table │ │ ├── Table.spec.js │ │ ├── Table.svelte │ │ └── examples │ │ │ ├── Basic.svelte │ │ │ └── data.js │ ├── Tabs │ │ ├── Tabs.svelte │ │ └── examples │ │ │ ├── Basic.svelte │ │ │ ├── _tab1.svelte │ │ │ └── _tab2.svelte │ ├── Tag │ │ ├── Tag.spec.js │ │ ├── Tag.svelte │ │ ├── examples │ │ │ └── Basic.svelte │ │ └── options.js │ ├── TextInput │ │ ├── TextInput.svelte │ │ ├── examples │ │ │ └── Basic.svelte │ │ └── options.js │ ├── TimeMenu │ │ ├── TimeMenu.svelte │ │ ├── _Menu.svelte │ │ ├── _TimeOptions.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── TimeSelect │ │ ├── TimeSelect.svelte │ │ └── examples │ │ │ └── Basic.svelte │ ├── Tooltip │ │ ├── Tooltip.svelte │ │ ├── examples │ │ │ └── Basic.svelte │ │ ├── options.js │ │ └── tippy.ui.css │ └── index.js ├── helpers │ ├── arrayHasItem.js │ ├── classnames.js │ ├── generateUniqueId.js │ ├── getPages.js │ ├── getPosition.js │ ├── inlineStyles.js │ └── whichAnimationEvent.js └── main.js ├── test ├── helpers.js ├── main.js ├── public │ ├── avatar_example.gif │ ├── flatpickr.css │ ├── global.css │ └── index.html └── runner.js ├── webpack ├── webpack.config.js └── webpack.config.tests.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | insert_final_newline = true 3 | indent_size = 2 4 | indent_style = space 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 2019, 4 | sourceType: 'module' 5 | }, 6 | env: { 7 | es6: true, 8 | browser: true, 9 | }, 10 | plugins: [ 11 | 'svelte3', 12 | ], 13 | overrides: [ 14 | { 15 | files: ['**/*.svelte'], 16 | processor: 'svelte3/svelte3', 17 | } 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | test/public/bundle.* 4 | yarn-error.log 5 | /*.js 6 | /*.mjs 7 | public/bundle.* 8 | build/** 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .idea 3 | .prettierrc 4 | 5 | node_modules 6 | public 7 | test 8 | webpack 9 | build 10 | 11 | test/public/bundle.* 12 | yarn-error.log 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "singleQuote": true, 4 | "tabWidth": 2 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG.md 2 | 3 | 4 | ## 4.0.0 (30/01/2020) 5 | 6 | - BREAKING CHANGE: Button: changed CSS prop background-color -> background 7 | 8 | ## 3.1.1 (29/12/2019) 9 | 10 | - Removed unused prop 'ItemIconComponent' from ContentSwitcher 11 | - Cleaned up Alert component 12 | 13 | ## 3.1.0 (14/12/2019) 14 | 15 | - Improved ContentSwitcher's CSS custom properties 16 | 17 | ## 3.0.0 (06/12/2019) 18 | 19 | - BREAKING CHANGE: Icon component refactored, now individually import the icon you need. 20 | 21 | 22 | ## 2.0.0 (17/11/2019) 23 | 24 | - BREAKING CHANGE: Button refactor to rely on CSS custom properties instead of `type` 25 | - Button changes breaks a few things like Chip component, will fix up soon ⏳ 26 | 27 | 28 | ## 1.0.2 (12/11/2019) 29 | 30 | - Cleaning up docs and tests 31 | 32 | 33 | ## 1.0.1 (11/11/2019) 34 | 35 | - Github package registry and npm name changes 36 | 37 | 38 | ## 1.0.0 (11/11/2019) 39 | 40 | - 👋 🌏 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 [these people](https://github.com/sveltekit/ui/graphs/contributors) 2 | 3 | Permission is hereby granted by the authors of this software, to any person, to use the software for any purpose, free of charge, including the rights to run, read, copy, change, distribute and sell it, and including usage rights to any patents the authors may hold on it, subject to the following conditions: 4 | 5 | This license, or a link to its text, must be included with all copies of the software and any derivative works. 6 | 7 | Any modification to the software submitted to the authors may be incorporated into the software under the terms of this license. 8 | 9 | The software is provided "as is", without warranty of any kind, including but not limited to the warranties of title, fitness, merchantability and non-infringement. The authors have no obligation to provide support or updates for the software, and may not be held liable for any damages, claims or other liability arising from its use. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Not in active dev anymore - plenty of other UI libs out there... 2 | 3 | # Sveltekit - Powerful, reliable & fully featured Svelte UI library 4 | 5 | ## Getting started 6 | ```sh 7 | $ npm install @sveltekit/ui 8 | ``` 9 | 10 | **Note:** Install as a dev dependency `npm i @sveltekit/ui -D` if using [Sapper](https://sapper.svelte.dev/) to avoid SSR errors. 11 | 12 | ## Dev 13 | 14 | ## Dependencies 15 | - [node](https://nodejs.org/en): stable 16 | - [yarn](https://yarnpkg.com/en/): stable 17 | 18 | ## Setup 19 | ```sh 20 | $ yarn 21 | ``` 22 | 23 | To work locally on the components you'll need to yarn link the project to itself 24 | ```sh 25 | $ yarn link 26 | $ yarn link @sveltekit/ui 27 | $ yarn dev 28 | ``` 29 | 30 | ## Tests 31 | Run tests (make sure you also have yarn dev running in another console) 32 | ```sh 33 | $ yarn test:browser 34 | ``` 35 | 36 | ## Releases 37 | 38 | Once all changes/PRs required for release have been merge to develop then create a PR for develop to be merged into master. 39 | 40 | Complete the PR, which will merge all develop changes into master. 41 | 42 | Checkout master and pull down from origin to retrieve latest changes. 43 | 44 | Update CHANGELOG and commit change (don't push). 45 | 46 | Run `npm version ` [https://docs.npmjs.com/about-semantic-versioning](https://docs.npmjs.com/about-semantic-versioning) 47 | Then `npm publish` 48 | 49 | Finally, checkout develop and merge in changes from master ie. `git merge master` 50 | -------------------------------------------------------------------------------- /build/generate-exports.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const glob = require("glob"); 4 | 5 | 6 | const allowedExtensions = ['.svelte', '.js', '.json']; 7 | 8 | const autoStart = `/*** THIS FILE IS AUTOMATICALLY GENERATED BY /build/generate-exports.js ***/\n\n`; 9 | 10 | 11 | function componentIndex(dir) { 12 | const components = glob.sync(dir + "/*/*"); 13 | const examples = glob.sync(dir + "/*/examples/*.svelte"); 14 | const addedComponents = {}; 15 | 16 | components.forEach(component => { 17 | const componentName = component.split('/')[3]; 18 | 19 | if (!addedComponents[componentName]) { 20 | addedComponents[componentName] = { name: componentName, examples: [] }; 21 | } 22 | 23 | if (component.includes('options.js')) { 24 | addedComponents[componentName].options = true 25 | } 26 | }); 27 | 28 | examples.forEach(example => { 29 | const componentName = example.split('/')[3]; 30 | const exampleName = example.split('/')[5]; 31 | if (!exampleName.includes('_')) { 32 | addedComponents[componentName].examples.push(exampleName.split('.')[0]); 33 | } 34 | }) 35 | 36 | Object.values(addedComponents).forEach(item => { 37 | function CreateContent() { 38 | let doc = `import components from '../../../src/components/index';\n` 39 | if (item.options) { 40 | doc += `import ${item.name}, { options } from '@sveltekit/ui/${item.name}';\n` 41 | } else { 42 | doc += `import ${item.name} from '@sveltekit/ui/${item.name}';\n` 43 | } 44 | 45 | if (item.examples) { 46 | item.examples.forEach(example => { 47 | doc += `import ${example} from '../../../src/components/${item.name}/examples/${example}.svelte';\n` 48 | }) 49 | } 50 | 51 | return doc; 52 | } 53 | 54 | let content = autoStart + CreateContent(); 55 | 56 | content += `\ncomponents.${item.name} = {\n`; 57 | content += ` name: '${item.name}',\n`; 58 | content += ` src: ${item.name},\n`; 59 | content += ` examples: [${[...item.examples]}],\n`; 60 | 61 | if (item.options) { 62 | content += ` options,\n`; 63 | } 64 | 65 | content += `}`; 66 | 67 | fs.mkdir(`build/components/${item.name}`, { recursive: true }, () => { 68 | fs.writeFileSync(`build/components/${item.name}/index.js`, content); 69 | }); 70 | }); 71 | } 72 | 73 | function moduleExports(dir) { 74 | fs.readdirSync(dir).forEach((file) => { 75 | const filePath = path.join(dir, file); 76 | 77 | if (fs.statSync(filePath).isDirectory() && file.charAt(0) !== '_') { 78 | fs.writeFileSync(`${file}.js`, autoStart + `module.exports = require('./${file}.mjs');`); 79 | } 80 | }); 81 | } 82 | 83 | function documentCSSVars(dir) { 84 | const svelteFiles = glob.sync(dir + "/*/*.svelte"); 85 | 86 | svelteFiles.forEach(async filename => { 87 | await fs.readFile(filename, 'utf-8', (err, content) => { 88 | let regexp = /var\(.+,\s*(.+)\)/g; 89 | 90 | if (content) { 91 | let output = []; 92 | let array = content.match(regexp) || []; 93 | 94 | array.forEach(item => { 95 | let varRx = /(?<=var\().*?(?=,)/; 96 | let defaultRx = /(?<=,)(.*)(?=\))/; 97 | let varName = item.match(varRx)[0]; 98 | let defaultValue = item.match(defaultRx)[0].trim(); 99 | 100 | if (!output.find(item => item.varName === varName)) { 101 | output.push({ 102 | varName, 103 | defaultValue 104 | }); 105 | } 106 | }); 107 | 108 | let targetFile = `build/components/${filename.split('/')[3]}/cssVars.json`; 109 | fs.writeFileSync(targetFile, `${JSON.stringify(output)}`); 110 | } 111 | }) 112 | }) 113 | }; 114 | 115 | function getComponents(dir) { 116 | fs.readdirSync(dir).forEach((file) => { 117 | const filePath = path.join(dir, file); 118 | 119 | if (fs.statSync(filePath).isDirectory() && file.charAt(0) !== '_') { 120 | createComponentIndex(dir, file); 121 | } 122 | }); 123 | } 124 | 125 | function createComponentIndex(dir, file) { 126 | let defaultExport = ''; 127 | let namedExports = ''; 128 | let _content = autoStart; 129 | 130 | const componentSrc = path.join(dir, file); 131 | const componentFile = `${file}.svelte`; 132 | const componentFilePath = path.join(componentSrc, componentFile); 133 | 134 | if (fs.statSync(componentFilePath)) { 135 | _content += `import ${file} from './src/components/${file}/${file}.svelte';\n`; 136 | defaultExport = `export default ${file};\n` 137 | } 138 | 139 | fs.readdirSync(componentSrc).forEach((srcFile) => { 140 | const fileExt = path.extname(srcFile); 141 | 142 | if (!srcFile.includes('spec') && !srcFile.includes('_') && srcFile !== componentFile && srcFile !== 'Select.svelte' && srcFile !== 'index.js' && srcFile !== 'export.js' && allowedExtensions.includes(fileExt)) { 143 | const basename = path.basename(srcFile, fileExt); 144 | 145 | _content += `import ${basename} from './src/components/${file}/${srcFile}';\n`; 146 | 147 | namedExports += `\n\t${basename},`; 148 | } 149 | }); 150 | 151 | _content += '\n'; 152 | 153 | if (namedExports) _content += `export {${namedExports.replace(/,$/, '')}\n};\n`; 154 | 155 | if (defaultExport) _content += defaultExport; 156 | 157 | if (file !== 'Select') { 158 | fs.writeFileSync(`${file}.mjs`, _content); 159 | } 160 | } 161 | 162 | componentIndex('./src/components'); 163 | getComponents('./src/components'); 164 | moduleExports('./src/components'); 165 | documentCSSVars('./src/components'); 166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltekit/ui", 3 | "description": "Svelte UI Library", 4 | "version": "4.0.0", 5 | "homepage": "https://github.com/sveltekit/ui", 6 | "repository": "git://github.com/sveltekit/ui.git", 7 | "scripts": { 8 | "generateExports": "node build/generate-exports.js", 9 | "build": "npm run generateExports && cross-env NODE_ENV=production webpack --config webpack/webpack.config.js", 10 | "dev": "npm run generateExports && webpack-dev-server --content-base public --config webpack/webpack.config.js", 11 | "test": "webpack --content-base test/public --config webpack/webpack.config.tests.js && node test/runner.js", 12 | "test:browser": "webpack-dev-server --content-base test/public --config webpack/webpack.config.tests.js" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.4.4", 16 | "@fortawesome/fontawesome-free": "^5.11.2", 17 | "autoprefixer": "^9.6.0", 18 | "babel-loader": "^8.0.6", 19 | "cross-env": "^5.2.0", 20 | "css-loader": "^2.1.1", 21 | "css-vars-ponyfill": "^2.0.2", 22 | "dayjs": "^1.8.14", 23 | "eslint": "^6.4.0", 24 | "eslint-plugin-import": "^2.18.2", 25 | "eslint-plugin-svelte3": "^2.7.3", 26 | "flatpickr": "https://github.com/sveltekit/flatpickr.git#master", 27 | "glob": "^7.1.4", 28 | "lodash": "^4.17.11", 29 | "mini-css-extract-plugin": "^0.7.0", 30 | "nouislider": "^11.1.0", 31 | "port-authority": "^1.0.5", 32 | "prismjs": "^1.16.0", 33 | "puppeteer": "^1.17.0", 34 | "randomcolor": "^0.5.4", 35 | "raw-loader": "^3.0.0", 36 | "require-nocache": "^1.0.0", 37 | "serve": "^11.0.0", 38 | "sirv": "^0.4.2", 39 | "style-loader": "^0.23.1", 40 | "svelte": "^3.7.0", 41 | "svelte-icons": "^1.1.0", 42 | "svelte-loader": "^2.13.4", 43 | "svelte-select": "^2.1.0", 44 | "tape-modern": "^1.1.1", 45 | "tinycolor2": "^1.4.1", 46 | "tippy.js": "^2.5.4", 47 | "validate.js": "^0.13.1", 48 | "webpack": "^4.32.0", 49 | "webpack-bundle-analyzer": "^3.3.2", 50 | "webpack-cli": "^3.3.2", 51 | "webpack-dev-server": "^3.4.1", 52 | "wnumb": "^1.1.0" 53 | }, 54 | "license": "MIT", 55 | "keywords": [ 56 | "svelte" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /public/avatar_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltekit/ui/a626fcb428104bd4ff99233aeac162a726f9ab18/public/avatar_example.gif -------------------------------------------------------------------------------- /public/close-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/github-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | padding: 0; 3 | margin: 0; 4 | min-height: 100%; 5 | } 6 | 7 | body { 8 | background: #fff; 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 10 | -webkit-font-smoothing: antialiased; 11 | color: #2c3e50; 12 | font-size: 14px; 13 | padding: 0px; 14 | margin: 0; 15 | } 16 | 17 | *, 18 | ::before, 19 | ::after { 20 | box-sizing: border-box; 21 | } 22 | 23 | code { 24 | font-family: "SFMono-Medium", "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Courier, monospace; 25 | } 26 | 27 | h1 { 28 | font-size: 29px; 29 | font-weight: 900; 30 | line-height: 32px; 31 | margin: 0 0 24px; 32 | } 33 | 34 | h2 { 35 | font-size: 20px; 36 | font-weight: bold; 37 | line-height: 24px; 38 | margin: 0 0 24px; 39 | } 40 | 41 | h3 { 42 | font-size: 16px; 43 | font-weight: bold; 44 | line-height: 20px; 45 | margin: 0 0 12px; 46 | } 47 | 48 | svg { 49 | -webkit-backface-visibility: hidden; 50 | -webkit-transform: translateZ(0) scale(1.0, 1.0); 51 | transform: translateZ(0); 52 | } 53 | 54 | .row { 55 | display: flex; 56 | flex-wrap: wrap; 57 | align-items: center; 58 | margin: 10px -5px; 59 | } 60 | 61 | .row > * { 62 | margin: 5px; 63 | } 64 | 65 | .col { 66 | display: flex; 67 | flex-direction: column; 68 | flex-wrap: nowrap; 69 | justify-content: flex-start; 70 | align-items: stretch; 71 | align-content: stretch; 72 | } 73 | 74 | .col > * { 75 | display: inline-block; 76 | width: auto; 77 | order: 1; 78 | flex-grow: 0; 79 | flex-shrink: 1; 80 | flex-basis: auto; 81 | align-self: auto; 82 | margin: 5px 0; 83 | } 84 | 85 | .col > :first-child { 86 | margin-top: 0; 87 | } 88 | 89 | .col > :last-child { 90 | margin-bottom: 0; 91 | } 92 | 93 | @media(min-width:890px) { 94 | 95 | 96 | 97 | .grid, 98 | .gridRow { 99 | display: grid; 100 | grid-template-columns: repeat(12, 1fr); 101 | grid-column-gap: 10px; 102 | grid-row-gap: 10px; 103 | margin: 10px 0; 104 | } 105 | 106 | .gridRow { 107 | align-items: baseline; 108 | } 109 | 110 | .gridCol-1 { 111 | grid-column: span 1; 112 | } 113 | 114 | .gridCol-2 { 115 | grid-column: span 2; 116 | } 117 | 118 | .gridCol-3 { 119 | grid-column: span 3; 120 | } 121 | 122 | .gridCol-4 { 123 | grid-column: span 4; 124 | } 125 | 126 | .gridCol-5 { 127 | grid-column: span 5; 128 | } 129 | 130 | .gridCol-6 { 131 | grid-column: span 6; 132 | } 133 | 134 | .gridCol-7 { 135 | grid-column: span 7; 136 | } 137 | 138 | .gridCol-8 { 139 | grid-column: span 8; 140 | } 141 | 142 | .gridCol-9 { 143 | grid-column: span 9; 144 | } 145 | 146 | .gridCol-10 { 147 | grid-column: span 10; 148 | } 149 | 150 | .gridCol-11 { 151 | grid-column: span 11; 152 | } 153 | 154 | .gridCol-12 { 155 | grid-column: span 12; 156 | } 157 | } 158 | 159 | :root { 160 | --blue_0: #e7f2ff; 161 | --blue_1: #b9daff; 162 | --blue_2: #73b6ff; 163 | --blue_3: #459eff; 164 | --blue_4: #007aff; 165 | --blue_5: #006fe8; 166 | --blue_6: #0064d1; 167 | --blue_7: #0059ba; 168 | --blue_8: #00438c; 169 | --blue_9: #002d5d; 170 | 171 | --green_0: #cff1d6; 172 | --green_1: #afe8bc; 173 | --green_2: #90dfa1; 174 | --green_3: #70d686; 175 | --green_4: #51ce6c; 176 | --green_5: #43a959; 177 | --green_6: #348445; 178 | --green_7: #2b6d39; 179 | --green_8: #22542c; 180 | --green_9: #183c20; 181 | 182 | --neutral_0: #ebedef; 183 | --neutral_1: #d8dbdf; 184 | --neutral_2: #c5cacf; 185 | --neutral_3: #b2b8bf; 186 | --neutral_4: #78848f; 187 | --neutral_5: #52616f; 188 | --neutral_6: #3f4f5f; 189 | --neutral_7: #2c3e50; 190 | --neutral_8: #212e3b; 191 | --neutral_9: #18222c; 192 | 193 | --red_0: #ffd3db; 194 | --red_1: #ff9fb1; 195 | --red_2: #ff7992; 196 | --red_3: #ff5373; 197 | --red_4: #ff2d55; 198 | --red_5: #e8294e; 199 | --red_6: #d12546; 200 | --red_7: #ac1f3a; 201 | --red_8: #86182d; 202 | --red_9: #5f1120; 203 | 204 | --brown: #6a4000; 205 | --yellow: #ffe6c1; 206 | --silver: #f3f6fc; 207 | --white: #fff; 208 | 209 | --primary_1: var(--blue_4); 210 | --primary_2: var(--green_4, #51ce6c); 211 | --primary_3: var(--neutral_7); 212 | --primary_4: var(--silver); 213 | --primary_5: var(--white); 214 | 215 | --secondary_1: #ff2d55; 216 | --secondary_2: var(--red_4); 217 | --secondary_3: #6554c0; 218 | --secondary_4: var(--orange_4); 219 | } 220 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Svelte Components 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/menu-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/prism-tomorrow.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML 3 | * Based on https://github.com/chriskempson/tomorrow-theme 4 | * @author Rose Pritchard 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #ccc; 10 | background: none; 11 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 12 | font-size: 1em; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | word-wrap: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | } 37 | 38 | :not(pre) > code[class*="language-"], 39 | pre[class*="language-"] { 40 | background: #2d2d2d; 41 | } 42 | 43 | /* Inline code */ 44 | :not(pre) > code[class*="language-"] { 45 | padding: .1em; 46 | border-radius: .3em; 47 | white-space: normal; 48 | } 49 | 50 | .token.comment, 51 | .token.block-comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: #999; 56 | } 57 | 58 | .token.punctuation { 59 | color: #ccc; 60 | } 61 | 62 | .token.tag, 63 | .token.attr-name, 64 | .token.namespace, 65 | .token.deleted { 66 | color: #e2777a; 67 | } 68 | 69 | .token.function-name { 70 | color: #6196cc; 71 | } 72 | 73 | .token.boolean, 74 | .token.number, 75 | .token.function { 76 | color: #f08d49; 77 | } 78 | 79 | .token.property, 80 | .token.class-name, 81 | .token.constant, 82 | .token.symbol { 83 | color: #f8c555; 84 | } 85 | 86 | .token.selector, 87 | .token.important, 88 | .token.atrule, 89 | .token.keyword, 90 | .token.builtin { 91 | color: #cc99cd; 92 | } 93 | 94 | .token.string, 95 | .token.char, 96 | .token.attr-value, 97 | .token.regex, 98 | .token.variable { 99 | color: #7ec699; 100 | } 101 | 102 | .token.operator, 103 | .token.entity, 104 | .token.url { 105 | color: #67cdcc; 106 | } 107 | 108 | .token.important, 109 | .token.bold { 110 | font-weight: bold; 111 | } 112 | .token.italic { 113 | font-style: italic; 114 | } 115 | 116 | .token.entity { 117 | cursor: help; 118 | } 119 | 120 | .token.inserted { 121 | color: green; 122 | } 123 | -------------------------------------------------------------------------------- /src/app/App.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | 61 | 62 |
63 | 66 | 67 |
68 | {#if $activeComponent.length > 0} 69 | 70 | {:else} 71 | 72 | {/if} 73 |
74 |
75 | -------------------------------------------------------------------------------- /src/app/CodeBlock.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | {@html highlighted} 17 | 18 | -------------------------------------------------------------------------------- /src/app/ComponentViewer.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 121 | 122 |
123 | {#if src} 124 |
125 |

{displayName}

126 | 127 | {#each examples as example} 128 |
129 | 133 |
134 | {/each} 135 |
136 | {/if} 137 | 138 | {#if elem && api && elem.$$.props.length > 0} 139 |

API

140 |
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | {#each elem.$$.props as prop} 149 | {#if api[prop]} 150 | 151 | 152 | 153 | {#if api[prop].type === 'function'} 154 | 155 | {:else} 156 | 157 | {/if} 158 | 159 | {/if} 160 | {/each} 161 | 162 |
PropertyTypeDefault
{prop}{api[prop].type}{JSON.stringify(api[prop].default)}{api[prop] ? api[prop].default : '-'}
163 |
164 | {/if} 165 | 166 | {#if options} 167 |

Options

168 | 169 | 170 | 171 | 172 | 173 | 174 | {#each Object.keys(options) as option} 175 | 176 | 177 | 183 | 184 | {/each} 185 | 186 |
KeyConstants
{option} 178 | {#each Object.keys(options[option]) as key} 179 | {key} 180 |   181 | {/each} 182 |
187 | {/if} 188 | 189 | {#if cssVarsJSON.length > 0} 190 |

CSS variables

191 | 192 | 193 | 194 | 195 | 196 | 197 | {#each cssVarsJSON as { varName, defaultValue }} 198 | 199 | 202 | 205 | 206 | {/each} 207 | 208 |
Variable NameDefault value
200 | {varName} 201 | 203 | {defaultValue} 204 |
209 | {/if} 210 | 211 | {#if _activeComponent} 212 | 215 | {/if} 216 |
217 | -------------------------------------------------------------------------------- /src/app/Example.svelte: -------------------------------------------------------------------------------- 1 | 30 | 31 | 52 | 53 | 54 |

{exampleName.split(/(?=[A-Z])/).join(" ")} Example

55 | 56 |
57 | 58 |
59 | 60 | {#if source} 61 |
62 | 63 |
64 | {/if} 65 | -------------------------------------------------------------------------------- /src/app/GettingStarted.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/Home.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 83 | 84 |
85 |
86 |

Powerful, reliable & fully featured Svelte UI library

87 |
88 | 89 |
90 |

Getting started

91 | 92 | $ npm install @sveltekit/ui -D 93 | 94 |

Import a component and it's options (if needed)

95 | 96 | {#if source} 97 |
98 | 99 |
100 | {/if} 101 | 102 |

103 | Navigate the site to see full examples, demos, code samples, api and event 104 | details for each component. 105 |

106 |
107 | 108 |
109 |

Theming

110 | 111 |

112 | We're rolling out CSS variables for each component, they allow you to 113 | override the default theme. If a CSS variable you need is missing feel 114 | free to help out and raise a PR. 115 |

116 | 117 | {#if theming} 118 |
119 | 120 |
121 | {/if} 122 | 123 | 124 |
125 | 126 |
127 |

About Sveltekit

128 |

129 | Started in 2018, Sveltekit has been used in multiple enterprise 130 | applications. The project was open-sourced in November 2019. 131 |

132 |

It's still actively worked on, it's main contributors are:

133 | 176 |
177 |
178 | -------------------------------------------------------------------------------- /src/app/Nav.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 154 | 155 |
156 | {#if !showNavMobile} 157 | menu icon 162 | {:else} 163 | close icon 168 | {/if} 169 | 170 | 171 | Sveltekit Logo 172 |
173 |

Sveltekit

174 |

UI library

175 |
176 |
177 | 178 | 183 | 184 | 196 |
197 | -------------------------------------------------------------------------------- /src/app/PropEditor.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 36 | 37 | {#if props && Object.keys(props).length > 0} 38 | 39 | 40 | 41 | 42 | 43 | 44 | {#each Object.keys(props) as prop} 45 | 46 | 47 | {#if typeof props[prop] === 'string'} 48 | 49 | {:else} 50 | 51 | {/if} 52 | 53 | {/each} 54 | 55 |
PropertyValue
{prop}
56 | {/if} 57 | -------------------------------------------------------------------------------- /src/app/ThemingSample.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/app/stores.js: -------------------------------------------------------------------------------- 1 | import { writable, readable } from 'svelte/store'; 2 | import _components from '../components/index.js'; 3 | 4 | export const components = readable(_components); 5 | export const activeComponent = writable(''); 6 | -------------------------------------------------------------------------------- /src/components/Alert/Alert.spec.js: -------------------------------------------------------------------------------- 1 | import { test, done } from 'tape-modern'; 2 | import Alert from './Alert.svelte'; 3 | import Basic from './examples/Basic.svelte'; 4 | 5 | const testTarget = document.getElementById('testTemplate'); 6 | const componentName = Alert.name; 7 | 8 | test(`${componentName}: with no data creates default elements`, async (t) => { 9 | const avatar = new Alert({ 10 | target: testTarget, 11 | props: {} 12 | }); 13 | 14 | t.ok(testTarget.querySelector('.alert')); 15 | 16 | avatar.$destroy(); 17 | }); 18 | 19 | 20 | test(`${componentName}: should render slot content`, async (t) => { 21 | const avatar = new Basic({ 22 | target: testTarget, 23 | props: { 24 | warning: 'He is on f 🔥 i 🔥 r 🔥 e 🔥 !' 25 | } 26 | }); 27 | 28 | t.equal(testTarget.querySelectorAll('.alert .content')[0].textContent, avatar.warning); 29 | 30 | avatar.$destroy(); 31 | }); 32 | -------------------------------------------------------------------------------- /src/components/Alert/Alert.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | 43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 | Alert slot fallback 51 |
52 |
53 | -------------------------------------------------------------------------------- /src/components/Alert/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | {warning} 10 |
11 | 12 |
13 | 14 | {warning} 15 | 16 |
17 | -------------------------------------------------------------------------------- /src/components/Alert/examples/LongMessage.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | Warning! VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG 8 | MESSAGE VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG 9 | MESSAGE. 10 | 11 |
12 | 13 |
14 | 15 | Information! VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG 16 | MESSAGE VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG MESSAGE VERY LONG 17 | MESSAGE. 18 | 19 |
20 | -------------------------------------------------------------------------------- /src/components/Alert/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: { 3 | WARNING: 'warning', 4 | INFORMATION: 'information' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import Avatar from './Avatar.svelte'; 3 | 4 | const testTarget = document.getElementById('testTemplate'); 5 | const componentName = Avatar.name; 6 | 7 | test(`${componentName}: with no data creates default elements`, async (t) => { 8 | const avatar = new Avatar({ 9 | target: testTarget, 10 | props: {} 11 | }); 12 | 13 | t.ok(testTarget.querySelector('.avatar')); 14 | 15 | avatar.$destroy(); 16 | }); 17 | 18 | test(`${componentName}: prop 'alt' should render`, async (t) => { 19 | const avatar = new Avatar({ 20 | target: testTarget, 21 | props: { 22 | alt: 'RB' 23 | } 24 | }); 25 | 26 | t.ok(testTarget.querySelector('.avatar .alt').innerHTML === 'RB'); 27 | 28 | avatar.$destroy(); 29 | }); 30 | 31 | test(`${componentName}: prop 'size' should change component's size`, async (t) => { 32 | const avatar = new Avatar({ 33 | target: testTarget, 34 | props: { 35 | size: 'medium' 36 | } 37 | }); 38 | 39 | t.ok(testTarget.querySelector('.avatar').offsetWidth === 64); 40 | 41 | avatar.$destroy(); 42 | }); 43 | 44 | test(`${componentName}: prop 'bgColour' should change component's background colour`, async (t) => { 45 | const avatar = new Avatar({ 46 | target: testTarget, 47 | props: { 48 | bgColour: 'yellow' 49 | } 50 | }); 51 | 52 | t.ok(testTarget.querySelector('.avatar').style['background-color'] === 'yellow'); 53 | 54 | avatar.$destroy(); 55 | }); 56 | 57 | 58 | test(`${componentName}: prop 'textColour' should change component's text colour`, async (t) => { 59 | const avatar = new Avatar({ 60 | target: testTarget, 61 | props: { 62 | alt: 'RB', 63 | textColour: 'yellow' 64 | } 65 | }); 66 | 67 | t.ok(testTarget.querySelector('.avatar').style['color'] === 'yellow'); 68 | 69 | avatar.$destroy(); 70 | }); 71 | 72 | 73 | test(`${componentName}: prop 'src' should change component's avatar image`, async (t) => { 74 | const avatar = new Avatar({ 75 | target: testTarget, 76 | props: { 77 | alt: 'RB', 78 | src: '/avatar_example.gif' 79 | } 80 | }); 81 | 82 | t.ok(testTarget.querySelector('.avatar').style['background-image'] === `url("/avatar_example.gif")`); 83 | 84 | avatar.$destroy(); 85 | }); 86 | 87 | 88 | test(`${componentName}: props 'Component' and 'componentProps' should override default component`, async (t) => { 89 | const childComponentAlt = 'xxx'; 90 | 91 | const avatar = new Avatar({ 92 | target: testTarget, 93 | props: { 94 | Component: Avatar, 95 | componentProps: { 96 | alt: childComponentAlt 97 | } 98 | } 99 | }); 100 | 101 | t.ok(testTarget.querySelector('.avatar .avatar .alt').textContent === childComponentAlt); 102 | 103 | avatar.$destroy(); 104 | }); 105 | -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.svelte: -------------------------------------------------------------------------------- 1 | 81 | 82 | 107 | 108 |
109 | {#if !src && !Component} 110 | {AltText} 111 | {:else if Component} 112 |
113 | 114 |
115 | {/if} 116 |
117 | -------------------------------------------------------------------------------- /src/components/Avatar/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Avatar/examples/Sizes.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | 21 |
22 | 26 |
27 | 28 |
29 | {#each Object.entries(options.size) as [sizeKey, size]} 30 | 31 | 32 | 33 | {/each} 34 | 35 | 36 | 37 |
38 | 39 |
40 | {#each Object.entries(options.size) as [sizeKey, size]} 41 | 42 | 43 | 44 | {/each} 45 | 46 | 47 | 48 |
49 | 50 |
51 | {#each Object.entries(options.size) as [sizeKey, size]} 52 | 53 | 54 | 55 | {/each} 56 | 57 | 58 | 59 |
60 | -------------------------------------------------------------------------------- /src/components/Avatar/examples/WithComponent.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/Avatar/examples/WithImage.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/Avatar/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | size: { 3 | X_SMALL: 'xsmall', 4 | SMALL: 'small', 5 | MEDIUM: 'medium', 6 | LARGE: 'large' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Button/Button.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | 3 | import { wait, simulateClick } from 'helpers'; 4 | 5 | import Button from './Button.svelte'; 6 | import options from './options'; 7 | import Basic from './examples/Basic.svelte'; 8 | import Icons from './examples/Icons.svelte'; 9 | import { Alert as Icon } from '@sveltekit/ui/Icons'; 10 | 11 | 12 | const testTarget = document.getElementById('testTemplate'); 13 | const componentName = Button.name; 14 | 15 | test(`${componentName}: basic example renders`, async (t) => { 16 | const button = new Basic({ 17 | target: testTarget 18 | }); 19 | 20 | t.ok(testTarget.querySelector('.button')); 21 | 22 | button.$destroy(); 23 | }); 24 | 25 | test(`${componentName}: prop 'Icon' renders icon on button`, async (t) => { 26 | const button = new Button({ 27 | target: testTarget, 28 | props: { 29 | Icon 30 | } 31 | }); 32 | 33 | t.ok(testTarget.querySelector('.button svg')); 34 | 35 | button.$destroy(); 36 | }); 37 | 38 | test(`${componentName}: prop 'iconPosition' renders icon at correct position`, async (t) => { 39 | const button = new Icons({ 40 | target: testTarget 41 | }); 42 | 43 | const buttons = testTarget.getElementsByClassName('button'); 44 | 45 | t.ok(buttons[0].querySelector('.inner').children[0].innerHTML === 'LARGE'); 46 | t.ok(buttons[1].querySelector('.inner').children[0].classList.contains('icon')); 47 | const thirdButton = buttons[2].querySelector('.inner').children[0]; 48 | t.ok(getComputedStyle(thirdButton).order === '1'); 49 | 50 | 51 | button.$destroy(); 52 | }); 53 | 54 | test(`${componentName}: prop 'isActive' adds class and removes pointer events from component`, async (t) => { 55 | const button = new Button({ 56 | target: testTarget, 57 | props: { 58 | isActive: true 59 | } 60 | }); 61 | 62 | const component = testTarget.querySelector('.button.isActive'); 63 | t.ok(component && getComputedStyle(component)['pointer-events'] === 'none'); 64 | 65 | button.$destroy(); 66 | }); 67 | 68 | test(`${componentName}: prop 'isBlock' adds class and 100% width to component`, async (t) => { 69 | const button = new Button({ 70 | target: testTarget, 71 | props: { 72 | isBlock: true 73 | } 74 | }); 75 | 76 | 77 | const component = testTarget.querySelector('.button.isBlock'); 78 | t.ok(component && getComputedStyle(component)['width'] === getComputedStyle(testTarget)['width']); 79 | 80 | button.$destroy(); 81 | }); 82 | 83 | test(`${componentName}: prop 'isOutlined' adds class and correct styles to component`, async (t) => { 84 | const button = new Button({ 85 | target: testTarget, 86 | props: { 87 | isOutlined: true 88 | } 89 | }); 90 | 91 | const component = testTarget.querySelector('.button.isOutlined'); 92 | t.ok(component && getComputedStyle(component)['border-bottom-color'] === 'rgb(81, 206, 108)'); 93 | 94 | button.$destroy(); 95 | }); 96 | 97 | test(`${componentName}: prop 'isRounded' adds class and correct styles to component`, async (t) => { 98 | const button = new Button({ 99 | target: testTarget, 100 | props: { 101 | isRounded: true 102 | } 103 | }); 104 | 105 | const component = testTarget.querySelector('.button.isRounded'); 106 | t.ok(component && getComputedStyle(component)['border-bottom-left-radius'] === '15px'); 107 | 108 | button.$destroy(); 109 | }); 110 | 111 | test(`${componentName}: prop 'isSelected' adds class and correct styles to component`, async (t) => { 112 | const button = new Button({ 113 | target: testTarget, 114 | props: { 115 | isSelected: true 116 | } 117 | }); 118 | 119 | const component = testTarget.querySelector('.button.isSelected'); 120 | t.ok(component && getComputedStyle(component)['background-color'] === 'rgb(52, 132, 69)'); 121 | 122 | button.$destroy(); 123 | }); 124 | 125 | test(`${componentName}: prop 'isWaiting' adds spinner to component`, async (t) => { 126 | const button = new Button({ 127 | target: testTarget, 128 | props: { 129 | isWaiting: true 130 | } 131 | }); 132 | 133 | const component = testTarget.querySelector('.button .spinner'); 134 | t.ok(component); 135 | 136 | button.$destroy(); 137 | }); 138 | 139 | test(`${componentName}: prop 'isWide' adds spinner to component`, async (t) => { 140 | const button = new Button({ 141 | target: testTarget, 142 | props: { 143 | isWide: true 144 | } 145 | }); 146 | 147 | const component = testTarget.querySelector('.button.isWide'); 148 | t.ok(component && getComputedStyle(component)['padding-left'] === '32px'); 149 | 150 | button.$destroy(); 151 | }); 152 | 153 | test(`${componentName}: prop 'isDisabled' disables the component`, async (t) => { 154 | const button = new Button({ 155 | target: testTarget, 156 | props: { 157 | isDisabled: true 158 | } 159 | }); 160 | 161 | const component = testTarget.querySelector('.button'); 162 | t.ok(component.disabled); 163 | 164 | button.$destroy(); 165 | }); 166 | 167 | test(`${componentName}: prop 'htmlType' applies correct attribute on component`, async (t) => { 168 | const button = new Button({ 169 | target: testTarget, 170 | props: { 171 | htmlType: options.htmlType.BUTTON 172 | } 173 | }); 174 | 175 | t.ok(testTarget.querySelector('.button').getAttribute('type') === 'button'); 176 | 177 | button.$set({htmlType: options.htmlType.SUBMIT}) 178 | 179 | await wait(0); 180 | 181 | t.ok(testTarget.querySelector('.button').getAttribute('type') === 'submit'); 182 | 183 | button.$destroy(); 184 | }); 185 | -------------------------------------------------------------------------------- /src/components/Button/Button.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 | 185 | 186 | 209 | -------------------------------------------------------------------------------- /src/components/Button/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Button/examples/Icons.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |

Different Icon Sets

31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /src/components/Button/examples/Options.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /src/components/Button/examples/SpinningLoader.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 10 | 15 | 16 | 21 |
22 | -------------------------------------------------------------------------------- /src/components/Button/examples/Styling.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/components/Button/examples/Variations.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /src/components/Button/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | htmlType: { 3 | BUTTON: 'button', 4 | SUBMIT: 'submit' 5 | }, 6 | iconPosition: { 7 | ONLY: 'only', 8 | LEFT: 'left', 9 | RIGHT: 'right' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Card/Card.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | 3 | import { wait, simulateClick } from 'helpers'; 4 | 5 | import Card from './Card.svelte'; 6 | import Clickable from './examples/Clickable.svelte'; 7 | import options from './options'; 8 | 9 | const testTarget = document.getElementById('testTemplate'); 10 | const componentName = Card.name; 11 | 12 | test(`${componentName}: renders correctly`, async (t) => { 13 | const card = new Card({ 14 | target: testTarget 15 | }); 16 | 17 | t.ok(testTarget.querySelector('.card')); 18 | 19 | card.$destroy(); 20 | }); 21 | 22 | test(`${componentName}: prop 'level' adds correct class to component`, async (t) => { 23 | const card = new Card({ 24 | target: testTarget, 25 | props: { 26 | level: options.level.THREE 27 | } 28 | }); 29 | 30 | t.ok(testTarget.querySelector('.card.level-3')); 31 | 32 | card.$destroy(); 33 | }); 34 | 35 | test(`${componentName}: prop 'isClickDisabled' disables click event`, async (t) => { 36 | const card = new Clickable({ 37 | target: testTarget, 38 | props: { 39 | isClickDisabled: true 40 | } 41 | }); 42 | 43 | const component = testTarget.querySelector('.card'); 44 | 45 | component.click(); 46 | 47 | t.ok(card.count === 0); 48 | 49 | card.$set({ isClickDisabled: false }); 50 | 51 | await wait(0); 52 | 53 | component.click(); 54 | 55 | t.ok(card.count === 1); 56 | 57 | card.$destroy(); 58 | }); 59 | 60 | test(`${componentName}: prop 'isClickable' adds class to component`, async (t) => { 61 | const card = new Clickable({ 62 | target: testTarget, 63 | props: { 64 | isClickable: true 65 | } 66 | }); 67 | 68 | t.ok(testTarget.querySelector('.card.isClickable')); 69 | 70 | card.$destroy(); 71 | }); 72 | 73 | -------------------------------------------------------------------------------- /src/components/Card/Card.svelte: -------------------------------------------------------------------------------- 1 | 30 | 31 | 62 | 63 |
64 | 65 |
66 | -------------------------------------------------------------------------------- /src/components/Card/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | 13 |
14 |

Slot data here...

15 |
16 |
17 | -------------------------------------------------------------------------------- /src/components/Card/examples/Clickable.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 |
16 | 17 |
18 |

Clickable

19 |

Click to increase count

20 |
21 |
22 | 23 | 24 |
25 |

Click count: { count }

26 |
27 |
28 |
29 | 30 | -------------------------------------------------------------------------------- /src/components/Card/examples/Levels.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 |
12 | {#each Object.entries(options.level) as [levelKey, levelValue]} 13 | 14 | 15 |
16 |

Card

17 |

card.options.level.{ levelKey }

18 |
19 |
20 |
21 | {/each} 22 |
23 | -------------------------------------------------------------------------------- /src/components/Card/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | level: { 3 | ONE: 1, 4 | TWO: 2, 5 | THREE: 3 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Checkbox/Checkbox.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import Checkbox from './Checkbox.svelte'; 3 | 4 | const testTarget = document.getElementById('testTemplate'); 5 | const componentName = Checkbox.name; 6 | 7 | test(`${componentName}: renders correctly`, async (t) => { 8 | const checkbox = new Checkbox({ 9 | target: testTarget 10 | }); 11 | 12 | t.ok(testTarget.querySelector('.checkbox-container')); 13 | 14 | checkbox.$destroy(); 15 | }); 16 | 17 | 18 | test(`${componentName}: prop 'name' sets name attribute`, async (t) => { 19 | const checkbox = new Checkbox({ 20 | target: testTarget, 21 | props: { 22 | name: 'Foo' 23 | } 24 | }); 25 | 26 | t.ok(testTarget.querySelector('.checkbox-container input').getAttribute('name') === 'Foo'); 27 | 28 | checkbox.$destroy(); 29 | }); 30 | 31 | test(`${componentName}: prop 'isChecked' sets attribute on input`, async (t) => { 32 | const checkbox = new Checkbox({ 33 | target: testTarget, 34 | props: { 35 | isChecked: true 36 | } 37 | }); 38 | 39 | t.ok(testTarget.querySelector('.checkbox-container input').checked); 40 | 41 | checkbox.$destroy(); 42 | }); 43 | 44 | test(`${componentName}: prop 'isDisabled' disables input`, async (t) => { 45 | const checkbox = new Checkbox({ 46 | target: testTarget, 47 | props: { 48 | isDisabled: true 49 | } 50 | }); 51 | 52 | t.ok(testTarget.querySelector('.checkbox-container input').disabled); 53 | 54 | checkbox.$destroy(); 55 | }); 56 | -------------------------------------------------------------------------------- /src/components/Checkbox/Checkbox.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 74 | 75 | 76 | dispatch('input', event)} 82 | on:change={event => dispatch('change', event)} /> 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/components/Checkbox/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 21 | -------------------------------------------------------------------------------- /src/components/Checkbox/examples/Disabled.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 35 | 36 |

37 | 40 | 41 | 44 |

45 | -------------------------------------------------------------------------------- /src/components/Chip/Chip.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import Chip from './Chip.svelte'; 3 | 4 | const testTarget = document.getElementById('testTemplate'); 5 | const componentName = Chip.name; 6 | 7 | test(`${componentName}: renders correctly`, async (t) => { 8 | const chip = new Chip({ 9 | target: testTarget 10 | }); 11 | 12 | t.ok(testTarget.querySelector('.chip')); 13 | 14 | chip.$destroy(); 15 | }); 16 | 17 | test(`${componentName}: prop 'avatar' renders avatar inside component`, async (t) => { 18 | const chip = new Chip({ 19 | target: testTarget, 20 | props: { 21 | avatar: { 22 | src: '/avatar_example.gif', 23 | alt: 'XY' 24 | } 25 | } 26 | }); 27 | 28 | t.ok(testTarget.querySelector('.chip div.avatar').style['backgroundImage'] === `url("/avatar_example.gif")`); 29 | 30 | chip.$destroy(); 31 | }); 32 | 33 | test(`${componentName}: prop 'isRemovable' allows component to be removed`, async (t) => { 34 | const chip = new Chip({ 35 | target: testTarget, 36 | props: { 37 | isRemovable: true 38 | } 39 | }); 40 | 41 | let eventReceived = false; 42 | 43 | const CancelListener = chip.$on('remove', (event) => { 44 | eventReceived = true; 45 | }) 46 | 47 | testTarget.querySelector('.chip .removeIcon').click(); 48 | t.ok(eventReceived); 49 | 50 | CancelListener(); 51 | chip.$destroy(); 52 | }); 53 | 54 | test(`${componentName}: prop 'isDisabled' disables component`, async (t) => { 55 | const chip = new Chip({ 56 | target: testTarget, 57 | props: { 58 | isDisabled: true 59 | } 60 | }); 61 | 62 | t.ok(testTarget.querySelector('.chip button').disabled); 63 | 64 | chip.$destroy(); 65 | }); 66 | 67 | test(`${componentName}: prop 'isWaiting' and 'isRemovable' shows loader on component`, async (t) => { 68 | const chip = new Chip({ 69 | target: testTarget, 70 | props: { 71 | isRemovable: true, 72 | isWaiting: true 73 | } 74 | }); 75 | 76 | t.ok(testTarget.querySelector('.chip .spinner')); 77 | 78 | chip.$destroy(); 79 | }); 80 | 81 | test(`${componentName}: prop 'isWaiting' and 'isRemovable' shows loader on component`, async (t) => { 82 | const chip = new Chip({ 83 | target: testTarget, 84 | props: { 85 | isRemovable: true, 86 | isWaiting: true 87 | } 88 | }); 89 | 90 | t.ok(testTarget.querySelector('.chip .spinner')); 91 | 92 | chip.$destroy(); 93 | }); 94 | 95 | test(`${componentName}: prop 'isActive' adds correct class on component`, async (t) => { 96 | const chip = new Chip({ 97 | target: testTarget, 98 | props: { 99 | isActive: true 100 | } 101 | }); 102 | 103 | t.ok(testTarget.querySelector('.chip .isActive')); 104 | 105 | chip.$destroy(); 106 | }); 107 | -------------------------------------------------------------------------------- /src/components/Chip/Chip.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | 87 | 88 | 89 |
90 | 102 | 103 | { #if isRemovable && !isWaiting } 104 | 105 | 106 | 107 | { /if } 108 | 109 | { #if isRemovable && isWaiting } 110 |
111 | 112 |
113 | {/if} 114 |
115 | -------------------------------------------------------------------------------- /src/components/Chip/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | Default 8 | 9 | 10 | Active 11 | 12 | 13 | Disabled 14 | 15 |
16 | -------------------------------------------------------------------------------- /src/components/Chip/examples/IsWaiting.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 | 20 | Removable Chip 21 | 22 |
23 | -------------------------------------------------------------------------------- /src/components/Chip/examples/Removable.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 | Removable Chip 12 | 13 | 14 | Removable Chip 15 | 16 |
17 | -------------------------------------------------------------------------------- /src/components/Chip/examples/WithAvatar.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 | 17 | 18 | Chip with Avatar image 19 | 20 | 21 | 22 | 23 | 24 | Chip with Avatar text 25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /src/components/ContentSwitcher/ContentSwitcher.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | 3 | import { wait, simulateClick } from 'helpers'; 4 | 5 | import ContentSwitcher from './ContentSwitcher.svelte'; 6 | import CarIcon from '../Icons/Car.svelte'; 7 | 8 | const testTarget = document.getElementById('testTemplate'); 9 | const componentName = ContentSwitcher.name; 10 | 11 | test(`${componentName}: renders correctly`, async (t) => { 12 | const contentSwitcher = new ContentSwitcher({ 13 | target: testTarget 14 | }); 15 | 16 | t.ok(testTarget.querySelector('.contentSwitcher')); 17 | 18 | contentSwitcher.$destroy(); 19 | }); 20 | 21 | 22 | test(`${componentName}: prop 'items' controls content options`, async (t) => { 23 | const contentSwitcher = new ContentSwitcher({ 24 | target: testTarget, 25 | props: { 26 | items: [ 27 | { 28 | label: 'Car' 29 | }, 30 | { 31 | label: 'Hotel' 32 | }, 33 | { 34 | label: 'Flight' 35 | } 36 | ] 37 | } 38 | }); 39 | 40 | t.ok(testTarget.querySelector('.contentSwitcher').getElementsByClassName('contentSwitcher_item').length === 3); 41 | 42 | contentSwitcher.$destroy(); 43 | }); 44 | 45 | 46 | test(`${componentName}: props 'activeItem' sets item as active`, async (t) => { 47 | const contentSwitcher = new ContentSwitcher({ 48 | target: testTarget, 49 | props: { 50 | activeItem: { 51 | label: 'Hotel', 52 | }, 53 | items: [ 54 | { 55 | label: 'Car' 56 | }, 57 | { 58 | label: 'Hotel' 59 | }, 60 | { 61 | label: 'Flight' 62 | } 63 | ] 64 | } 65 | }); 66 | 67 | t.ok(testTarget.querySelector('.active .contentSwitcher_item_label').innerHTML === 'Hotel'); 68 | 69 | contentSwitcher.$destroy(); 70 | }); 71 | 72 | test(`${componentName}: clicking item changes 'activeItem'`, async (t) => { 73 | const contentSwitcher = new ContentSwitcher({ 74 | target: testTarget, 75 | props: { 76 | items: [ 77 | { 78 | label: 'Car' 79 | }, 80 | { 81 | label: 'Hotel' 82 | }, 83 | { 84 | label: 'Flight' 85 | } 86 | ] 87 | } 88 | }); 89 | 90 | testTarget.querySelector('.contentSwitcher').getElementsByClassName('contentSwitcher_item')[1].click(); 91 | await wait(0); 92 | t.ok(testTarget.querySelector('.active .contentSwitcher_item_label').innerHTML === 'Hotel'); 93 | 94 | contentSwitcher.$destroy(); 95 | }); 96 | -------------------------------------------------------------------------------- /src/components/ContentSwitcher/ContentSwitcher.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 89 | 90 | 91 |
92 | {#each items as item, i} 93 |
94 | {#if item.Icon} 95 |
96 | 99 | 100 |
101 | {/if} 102 | {#if item.label} 103 |
104 | { item.label } 105 |
106 | {/if} 107 |
108 | {/each} 109 |
110 | -------------------------------------------------------------------------------- /src/components/ContentSwitcher/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/DatePicker/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |

Date: {date}

11 |
12 |
13 | -------------------------------------------------------------------------------- /src/components/DatePicker/examples/DateFormat.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 | 11 |

Date: {date}

12 |
13 |
14 | -------------------------------------------------------------------------------- /src/components/DatePicker/examples/DayNavigator.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |

Date: {date}

11 |
12 |
13 | -------------------------------------------------------------------------------- /src/components/DatePicker/examples/Inline.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |

Date: {date}

11 |
12 |
13 | -------------------------------------------------------------------------------- /src/components/DatePicker/examples/MinMax.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 12 |

Date: {date}

13 |

minDate: {minDate}

14 |

maxDate: {maxDate}

15 |
16 |
17 | -------------------------------------------------------------------------------- /src/components/DatePicker/examples/Range.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 | 11 |

Date: {date}

12 |

toDate: {toDate}

13 |
14 |
15 | -------------------------------------------------------------------------------- /src/components/DatePicker/examples/TextOnly.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 | 11 |

Date: {date}

12 |
13 |
14 | -------------------------------------------------------------------------------- /src/components/Dropdown/_DropdownMenu.svelte: -------------------------------------------------------------------------------- 1 | 68 | 69 | 77 | 78 | 114 | -------------------------------------------------------------------------------- /src/components/Dropdown/_DropdownMenuDivider.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/Dropdown/_DropdownMenuItem.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/Custom.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/Hover.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/IsBlock.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 34 | 35 | 36 |
BLOCK TOGGLE (I get removed at 640px)
37 |
38 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/IsMulti.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/IsSearchable.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/ItemKey.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/MaxHeight.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/SelectedItem.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | 27 |

selectedItem: {selectedItem.label}

28 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/SelectedItems.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 |

selectedItem:

29 |
    30 | {#each selectedItem as item} 31 |
  • {item.label}
  • 32 | {/each} 33 |
34 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/TotallyCustom.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/Width.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/_CustomDropdown.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | -------------------------------------------------------------------------------- /src/components/Dropdown/examples/_CustomList.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | 23 | 24 | Here we go 25 | Here we go 26 | 27 | Im not clickable 28 | 29 | Here we go 30 | Here we go 31 |
32 | Im totally freeballing 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/components/Dropdown/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | placement: { 3 | BOTTOM_LEFT: 'bottomLeft', 4 | BOTTOM_CENTRE: 'bottomCentre', 5 | BOTTOM_RIGHT: 'bottomRight', 6 | TOP_LEFT: 'topLeft', 7 | TOP_CENTRE: 'topCentre', 8 | TOP_RIGHT: 'topRight' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Form/Form.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 58 | 59 |
60 | 61 |
62 | -------------------------------------------------------------------------------- /src/components/Form/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 |
18 |
19 | 20 |
21 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /src/components/Icons/Add.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Alert.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/ArrowDown.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/ArrowUp.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Car.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Check.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/ChevronLeft.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/ChevronRight.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Clock.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Close.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Copy.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Date.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Heart.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Hotel.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Icons.svelte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltekit/ui/a626fcb428104bd4ff99233aeac162a726f9ab18/src/components/Icons/Icons.svelte -------------------------------------------------------------------------------- /src/components/Icons/Information.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Lock.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/More.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Plane.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Remove.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/Search.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Icons/examples/AllIcons.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 41 | 42 |
43 | {#each Object.keys(Icons) as iconKey} 44 |
45 | 46 |
47 | { #if iconKey !== 'default' } 48 | 49 |

{iconKey}

50 | { /if } 51 |
52 |
53 |
54 | {/each} 55 |
56 | -------------------------------------------------------------------------------- /src/components/Modal/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/Modal/examples/Props.svelte: -------------------------------------------------------------------------------- 1 | 40 | 41 | 50 | 51 |
52 | {#each Object.entries(modalProps) as [propName, propValue]} 53 | 57 | {/each} 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /src/components/Modal/modal.js: -------------------------------------------------------------------------------- 1 | import _noop from 'lodash/noop'; 2 | import { detach, insert, noop } from 'svelte/internal'; 3 | 4 | function createSlots(slots) { //https://github.com/sveltejs/svelte/issues/2588 5 | const svelteSlots = {}; 6 | 7 | for (const slotName in slots) { 8 | svelteSlots[slotName] = [createSlotFn(slots[slotName])]; 9 | } 10 | 11 | function createSlotFn(element) { 12 | return function () { 13 | return { 14 | c: noop, 15 | 16 | m: function mount(target, anchor) { 17 | insert(target, element, anchor); 18 | }, 19 | 20 | d: function destroy(detaching) { 21 | // not sure if we need this still? 22 | // if (detaching) { 23 | // console.log('element :', element); 24 | // // detach(element); 25 | // } 26 | }, 27 | 28 | l: noop, 29 | }; 30 | } 31 | } 32 | return svelteSlots; 33 | } 34 | 35 | const config = { 36 | appElem: null 37 | }; 38 | 39 | const modal = { 40 | component: null, 41 | resolve: _noop, 42 | reject: _noop 43 | }; 44 | 45 | function cancel(reason) { 46 | remove(); 47 | if (modal) modal.reject(reason); 48 | } 49 | 50 | function complete(value) { 51 | remove(); 52 | modal.resolve(value); 53 | } 54 | 55 | function remove() { 56 | if (!modal.component) return; 57 | 58 | document.body.removeChild(modal.component.targetElem); 59 | modal.component.targetElem.removeEventListener('keydown', onkeydown); 60 | modal.component.$destroy(); 61 | modal.component = null; 62 | document.body.style.overflow = ''; 63 | } 64 | 65 | function open(ModalComponent, options = {}, content) { 66 | let defer = new Promise((resolve, reject) => { 67 | modal.resolve = resolve; 68 | modal.reject = reject; 69 | }); 70 | 71 | const targetElem = document.createElement('div'); 72 | const contentFrag = document.createDocumentFragment(); 73 | const contentElem = document.createElement('div'); 74 | contentElem.innerHTML = content; 75 | contentFrag.appendChild(contentElem); 76 | 77 | const props = Object.assign(options, { 78 | complete, 79 | cancel 80 | }); 81 | props.targetElem = targetElem; 82 | props.$$slots = createSlots({ default: contentFrag }) 83 | props.$$scope = {}; 84 | 85 | modal.component = new ModalComponent({ 86 | target: targetElem, 87 | props, 88 | }); 89 | 90 | targetElem.addEventListener('keydown', onkeydown); 91 | document.body.style.overflow = 'hidden'; 92 | document.body.appendChild(targetElem); 93 | 94 | return defer; 95 | } 96 | 97 | function options(opts) { 98 | Object.assign(config, opts); 99 | } 100 | const focusableSelectors = [ 101 | 'button:not(:disabled)', 102 | '[href]', 103 | 'input:not(:disabled)', 104 | 'select:not(:disabled)', 105 | 'textarea:not(:disabled)', 106 | '[tabindex]:not([tabindex="-1"]):not(:disabled)' 107 | ]; 108 | 109 | function onkeydown(event) { 110 | const focusable = this.querySelectorAll(focusableSelectors.join(',')); 111 | const isTab = event.key === 'Tab'; 112 | const isTabForward = isTab && !event.shiftKey; 113 | const isTabBackward = isTab && event.shiftKey; 114 | const firstFocusableElem = focusable[0]; 115 | const lastFocusableElem = focusable[focusable.length - 1]; 116 | 117 | if (isTabForward && document.activeElement === lastFocusableElem) { 118 | event.preventDefault(); 119 | firstFocusableElem.focus(); 120 | } 121 | 122 | if (isTabBackward && document.activeElement === firstFocusableElem) { 123 | event.preventDefault(); 124 | lastFocusableElem.focus(); 125 | } 126 | } 127 | 128 | export default { 129 | open, 130 | remove, 131 | options 132 | } 133 | -------------------------------------------------------------------------------- /src/components/Notification/Notification.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from "tape-modern"; 2 | import { wait, simulateClick } from "helpers"; 3 | 4 | import Notification from "./Notification.svelte"; 5 | import notification from "./notification"; 6 | import options from "./options"; 7 | 8 | const componentName = Notification.name; 9 | let title = 'Test title'; 10 | let text = 'Test text'; 11 | let isTimedAction = false; 12 | 13 | test(`${componentName}: opens correctly`, async t => { 14 | let key = notification.generateKey(); 15 | 16 | notification 17 | .open({ 18 | title, 19 | text, 20 | key, 21 | isTimedAction, 22 | actions: {} 23 | }) 24 | .then(() => {}) 25 | .catch(() => {}) 26 | .finally(() => { 27 | key = null; 28 | }); 29 | 30 | t.ok(document.querySelector('.notification')); 31 | 32 | notification.cancel(key); 33 | }); 34 | 35 | test(`${componentName}: closes correctly`, async t => { 36 | let key = notification.generateKey(); 37 | 38 | notification 39 | .open({ 40 | title, 41 | text, 42 | key, 43 | isTimedAction, 44 | actions: {} 45 | }) 46 | .then(() => { 47 | }) 48 | .catch(() => { 49 | }) 50 | .finally(() => { 51 | key = null; 52 | }); 53 | 54 | t.ok(document.querySelector('.notification')); 55 | 56 | notification.cancel(key); 57 | 58 | await wait(800); 59 | 60 | t.ok(!document.querySelector('.notification')); 61 | }); 62 | 63 | test(`${componentName}: isClosable shows close button and closes correctly when clicked`, async t => { 64 | let key = notification.generateKey(); 65 | 66 | notification 67 | .open({ 68 | title, 69 | text, 70 | key, 71 | isTimedAction, 72 | isClosable: true, 73 | }) 74 | .then(() => { 75 | }) 76 | .catch(() => { 77 | }) 78 | .finally(() => { 79 | key = null; 80 | }); 81 | 82 | t.ok(document.querySelector('.closer')); 83 | await wait(0); 84 | document.querySelector('.closer button').click(); 85 | await wait(0); 86 | t.ok(key === null); 87 | }); 88 | 89 | test(`${componentName}: isDark sets dark theme`, async t => { 90 | let key = notification.generateKey(); 91 | 92 | notification 93 | .open({ 94 | title, 95 | text, 96 | key, 97 | isTimedAction, 98 | isDark: true, 99 | }) 100 | .then(() => { 101 | }) 102 | .catch(() => { 103 | }) 104 | .finally(() => { 105 | key = null; 106 | }); 107 | 108 | t.ok(document.querySelector('.isDark')); 109 | }); 110 | 111 | test(`${componentName}: isLoading displays loading indicator`, async t => { 112 | let key = notification.generateKey(); 113 | 114 | notification 115 | .open({ 116 | title, 117 | text, 118 | key, 119 | isLoading: true, 120 | isTimedAction 121 | }) 122 | .then(() => { 123 | }) 124 | .catch(() => { 125 | }) 126 | .finally(() => { 127 | key = null; 128 | }); 129 | 130 | t.ok(document.querySelector('.isLoading')); 131 | 132 | notification.cancel(key); 133 | }); 134 | 135 | test(`${componentName}: if isTimedAction isTimedAction.cancel fires after time and removeDelay sets remove delay time`, async t => { 136 | let key = notification.generateKey(); 137 | 138 | notification 139 | .open({ 140 | title, 141 | text, 142 | key, 143 | removeDelay: 0, 144 | isTimedAction: true, 145 | }) 146 | .then(() => { 147 | }) 148 | .catch(() => { 149 | }) 150 | .finally(() => { 151 | key = null; 152 | }); 153 | 154 | await wait(700); 155 | t.ok(key === null); 156 | }); 157 | 158 | test(`${componentName}: button added for each action method in actions prop`, async t => { 159 | let key = notification.generateKey(); 160 | 161 | notification 162 | .open({ 163 | title, 164 | text, 165 | key, 166 | actions: { 167 | foo: () => {}, 168 | bar: () => {}, 169 | }, 170 | }) 171 | 172 | t.ok(document.querySelectorAll('.actions button').length === 2); 173 | await wait(0); 174 | notification.cancel(key); 175 | }); 176 | 177 | test(`${componentName}: title and text props display on notification`, async t => { 178 | let key = notification.generateKey(); 179 | 180 | notification 181 | .open({ 182 | title, 183 | text, 184 | key, 185 | isTimedAction: true 186 | }) 187 | 188 | t.ok(document.querySelector('.title').innerHTML === title); 189 | t.ok(document.querySelector('.text').innerHTML === text); 190 | notification.cancel(key); 191 | }); 192 | 193 | test(`${componentName}: placement prop correctly positions notification`, async t => { 194 | let key = notification.generateKey(); 195 | 196 | notification 197 | .open({ 198 | title, 199 | text, 200 | key, 201 | placement: options.placement.TOP_LEFT 202 | }) 203 | 204 | t.ok(document.querySelector('.placement-topLeft')); 205 | notification.cancel(key); 206 | }); 207 | -------------------------------------------------------------------------------- /src/components/Notification/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 75 | 76 | 85 | 86 |
87 | 91 | 92 | 96 | 97 | 101 | 102 | 106 |
107 | 108 |
109 | 110 | 111 |
112 | 113 |
114 | 115 | 116 | 122 |
123 | 124 |
125 | 126 | 127 |
128 | 129 | {#if currentKey} 130 |
131 | 132 |

{currentKey}

133 |
134 | {/if} 135 | -------------------------------------------------------------------------------- /src/components/Notification/notification.js: -------------------------------------------------------------------------------- 1 | import Notification from './Notification.svelte'; 2 | 3 | let active = []; 4 | 5 | function open(options) { 6 | const target = document.createElement('div'); 7 | const props = options; 8 | props.targetElem = target; 9 | 10 | const notification = new Notification({ 11 | target, 12 | props 13 | }); 14 | 15 | document.body.appendChild(target); 16 | 17 | const onRemove = notification.$on('remove', () => { 18 | onRemove(); 19 | remove(notification); 20 | }); 21 | 22 | const onDestroyListener = notification.$on('exit', () => { 23 | onDestroyListener(); 24 | destroy(notification); 25 | }); 26 | 27 | 28 | active.forEach((activeNotification) => { 29 | if (!activeNotification.isExiting) { 30 | activeNotification.remove(); 31 | } 32 | }); 33 | 34 | active = active.concat(notification); 35 | return notification.promise; 36 | } 37 | 38 | function complete(key, value) { 39 | const activeNotification = key && active.find((notification) => { 40 | return notification.key === key; 41 | }); 42 | 43 | activeNotification && activeNotification.complete(value); 44 | } 45 | 46 | function cancel(key, value) { 47 | const activeNotification = key && active.find((notification) => { 48 | return notification.key === key; 49 | }); 50 | 51 | activeNotification && activeNotification.cancel(value); 52 | } 53 | 54 | function destroy(notification) { 55 | const { targetElem } = notification; 56 | 57 | notification.$destroy(); 58 | document.body.removeChild(targetElem); 59 | } 60 | 61 | function remove(notification) { 62 | active = active.filter((activeNotification) => { 63 | return activeNotification !== notification; 64 | }); 65 | } 66 | 67 | function generateKey() { 68 | return `notification${Date.now()}`; 69 | } 70 | 71 | export default { 72 | open, 73 | complete, 74 | cancel, 75 | generateKey 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Notification/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | placement: { 3 | BOTTOM_LEFT: 'bottomLeft', 4 | BOTTOM_RIGHT: 'bottomRight', 5 | TOP_LEFT: 'topLeft', 6 | TOP_RIGHT: 'topRight' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/NumberInput/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Pagination/Pagination.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import { wait } from 'helpers'; 3 | import Pagination from './Pagination.svelte'; 4 | 5 | const testTarget = document.getElementById('testTemplate'); 6 | const componentName = Pagination.name; 7 | 8 | const total = 100; 9 | const pageSize = 10; 10 | const current = 1; 11 | 12 | test(`${componentName}: displays correct summary`, async (t) => { 13 | const pagination = new Pagination({ 14 | target: testTarget, 15 | props: { 16 | current, 17 | pageSize, 18 | total 19 | } 20 | }); 21 | 22 | const summaryLabel = testTarget.querySelector('.summary').textContent; 23 | t.equal(summaryLabel, `1 - 10 of 100`); 24 | 25 | pagination.$destroy(); 26 | }); 27 | 28 | test(`${componentName}: summary updates on page navigation`, async (t) => { 29 | const pagination = new Pagination({ 30 | target: testTarget, 31 | props: { 32 | current, 33 | pageSize, 34 | total 35 | } 36 | }); 37 | 38 | const nextButton = testTarget.querySelector('.navigation .arrow:nth-child(2) button'); 39 | nextButton.click(); 40 | 41 | await wait(0); 42 | 43 | const summaryLabel = testTarget.querySelector('.summary').textContent; 44 | t.equal(summaryLabel, '11 - 20 of 100'); 45 | 46 | pagination.$destroy(); 47 | }); 48 | 49 | test(`${componentName}: next button is disabled on last page`, async (t) => { 50 | const pagination = new Pagination({ 51 | target: testTarget, 52 | props: { 53 | current, 54 | pageSize, 55 | total 56 | } 57 | }); 58 | 59 | pagination.$set({current: 10}); 60 | 61 | await wait(0); 62 | 63 | const nextButton = testTarget.querySelector('.navigation .arrow:nth-child(2) button'); 64 | t.ok(nextButton.disabled === true); 65 | 66 | pagination.$destroy(); 67 | }); 68 | 69 | test(`${componentName}: does not display summary`, async (t) => { 70 | const pagination = new Pagination({ 71 | target: testTarget, 72 | props: { 73 | current, 74 | pageSize, 75 | total, 76 | showSummary: false 77 | } 78 | }); 79 | 80 | t.ok(testTarget.querySelector('.summary') === null); 81 | 82 | pagination.$destroy(); 83 | }); 84 | 85 | test(`${componentName}: does not display pages`, async (t) => { 86 | const pagination = new Pagination({ 87 | target: testTarget, 88 | props: { 89 | current, 90 | pageSize, 91 | total, 92 | showPages: false 93 | } 94 | }); 95 | 96 | t.ok(testTarget.querySelector('.pages') === null); 97 | 98 | pagination.$destroy(); 99 | }); 100 | 101 | test(`${componentName}: does not display navigation`, async (t) => { 102 | const pagination = new Pagination({ 103 | target: testTarget, 104 | props: { 105 | current, 106 | pageSize, 107 | total, 108 | canNavigate: false 109 | } 110 | }); 111 | 112 | t.ok(testTarget.querySelector('.navigation') === null); 113 | 114 | pagination.$destroy(); 115 | }); 116 | 117 | test(`${componentName}: button type is updated`, async (t) => { 118 | const pagination = new Pagination({ 119 | target: testTarget, 120 | props: { 121 | current, 122 | pageSize, 123 | total, 124 | buttonType: 'primary' 125 | } 126 | }); 127 | 128 | t.ok(testTarget.querySelector('.button').classList.contains('type-primary'), `buttons have class 'type-primary'`); 129 | 130 | pagination.$destroy(); 131 | }); 132 | 133 | test(`${componentName}: active page button is selected`, async (t) => { 134 | const pagination = new Pagination({ 135 | target: testTarget, 136 | props: { 137 | current, 138 | pageSize, 139 | total, 140 | buttonType: 'primary' 141 | } 142 | }); 143 | 144 | const page3Button = testTarget.querySelector('.pages .page:nth-child(3) button'); 145 | page3Button.click(); 146 | 147 | await wait(0); 148 | 149 | t.ok(page3Button.classList.contains('isSelected')); 150 | 151 | pagination.$destroy(); 152 | }); 153 | -------------------------------------------------------------------------------- /src/components/Pagination/Pagination.svelte: -------------------------------------------------------------------------------- 1 | 93 | 94 | 95 | 139 | 140 | 177 | -------------------------------------------------------------------------------- /src/components/Pagination/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/ProgressBar/ProgressBar.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import { wait } from 'helpers'; 3 | import ProgressBar from './ProgressBar.svelte'; 4 | 5 | const testTarget = document.getElementById('testTemplate'); 6 | const componentName = ProgressBar.name; 7 | 8 | const maxValue = 100; 9 | const value = 10; 10 | 11 | test(`${componentName}: size is set`, async (t) => { 12 | const progressBar = new ProgressBar({ 13 | target: testTarget, 14 | props: { 15 | size: 'large', 16 | maxValue, 17 | value 18 | } 19 | }); 20 | 21 | t.ok(testTarget.querySelector('.progressBar').classList.contains('size-large')); 22 | 23 | progressBar.$destroy(); 24 | }); 25 | 26 | test(`${componentName}: bar has correct width`, async (t) => { 27 | const progressBar = new ProgressBar({ 28 | target: testTarget, 29 | props: { 30 | maxValue, 31 | value 32 | } 33 | }); 34 | 35 | t.ok(true); 36 | 37 | progressBar.$destroy(); 38 | }); 39 | 40 | test(`${componentName}: indicators are displayed`, async (t) => { 41 | const progressBar = new ProgressBar({ 42 | target: testTarget, 43 | props: { 44 | maxValue, 45 | value, 46 | indicators: [10,20,30,40,50] 47 | } 48 | }); 49 | 50 | t.ok(testTarget.querySelectorAll('.indicator').length === 5); 51 | 52 | progressBar.$destroy(); 53 | }); 54 | 55 | test(`${componentName}: bar is negative colour when target not met`, async (t) => { 56 | const positiveColour = 'yellow'; 57 | const negativeColour = 'black'; 58 | 59 | const progressBar = new ProgressBar({ 60 | target: testTarget, 61 | props: { 62 | maxValue, 63 | value, 64 | positiveColour, 65 | negativeColour, 66 | target: 50 67 | } 68 | }); 69 | 70 | t.ok(testTarget.querySelector('.bar').style.backgroundColor === negativeColour); 71 | 72 | progressBar.$destroy(); 73 | }); 74 | 75 | test(`${componentName}: bar is positive colour when target is met`, async (t) => { 76 | const positiveColour = 'yellow'; 77 | const negativeColour = 'black'; 78 | 79 | const progressBar = new ProgressBar({ 80 | target: testTarget, 81 | props: { 82 | maxValue, 83 | value: 90, 84 | positiveColour, 85 | negativeColour, 86 | target: 50 87 | } 88 | }); 89 | 90 | t.ok(testTarget.querySelector('.bar').style.backgroundColor === positiveColour); 91 | 92 | progressBar.$destroy(); 93 | }); 94 | 95 | 96 | test(`${componentName}: bar is animated`, async (t) => { 97 | const progressBar = new ProgressBar({ 98 | target: testTarget, 99 | props: { 100 | isAnimated: true, 101 | maxValue, 102 | value: maxValue 103 | } 104 | }); 105 | 106 | t.ok(testTarget.querySelector('.bar').classList.contains('isAnimated')); 107 | 108 | progressBar.$destroy(); 109 | }); 110 | -------------------------------------------------------------------------------- /src/components/ProgressBar/ProgressBar.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 | 48 | 97 | 98 |
99 |
100 | 101 | {#each indicators as indicator} 102 | 103 | {/each} 104 |
105 | -------------------------------------------------------------------------------- /src/components/ProgressBar/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 |
12 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /src/components/ProgressBar/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | size: { 3 | X_SMALL: 'xsmall', 4 | SMALL: 'small', 5 | MEDIUM: 'medium', 6 | LARGE: 'large' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Radio/Radio.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import Radio from './Radio.svelte'; 3 | 4 | const testTarget = document.getElementById('testTemplate'); 5 | const componentName = Radio.name; 6 | 7 | test(`${componentName}: renders correctly`, async (t) => { 8 | const radio = new Radio({ 9 | target: testTarget 10 | }); 11 | 12 | t.ok(testTarget.querySelector('input[type="radio"]')); 13 | 14 | radio.$destroy(); 15 | }); 16 | 17 | test(`${componentName}: prop 'name' sets name attribute`, async (t) => { 18 | const name = 'foo'; 19 | 20 | const radio = new Radio({ 21 | target: testTarget, 22 | props: { 23 | name 24 | } 25 | }); 26 | 27 | t.ok(testTarget.querySelector('input').getAttribute('name') === name); 28 | 29 | radio.$destroy(); 30 | }); 31 | 32 | test(`${componentName}: prop 'isChecked' sets attribute on input`, async (t) => { 33 | const radio = new Radio({ 34 | target: testTarget, 35 | props: { 36 | isChecked: true 37 | } 38 | }); 39 | 40 | t.ok(testTarget.querySelector('input').checked); 41 | 42 | radio.$destroy(); 43 | }); 44 | 45 | 46 | test(`${componentName}: prop 'isDisabled' disables input`, async (t) => { 47 | const radio = new Radio({ 48 | target: testTarget, 49 | props: { 50 | isDisabled: true 51 | } 52 | }); 53 | 54 | t.ok(testTarget.querySelector('input').disabled); 55 | 56 | radio.$destroy(); 57 | }); 58 | -------------------------------------------------------------------------------- /src/components/Radio/Radio.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 26 | -------------------------------------------------------------------------------- /src/components/Radio/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 |
19 | 23 | 24 | 28 |
29 | 30 | 31 |

selected: {selected}

32 | -------------------------------------------------------------------------------- /src/components/RangeSlider/RangeSlider.svelte: -------------------------------------------------------------------------------- 1 | 47 | 48 | 49 | 50 |
51 | -------------------------------------------------------------------------------- /src/components/RangeSlider/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 |

selected: {values[0]}

10 | -------------------------------------------------------------------------------- /src/components/RangeSlider/examples/RangeSliderHandles.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 |

selected: {JSON.stringify(values)}

10 | -------------------------------------------------------------------------------- /src/components/RangeSlider/examples/RangeSliderSteps.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 |

selected: {values[0]}

10 | -------------------------------------------------------------------------------- /src/components/RangeSlider/examples/RangeSliderTooltips.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 |

selected: {JSON.stringify(values)}

12 | -------------------------------------------------------------------------------- /src/components/RangeSlider/examples/RangeSliderUpdateWhenSliding.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 |

selected: {values[0]}

10 | -------------------------------------------------------------------------------- /src/components/Search/Search.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import { wait, simulateClick } from 'helpers'; 3 | 4 | import Search from './Search.svelte'; 5 | import Basic from './examples/Basic.svelte'; 6 | import Debounce from './examples/Debounce.svelte'; 7 | 8 | const testTarget = document.getElementById('testTemplate'); 9 | const componentName = Search.name; 10 | 11 | test(`${componentName}: searchText represents value entered in text input element`, async (t) => { 12 | const searchText = 'foo bar'; 13 | 14 | const search = new Basic({ 15 | target: testTarget 16 | }); 17 | 18 | const inputElem = testTarget.querySelector('input'); 19 | 20 | t.equal(inputElem.value, ''); 21 | 22 | inputElem.value = searchText; 23 | 24 | const event = new Event('input'); 25 | inputElem.dispatchEvent(event); 26 | 27 | await wait(0); 28 | t.equal(search.$$.ctx.searchText, searchText); 29 | 30 | search.$destroy(); 31 | }); 32 | 33 | test(`${componentName}: setting value for input from outside the component updates the input element`, async (t) => { 34 | const searchText = 'foo bar'; 35 | 36 | const search = new Search({ 37 | target: testTarget 38 | }); 39 | 40 | const inputElem = testTarget.querySelector('input'); 41 | 42 | t.equal(inputElem.value, ''); 43 | 44 | search.$set({value: searchText}); 45 | 46 | await wait(0); 47 | t.equal(inputElem.value, searchText); 48 | 49 | search.$destroy(); 50 | }); 51 | 52 | test(`${componentName}: when debounce is true searchText represents value entered in text input element after debounce wait time`, async (t) => { 53 | const searchText = 'foo bar'; 54 | 55 | const search = new Debounce({ 56 | target: testTarget 57 | }); 58 | 59 | const inputElem = testTarget.querySelector('input'); 60 | 61 | inputElem.value = searchText; 62 | 63 | const event = new Event('input'); 64 | inputElem.dispatchEvent(event); 65 | 66 | await wait(0); 67 | t.equal(search.$$.ctx.searchText, ''); 68 | 69 | await wait(300); 70 | t.equal(search.$$.ctx.searchText, searchText); 71 | 72 | search.$destroy(); 73 | }); 74 | -------------------------------------------------------------------------------- /src/components/Search/Search.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 59 | 60 |
61 | 62 |
63 | 64 |
65 |
66 | 67 | { #if value} 68 |
69 | 70 |
71 | { /if } 72 |
73 | -------------------------------------------------------------------------------- /src/components/Search/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |

Search text: {searchText}

9 | -------------------------------------------------------------------------------- /src/components/Search/examples/Debounce.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |

Search text: {searchText}

9 | -------------------------------------------------------------------------------- /src/components/Search/examples/Placeholder.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |

Search text: {searchText}

9 | -------------------------------------------------------------------------------- /src/components/Spinner/Spinner.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import { wait } from 'helpers'; 3 | import Spinner from './Spinner.svelte'; 4 | 5 | const testTarget = document.getElementById('testTemplate'); 6 | const componentName = Spinner.name; 7 | 8 | test(`${componentName}: is dimensions of parent`, async (t) => { 9 | const spinner = new Spinner({ 10 | target: testTarget 11 | }); 12 | 13 | testTarget.setAttribute('style', 'width:200px;height:200px;'); 14 | 15 | t.equal(testTarget.querySelector('.spinner').getBoundingClientRect().width, 200); 16 | 17 | testTarget.setAttribute('style', ''); 18 | 19 | spinner.$destroy(); 20 | }); 21 | 22 | test(`${componentName}: colour is inherited from css current color`, async (t) => { 23 | const spinner = new Spinner({ 24 | target: testTarget 25 | }); 26 | 27 | const color = 'rgb(123, 123, 123)'; 28 | 29 | testTarget.setAttribute('style', `width:200px;height:200px;color:${color};`); 30 | 31 | t.equal(getComputedStyle(testTarget.querySelector('circle'))['stroke'], color); 32 | 33 | testTarget.setAttribute('style', ''); 34 | 35 | spinner.$destroy(); 36 | }); 37 | -------------------------------------------------------------------------------- /src/components/Spinner/Spinner.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 |
36 | 37 | 38 | 39 |
40 | -------------------------------------------------------------------------------- /src/components/Spinner/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 |
12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /src/components/Switch/Switch.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import Switch from './Switch.svelte'; 3 | 4 | const testTarget = document.getElementById('testTemplate'); 5 | const componentName = Switch.name; 6 | 7 | test(`${componentName}: with no data creates default elements`, async (t) => { 8 | const switchComponent = new Switch({ 9 | target: testTarget, 10 | props: {} 11 | }); 12 | 13 | t.ok(testTarget.querySelector('.switch')); 14 | 15 | switchComponent.$destroy(); 16 | }); 17 | 18 | test(`${componentName}: clicking changes isActive value`, async (t) => { 19 | let isActive = true; 20 | 21 | const switchComponent = new Switch({ 22 | target: testTarget, 23 | props: { 24 | isActive 25 | } 26 | }); 27 | 28 | testTarget.querySelector('.switch').click(); 29 | t.ok(switchComponent.isActive !== isActive); 30 | 31 | switchComponent.$destroy(); 32 | }); 33 | 34 | test(`${componentName}: clicking when disabled does not change isActive value`, async (t) => { 35 | const isActive = true; 36 | 37 | const switchComponent = new Switch({ 38 | target: testTarget, 39 | props: { 40 | isActive, 41 | isDisabled: true 42 | } 43 | }); 44 | 45 | testTarget.querySelector('.switch').click(); 46 | t.equal(switchComponent.isActive, isActive); 47 | 48 | switchComponent.$destroy(); 49 | }); 50 | 51 | test(`${componentName}: isWaiting displays spinner`, async (t) => { 52 | const switchComponent = new Switch({ 53 | target: testTarget, 54 | props: { 55 | isWaiting: true 56 | } 57 | }); 58 | 59 | t.ok(testTarget.querySelector('.handle .spinner')); 60 | 61 | switchComponent.$destroy(); 62 | }); 63 | 64 | test(`${componentName}: clicking when isWaiting does not change isActive`, async (t) => { 65 | const isActive = true; 66 | 67 | const switchComponent = new Switch({ 68 | target: testTarget, 69 | props: { 70 | isWaiting: true, 71 | isActive 72 | } 73 | }); 74 | 75 | testTarget.querySelector('.switch').click(); 76 | t.equal(switchComponent.isActive, isActive); 77 | 78 | switchComponent.$destroy(); 79 | }); 80 | -------------------------------------------------------------------------------- /src/components/Switch/Switch.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | 95 | 96 | 97 |
98 | 99 | { #if isWaiting } 100 | 101 | 102 | 103 | { /if } 104 | 105 |
106 | -------------------------------------------------------------------------------- /src/components/Switch/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Table/Table.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import { wait, simulateClick } from 'helpers'; 3 | 4 | import Table from './Table.svelte'; 5 | import Basic from './examples/Basic.svelte'; 6 | 7 | const testTarget = document.getElementById('testTemplate'); 8 | const componentName = Table.name; 9 | 10 | test(`${componentName}: renders correctly`, async (t) => { 11 | const table = new Table({ 12 | target: testTarget, 13 | props: { 14 | hasBorder: true, 15 | columns: [ 16 | { 17 | title: 'First Name', 18 | cell: 'FirstName' 19 | }, 20 | { 21 | title: 'Last Name', 22 | cell: 'LastName' 23 | }, 24 | { 25 | title: 'Total Bookings', 26 | cell: 'Score' 27 | } 28 | ], 29 | data: [ 30 | { 31 | FirstName: 'Xavier', 32 | LastName: 'Xu', 33 | Score: 106 34 | }, 35 | { 36 | FirstName: 'Bobby', 37 | LastName: 'Bilbosh', 38 | Score: 71 39 | } 40 | ] 41 | } 42 | }); 43 | 44 | 45 | table.$destroy(); 46 | }); 47 | -------------------------------------------------------------------------------- /src/components/Table/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 |
35 | Sorry there are no results so 36 |
37 |
38 | -------------------------------------------------------------------------------- /src/components/Table/examples/data.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltekit/ui/a626fcb428104bd4ff99233aeac162a726f9ab18/src/components/Table/examples/data.js -------------------------------------------------------------------------------- /src/components/Tabs/Tabs.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 95 | 96 | 97 |
98 |
    99 | {#each tabs as tab, i} 100 |
  • 101 | {#if tab.href} 102 | {tab.label} 103 | {:else} 104 | {tab.label} 105 | {/if} 106 |
  • 107 | {/each} 108 |
109 | {#each tabs as tab} 110 | {#if tab === activeTab && tab.component} 111 |
112 | 113 |
114 | {/if} 115 | {/each} 116 |
117 | -------------------------------------------------------------------------------- /src/components/Tabs/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/Tabs/examples/_tab1.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |

TAB ONE!

9 |

Content here...

10 |
11 | -------------------------------------------------------------------------------- /src/components/Tabs/examples/_tab2.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 |
13 |

Hello form Tab2!

14 | {#if image} 15 | example image 16 | {/if} 17 |
18 | -------------------------------------------------------------------------------- /src/components/Tag/Tag.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tape-modern'; 2 | import { wait, simulateClick } from 'helpers'; 3 | 4 | import Tag from './Tag.svelte'; 5 | import BasicExample from './examples/Basic.svelte'; 6 | import options from './options'; 7 | 8 | const testTarget = document.getElementById('testTemplate'); 9 | const componentName = Tag.name; 10 | 11 | test(`${componentName}: displays default slot text`, async (t) => { 12 | const tabLabel = 'xxxxx'; 13 | 14 | const tag = new BasicExample({ 15 | target: testTarget, 16 | props: { 17 | tabLabel, 18 | iconData: null 19 | } 20 | }); 21 | 22 | t.equal(testTarget.querySelector('.text').textContent, tabLabel); 23 | 24 | tag.$destroy(); 25 | }); 26 | 27 | test(`${componentName}: has correct background colour`, async (t) => { 28 | const backgroundColour = 'rgb(255, 0, 0)'; 29 | 30 | const tag = new Tag({ 31 | target: testTarget, 32 | props: { 33 | backgroundColour 34 | } 35 | }); 36 | 37 | t.equal(getComputedStyle(testTarget.querySelector('.tag')).backgroundColor, backgroundColour); 38 | 39 | tag.$destroy(); 40 | }); 41 | 42 | test(`${componentName}: has correct text colour`, async (t) => { 43 | const color = 'rgb(0, 0, 0)'; 44 | 45 | const tag = new Tag({ 46 | target: testTarget, 47 | props: { 48 | color 49 | } 50 | }); 51 | 52 | t.equal(getComputedStyle(testTarget.querySelector('.tag')).color, color); 53 | 54 | tag.$destroy(); 55 | }); 56 | 57 | test(`${componentName}: has icon before text`, async (t) => { 58 | const tag = new BasicExample({ 59 | target: testTarget, 60 | props: { 61 | iconPosition: options.iconPosition.LEFT 62 | } 63 | }); 64 | 65 | t.ok(getComputedStyle(testTarget.querySelector('.text')).order > getComputedStyle(testTarget.querySelector('.icon')).order); 66 | 67 | await wait(0); 68 | 69 | tag.$destroy(); 70 | }); 71 | 72 | test(`${componentName}: has icon after text`, async (t) => { 73 | const tag = new BasicExample({ 74 | target: testTarget, 75 | props: { 76 | iconPosition: options.iconPosition.RIGHT 77 | } 78 | }); 79 | 80 | await wait(0); 81 | 82 | t.ok(getComputedStyle(testTarget.querySelector('.text')).order < getComputedStyle(testTarget.querySelector('.icon')).order); 83 | 84 | tag.$destroy(); 85 | }); 86 | -------------------------------------------------------------------------------- /src/components/Tag/Tag.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | 78 | 79 |
80 |
81 | {#if Icon} 82 | 83 | 84 | 85 | {/if} 86 | 87 | 88 | 89 | 90 |
91 |
92 | -------------------------------------------------------------------------------- /src/components/Tag/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | { tabLabel } 11 | -------------------------------------------------------------------------------- /src/components/Tag/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | iconPosition: { 3 | LEFT: 'left', 4 | RIGHT: 'right', 5 | ONLY: 'only' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/TextInput/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |

Value: {value}

8 | -------------------------------------------------------------------------------- /src/components/TextInput/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | appearance: { 3 | DEFAULT: 'default', 4 | STANDOUT: 'standout', 5 | STANDOUT_INVERTED: 'standoutInverted', 6 | }, 7 | 8 | htmlType: { 9 | TEXT: 'text', 10 | NUMBER: 'number', 11 | EMAIL: 'email', 12 | PASSWORD: 'password' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/TimeMenu/TimeMenu.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | {selectedItem || placeholder} 54 |
55 |
56 | -------------------------------------------------------------------------------- /src/components/TimeMenu/_Menu.svelte: -------------------------------------------------------------------------------- 1 | 78 | 79 | 84 | 85 |
86 | 87 |
88 | 89 | 90 | { #each tabs as tab } 91 | { #if tab === activeTab } 92 | 93 | { /if } 94 | { /each } 95 |
96 |
97 |
98 | -------------------------------------------------------------------------------- /src/components/TimeMenu/_TimeOptions.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | 29 |
30 | { #each options as option } 31 |
32 | 42 |
43 | { /each } 44 |
45 | -------------------------------------------------------------------------------- /src/components/TimeMenu/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/TimeSelect/TimeSelect.svelte: -------------------------------------------------------------------------------- 1 | 61 | 62 | 77 | 78 | 79 |
80 |
81 | 82 |
83 | 84 | 99 |
100 | -------------------------------------------------------------------------------- /src/components/TimeSelect/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Tooltip/Tooltip.svelte: -------------------------------------------------------------------------------- 1 | 39 | 40 |
41 | 42 |
43 | -------------------------------------------------------------------------------- /src/components/Tooltip/examples/Basic.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Tooltip/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | placement: { 3 | TOP: 'top', 4 | RIGHT: 'right', 5 | BOTTOM: 'bottom', 6 | LEFT: 'left' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Tooltip/tippy.ui.css: -------------------------------------------------------------------------------- 1 | .tippy-popper[x-placement^=top] .tippy-tooltip.ui-theme .tippy-arrow { 2 | border-top: 7px solid #2c3e50; 3 | border-right: 7px solid transparent; 4 | border-left: 7px solid transparent 5 | } 6 | 7 | .tippy-popper[x-placement^=bottom] .tippy-tooltip.ui-theme .tippy-arrow { 8 | border-bottom: 7px solid #2c3e50; 9 | border-right: 7px solid transparent; 10 | border-left: 7px solid transparent 11 | } 12 | 13 | .tippy-popper[x-placement^=left] .tippy-tooltip.ui-theme .tippy-arrow { 14 | border-left: 7px solid #2c3e50; 15 | border-top: 7px solid transparent; 16 | border-bottom: 7px solid transparent 17 | } 18 | 19 | .tippy-popper[x-placement^=right] .tippy-tooltip.ui-theme .tippy-arrow { 20 | border-right: 7px solid #2c3e50; 21 | border-top: 7px solid transparent; 22 | border-bottom: 7px solid transparent 23 | } 24 | 25 | .tippy-tooltip.ui-theme, 26 | .tippy-tooltip.ui-theme .tippy-backdrop { 27 | background-color: #2c3e50; 28 | } 29 | 30 | .tippy-tooltip.ui-theme { 31 | border-radius: 2px; 32 | font-size: 12px; 33 | line-height: 14px; 34 | padding: 8px; 35 | } 36 | 37 | .tippy-tooltip.ui-theme .tippy-roundarrow { 38 | fill: #2c3e50; 39 | } 40 | 41 | .tippy-tooltip.ui-theme[data-animatefill] { 42 | background-color: transparent 43 | } 44 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export default {}; -------------------------------------------------------------------------------- /src/helpers/arrayHasItem.js: -------------------------------------------------------------------------------- 1 | export default function arrayHasItem(items, item) { 2 | if (!items || !Array.isArray(items)) return false; 3 | return items.includes(item); 4 | } 5 | -------------------------------------------------------------------------------- /src/helpers/classnames.js: -------------------------------------------------------------------------------- 1 | export function classnames() { 2 | let hasOwn = {}.hasOwnProperty; 3 | 4 | let classes = []; 5 | 6 | for (let i = 0; i < arguments.length; i++) { 7 | let arg = arguments[i]; 8 | if (!arg) continue; 9 | 10 | let argType = typeof arg; 11 | 12 | if (argType === 'string' || argType === 'number') { 13 | classes.push(arg); 14 | } else if (Array.isArray(arg) && arg.length) { 15 | let inner = classNames.apply(null, arg); 16 | if (inner) { 17 | classes.push(inner); 18 | } 19 | } else if (argType === 'object') { 20 | for (let key in arg) { 21 | if (hasOwn.call(arg, key) && arg[key]) { 22 | classes.push(key); 23 | } 24 | } 25 | } 26 | } 27 | 28 | return classes.join(' '); 29 | } 30 | -------------------------------------------------------------------------------- /src/helpers/generateUniqueId.js: -------------------------------------------------------------------------------- 1 | export default function generateUniqueId() { 2 | return Math.random().toString(36).substr(2, 9); 3 | } 4 | -------------------------------------------------------------------------------- /src/helpers/getPages.js: -------------------------------------------------------------------------------- 1 | import range from 'lodash/range'; 2 | 3 | const MAX_PAGES = 5; 4 | const PADDING = 2; 5 | 6 | export default function (current, pageSize, total) { 7 | const totalPages = Math.ceil(total/pageSize); 8 | let startPage = 1; 9 | let endPage = totalPages + 1; 10 | 11 | if(totalPages > MAX_PAGES) { 12 | startPage = current > PADDING ? current - PADDING : 1; 13 | 14 | if(totalPages - current <= PADDING) { 15 | startPage = totalPages - (MAX_PAGES - 1); 16 | } 17 | 18 | endPage = startPage + (MAX_PAGES); 19 | 20 | if (endPage > totalPages) { 21 | endPage = totalPages + 1; 22 | } 23 | } 24 | 25 | return range(startPage, endPage); 26 | } 27 | -------------------------------------------------------------------------------- /src/helpers/getPosition.js: -------------------------------------------------------------------------------- 1 | export default function getPosition(element, container) { 2 | let xPos = 0; 3 | let yPos = 0; 4 | let el = element; 5 | 6 | while (el) { 7 | xPos += (el.offsetLeft + el.clientLeft - el.scrollLeft); 8 | yPos += (el.offsetTop + el.clientTop - el.scrollTop); 9 | 10 | el = el.offsetParent === container ? null : el.offsetParent; 11 | } 12 | 13 | return { 14 | left: xPos, 15 | top: yPos, 16 | bottom: document.body.offsetHeight - yPos, 17 | right: document.body.offsetWidth - xPos 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/helpers/inlineStyles.js: -------------------------------------------------------------------------------- 1 | const inlineStyles = (...args) => { 2 | const declarations = {}; 3 | let styles = ''; 4 | for (const item of args) { 5 | if (item && typeof item === 'object') { 6 | for (const [key, value] of Object.entries(item)) { 7 | const typeOfValue = typeof value; 8 | if (typeOfValue === 'string' || typeOfValue === 'number') { 9 | declarations[key] = value; 10 | } 11 | } 12 | } 13 | } 14 | 15 | for (const [key, value] of Object.entries(declarations)) { 16 | styles += `${key}:${value};`; 17 | } 18 | 19 | return styles; 20 | }; 21 | 22 | export default inlineStyles; 23 | -------------------------------------------------------------------------------- /src/helpers/whichAnimationEvent.js: -------------------------------------------------------------------------------- 1 | export default function whichAnimationEvent(){ 2 | const el = document.createElement('fakeelement'); 3 | 4 | const animations = { 5 | 'animation': 'animationend', 6 | 'OAnimation': 'oAnimationEnd', 7 | 'MozAnimation': 'animationend', 8 | 'WebkitAnimation': 'webkitAnimationEnd' 9 | }; 10 | 11 | for(const a in animations){ 12 | if(el.style[a] !== undefined){ 13 | return animations[a]; 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | 2 | import App from './app/App.svelte'; 3 | 4 | 5 | const app = new App({ 6 | target: document.body, 7 | }); 8 | 9 | export default app; 10 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | export function wait(ms) { 2 | return new Promise(f => setTimeout(f, ms)); 3 | }; 4 | 5 | export function simulateClick(elem) { 6 | const evt = new MouseEvent('mousedown', { 7 | bubbles: true, 8 | cancelable: true, 9 | view: window 10 | }); 11 | 12 | console.log('dispatch', elem.dispatchEvent(evt)); 13 | } 14 | 15 | export function handleKeyboard(key) { 16 | window.dispatchEvent(new KeyboardEvent('keydown', {'key': key})); 17 | return new Promise(f => setTimeout(f, 0)); 18 | } 19 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltekit/ui/a626fcb428104bd4ff99233aeac162a726f9ab18/test/main.js -------------------------------------------------------------------------------- /test/public/avatar_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltekit/ui/a626fcb428104bd4ff99233aeac162a726f9ab18/test/public/avatar_example.gif -------------------------------------------------------------------------------- /test/public/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | padding: 0; 3 | margin: 0; 4 | min-height: 100%; 5 | } 6 | 7 | body { 8 | background: #fff; 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 10 | -webkit-font-smoothing: antialiased; 11 | color: #2c3e50; 12 | font-size: 14px; 13 | padding: 0px; 14 | margin: 0; 15 | } 16 | 17 | *, 18 | ::before, 19 | ::after { 20 | box-sizing: border-box; 21 | } 22 | 23 | code { 24 | font-family: "SFMono-Medium", "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Courier, monospace; 25 | } 26 | 27 | h1 { 28 | font-size: 29px; 29 | font-weight: 900; 30 | line-height: 32px; 31 | margin: 0 0 24px; 32 | } 33 | 34 | h2 { 35 | font-size: 20px; 36 | font-weight: bold; 37 | line-height: 24px; 38 | margin: 0 0 24px; 39 | } 40 | 41 | h3 { 42 | font-size: 16px; 43 | font-weight: bold; 44 | line-height: 20px; 45 | margin: 0 0 12px; 46 | } 47 | 48 | svg { 49 | -webkit-backface-visibility: hidden; 50 | -webkit-transform: translateZ(0) scale(1.0, 1.0); 51 | transform: translateZ(0); 52 | } 53 | 54 | .row { 55 | display: flex; 56 | flex-wrap: wrap; 57 | align-items: center; 58 | margin: 10px -5px; 59 | } 60 | 61 | .row > * { 62 | margin: 5px; 63 | } 64 | 65 | .col { 66 | display: flex; 67 | flex-direction: column; 68 | flex-wrap: nowrap; 69 | justify-content: flex-start; 70 | align-items: stretch; 71 | align-content: stretch; 72 | } 73 | 74 | .col > * { 75 | display: inline-block; 76 | width: auto; 77 | order: 1; 78 | flex-grow: 0; 79 | flex-shrink: 1; 80 | flex-basis: auto; 81 | align-self: auto; 82 | margin: 5px 0; 83 | } 84 | 85 | .col > :first-child { 86 | margin-top: 0; 87 | } 88 | 89 | .col > :last-child { 90 | margin-bottom: 0; 91 | } 92 | 93 | @media(min-width:890px) { 94 | 95 | 96 | 97 | .grid, 98 | .gridRow { 99 | display: grid; 100 | grid-template-columns: repeat(12, 1fr); 101 | grid-column-gap: 10px; 102 | grid-row-gap: 10px; 103 | margin: 10px 0; 104 | } 105 | 106 | .gridRow { 107 | align-items: baseline; 108 | } 109 | 110 | .gridCol-1 { 111 | grid-column: span 1; 112 | } 113 | 114 | .gridCol-2 { 115 | grid-column: span 2; 116 | } 117 | 118 | .gridCol-3 { 119 | grid-column: span 3; 120 | } 121 | 122 | .gridCol-4 { 123 | grid-column: span 4; 124 | } 125 | 126 | .gridCol-5 { 127 | grid-column: span 5; 128 | } 129 | 130 | .gridCol-6 { 131 | grid-column: span 6; 132 | } 133 | 134 | .gridCol-7 { 135 | grid-column: span 7; 136 | } 137 | 138 | .gridCol-8 { 139 | grid-column: span 8; 140 | } 141 | 142 | .gridCol-9 { 143 | grid-column: span 9; 144 | } 145 | 146 | .gridCol-10 { 147 | grid-column: span 10; 148 | } 149 | 150 | .gridCol-11 { 151 | grid-column: span 11; 152 | } 153 | 154 | .gridCol-12 { 155 | grid-column: span 12; 156 | } 157 | } 158 | 159 | :root { 160 | --blue_0: #e7f2ff; 161 | --blue_1: #b9daff; 162 | --blue_2: #73b6ff; 163 | --blue_3: #459eff; 164 | --blue_4: #007aff; 165 | --blue_5: #006fe8; 166 | --blue_6: #0064d1; 167 | --blue_7: #0059ba; 168 | --blue_8: #00438c; 169 | --blue_9: #002d5d; 170 | 171 | --green_0: #cff1d6; 172 | --green_1: #afe8bc; 173 | --green_2: #90dfa1; 174 | --green_3: #70d686; 175 | --green_4: #51ce6c; 176 | --green_5: #43a959; 177 | --green_6: #348445; 178 | --green_7: #2b6d39; 179 | --green_8: #22542c; 180 | --green_9: #183c20; 181 | 182 | --neutral_0: #ebedef; 183 | --neutral_1: #d8dbdf; 184 | --neutral_2: #c5cacf; 185 | --neutral_3: #b2b8bf; 186 | --neutral_4: #78848f; 187 | --neutral_5: #52616f; 188 | --neutral_6: #3f4f5f; 189 | --neutral_7: #2c3e50; 190 | --neutral_8: #212e3b; 191 | --neutral_9: #18222c; 192 | 193 | --red_0: #ffd3db; 194 | --red_1: #ff9fb1; 195 | --red_2: #ff7992; 196 | --red_3: #ff5373; 197 | --red_4: #ff2d55; 198 | --red_5: #e8294e; 199 | --red_6: #d12546; 200 | --red_7: #ac1f3a; 201 | --red_8: #86182d; 202 | --red_9: #5f1120; 203 | 204 | --brown: #6a4000; 205 | --yellow: #ffe6c1; 206 | --silver: #f3f6fc; 207 | --white: #fff; 208 | 209 | --primary_1: var(--blue_4); 210 | --primary_2: var(--green_4, #51ce6c); 211 | --primary_3: var(--neutral_7); 212 | --primary_4: var(--silver); 213 | --primary_5: var(--white); 214 | 215 | --secondary_1: #ff2d55; 216 | --secondary_2: var(--red_4); 217 | --secondary_3: #6554c0; 218 | --secondary_4: var(--orange_4); 219 | } 220 | -------------------------------------------------------------------------------- /test/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Sveltekit Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/runner.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const ports = require('port-authority'); 3 | const sirv = require('sirv'); 4 | const puppeteer = require('puppeteer'); 5 | let page; 6 | 7 | async function go() { 8 | console.log('go'); 9 | const port = await ports.find(1234); 10 | console.log(`found available port: ${port}`); 11 | 12 | const server = http.createServer(sirv('test/public')); 13 | server.listen(port); 14 | 15 | await ports.wait(port).catch(() => { 16 | }); // workaround windows gremlins 17 | 18 | const browser = await puppeteer.launch({args: ['--no-sandbox']}); 19 | page = await browser.newPage(); 20 | 21 | page.on('console', msg => { 22 | console[msg.type()](msg.text()); 23 | }); 24 | 25 | await page.goto(`http://localhost:${port}`); 26 | 27 | await page.evaluate(() => done); 28 | 29 | server.close(); 30 | } 31 | 32 | 33 | go(); 34 | -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const glob = require("glob"); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 5 | 6 | const mode = process.env.NODE_ENV || 'development'; 7 | const prod = mode === 'production'; 8 | const components = glob.sync("./build/components/*/index.js"); 9 | components.push('./src/main.js'); 10 | 11 | 12 | module.exports = { 13 | stats: { 14 | errorDetails: true 15 | }, 16 | entry: { 17 | bundle: components 18 | }, 19 | resolve: { 20 | alias: { 21 | '@sveltekit/ui': path.resolve(__dirname, '../'), 22 | stores: path.resolve(__dirname, '../src/app/stores.js') 23 | }, 24 | extensions: ['.mjs', '.js', '.svelte'] 25 | }, 26 | output: { 27 | path: path.resolve(__dirname, '../public'), 28 | filename: '[name].js', 29 | chunkFilename: '[name].[id].js' 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.svelte$/, 35 | use: { 36 | loader: 'svelte-loader', 37 | options: { 38 | dev: true, 39 | emitCss: false, 40 | hotReload: true, 41 | accessors: true 42 | } 43 | } 44 | }, 45 | { 46 | test: /\.css$/, 47 | use: [ 48 | prod ? MiniCssExtractPlugin.loader : 'style-loader', 49 | 'css-loader', 50 | ] 51 | } 52 | ] 53 | }, 54 | mode, 55 | optimization: { 56 | // We no not want to minimize our code. 57 | minimize: false 58 | }, 59 | plugins: [ 60 | // new BundleAnalyzerPlugin(), 61 | new MiniCssExtractPlugin({ 62 | filename: '[name].css' 63 | }) 64 | ], 65 | devtool: prod ? false : 'source-map' 66 | }; 67 | -------------------------------------------------------------------------------- /webpack/webpack.config.tests.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const glob = require("glob"); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | const mode = process.env.NODE_ENV || 'development'; 6 | const prod = mode === 'production'; 7 | 8 | const componentsTests = glob.sync("./src/components/*/*.spec.js"); 9 | componentsTests.push('./test/main.js'); 10 | 11 | const alias = { 12 | '@sveltekit/ui': path.resolve(__dirname, '../'), 13 | helpers: path.resolve(__dirname, '../test/helpers.js') 14 | }; 15 | 16 | componentsTests.forEach(test => { 17 | let name = `sveltekit/tests/${test.split('/')[3]}` 18 | alias[name] = path.resolve(__dirname, test) 19 | }) 20 | 21 | module.exports = { 22 | entry: { 23 | bundle: componentsTests 24 | }, 25 | resolve: { 26 | alias, 27 | extensions: ['.mjs', '.js', '.svelte'] 28 | }, 29 | output: { 30 | path: path.resolve(__dirname, '../test/public'), 31 | filename: '[name].js', 32 | chunkFilename: '[name].[id].js', 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.svelte$/, 38 | use: { 39 | loader: 'svelte-loader', 40 | options: { 41 | dev: true, 42 | emitCss: true, 43 | hotReload: true, 44 | accessors: true 45 | } 46 | } 47 | }, 48 | { 49 | test: /\.css$/, 50 | use: [ 51 | /** 52 | * MiniCssExtractPlugin doesn't support HMR. 53 | * For developing, use 'style-loader' instead. 54 | * */ 55 | prod ? MiniCssExtractPlugin.loader : 'style-loader', 56 | 'css-loader' 57 | ] 58 | } 59 | ] 60 | }, 61 | mode, 62 | plugins: [ 63 | new MiniCssExtractPlugin({ 64 | filename: '[name].css' 65 | }) 66 | ], 67 | devtool: prod ? false : 'source-map' 68 | }; 69 | --------------------------------------------------------------------------------