parent
5bfce96e64
commit
5c3d7588f9
@ -0,0 +1,120 @@
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
@ -1,2 +1,94 @@
|
||||
# select
|
||||
# Tiny Components - Select
|
||||
Created with [Riot.js](https://riot.js.org). Select Field Element, Filter-Option to connect a [Observable](https://github.com/riot/observable). Using Styles for UI from [Plain-UI](https://plain-ui.com)
|
||||
|
||||
Source: [https://git.node001.net/tiny-components/select](https://git.node001.net/tiny-components/select)
|
||||
|
||||
## Installation
|
||||
|
||||
Setup this registry in your project .npmrc file:
|
||||
|
||||
```
|
||||
@tiny-components:registry=https://git.node001.net/api/packages/tiny-components/npm/
|
||||
```
|
||||
|
||||
Install with npm or yarn
|
||||
|
||||
```
|
||||
npm i --save @tiny-components/select
|
||||
yarn add @tiny-components/select
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
<tiny-field-select></tiny-field-select>
|
||||
```
|
||||
|
||||
### Attribute: multiple
|
||||
|
||||
Select Multiple Options
|
||||
|
||||
### Attribute: searchable
|
||||
|
||||
Show Input-Field for Filtering Options, only works if Observable as Store is added and has Function Query.
|
||||
|
||||
### Props: options
|
||||
|
||||
Default Options for Start
|
||||
|
||||
### Props: store
|
||||
|
||||
import observable from '@riotjs/observable'
|
||||
|
||||
let store = observable({
|
||||
locations: [{
|
||||
'label' : 'A',
|
||||
'value' : 'a'
|
||||
},{
|
||||
'label' : 'B',
|
||||
'value' : 'b'
|
||||
},{
|
||||
'label' : 'C',
|
||||
'value' : 'c'
|
||||
},{
|
||||
'label' : 'D',
|
||||
'value' : 'd'
|
||||
},{
|
||||
'label' : 'E',
|
||||
'value' : 'e'
|
||||
},{
|
||||
'label' : 'F',
|
||||
'value' : 'f'
|
||||
},{
|
||||
'label' : 'G',
|
||||
'value' : 'g'
|
||||
},{
|
||||
'label' : 'H',
|
||||
'value' : 'h'
|
||||
},{
|
||||
'label' : 'I',
|
||||
'value' : 'i'
|
||||
},{
|
||||
'label' : 'J',
|
||||
'value' : 'j'
|
||||
}],
|
||||
|
||||
query(value) {
|
||||
if (value !== undefined) {
|
||||
const results = []
|
||||
|
||||
// run through locations and check string contains value
|
||||
for (let i = 0; i < this.locations.length; i++) {
|
||||
if (this.locations[i].value.includes(value)) {
|
||||
results.push(this.locations[i])
|
||||
}
|
||||
}
|
||||
|
||||
this.trigger('update', results)
|
||||
} else {
|
||||
|
||||
// if value is undefined, adding all locations
|
||||
this.trigger('update', this.locations)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"/example/js/spritemap.js": "/example/js/spritemap.js?version=7ce719c9e00cdc31c694d971b9a5e812",
|
||||
"/example/js/example.js": "/example/js/example.js?version=2451e89d416b448c318a4c3866f1ebf8",
|
||||
"/example/css/styles.css": "/example/css/styles.css?version=aa01e6a7dccff39e3f40dcf35c9e9576"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Tiny Components | Select</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link href="/css/styles.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header class="header">
|
||||
<div class="bar">
|
||||
<div class="bar__start">
|
||||
<h1 class="m-top-4 m-bottom-4 h4">
|
||||
@tiny-components/select
|
||||
</h1>
|
||||
</div>
|
||||
<div class="bar__main justify-end">
|
||||
<a class="button button--small m-left-sm-3 m-bottom-0" href="https://git.node001.net/tiny-components/datepicker" rel="noopener" target="_blank">
|
||||
Gitea
|
||||
<svg class="m-left-3 icon fill-text" aria-hidden="true">
|
||||
<use xlink:href="/symbol-defs.svg#icon-gitea"></use>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container m-top-6">
|
||||
<div class="grid">
|
||||
<div class="col-12 col-md-6">
|
||||
<form>
|
||||
<div class="field-group">
|
||||
<label class="field-label">
|
||||
Locations
|
||||
<tiny-field-select name="location"></tiny-field-select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="field-label">
|
||||
Locations (Multiple)
|
||||
<tiny-field-select multiple name="location"></tiny-field-select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="field-label">
|
||||
Locations (Multiple + Filter)
|
||||
<tiny-field-select-api multiple searchable name="location" placeholder-filter="Filter Items"></tiny-field-select-api>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script defer src="/js/example.js"></script>
|
||||
<script defer>
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
riot.mount('tiny-field-select', {
|
||||
options: [{
|
||||
'label' : 'A',
|
||||
'value' : 'a'
|
||||
},{
|
||||
'label' : 'B',
|
||||
'value' : 'b'
|
||||
},{
|
||||
'label' : 'C',
|
||||
'value' : 'c'
|
||||
},{
|
||||
'label' : 'D',
|
||||
'value' : 'd'
|
||||
},{
|
||||
'label' : 'E',
|
||||
'value' : 'e'
|
||||
},{
|
||||
'label' : 'F',
|
||||
'value' : 'f'
|
||||
},{
|
||||
'label' : 'G',
|
||||
'value' : 'g'
|
||||
},{
|
||||
'label' : 'H',
|
||||
'value' : 'h'
|
||||
},{
|
||||
'label' : 'I',
|
||||
'value' : 'i'
|
||||
},{
|
||||
'label' : 'J',
|
||||
'value' : 'j'
|
||||
}]
|
||||
})
|
||||
|
||||
riot.mount('tiny-field-select-api', {
|
||||
store: LocationStore
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
(self.webpackChunk_tiny_components_select=self.webpackChunk_tiny_components_select||[]).push([["spritemap"],{"?4e0c"(){eval("{\n\n//# sourceURL=webpack://@tiny-components/select/spritemap-dummy-module?\n}")}}]);
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@tiny-components/select",
|
||||
"version": "0.1.0",
|
||||
"description": "Select with filter for Desktop and Mobile",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@git.node001.net:tiny-components/select.git"
|
||||
},
|
||||
"author": "Björn Hase <herrhase@node001.net>",
|
||||
"license": "GPLV3",
|
||||
"dependencies": {
|
||||
"riot": "^10.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@riotjs/observable": "^4.1.1",
|
||||
"@riotjs/webpack-loader": "^10.0.0",
|
||||
"@tiny-components/plain-ui": "^0.6.0",
|
||||
"@tiny-components/webpack": "^0.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode development --config webpack.config.js"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import * as riot from 'riot'
|
||||
import Select from './select.riot'
|
||||
import observable from '@riotjs/observable'
|
||||
|
||||
window.riot = riot
|
||||
window.LocationStore = observable({
|
||||
|
||||
locations: [{
|
||||
'label' : 'A',
|
||||
'value' : 'a'
|
||||
},{
|
||||
'label' : 'B',
|
||||
'value' : 'b'
|
||||
},{
|
||||
'label' : 'C',
|
||||
'value' : 'c'
|
||||
},{
|
||||
'label' : 'D',
|
||||
'value' : 'd'
|
||||
},{
|
||||
'label' : 'E',
|
||||
'value' : 'e'
|
||||
},{
|
||||
'label' : 'F',
|
||||
'value' : 'f'
|
||||
},{
|
||||
'label' : 'G',
|
||||
'value' : 'g'
|
||||
},{
|
||||
'label' : 'H',
|
||||
'value' : 'h'
|
||||
},{
|
||||
'label' : 'I',
|
||||
'value' : 'i'
|
||||
},{
|
||||
'label' : 'J',
|
||||
'value' : 'j'
|
||||
}],
|
||||
|
||||
query(value) {
|
||||
if (value !== undefined) {
|
||||
const results = []
|
||||
|
||||
for (let i = 0; i < this.locations.length; i++) {
|
||||
if (this.locations[i].value.includes(value)) {
|
||||
results.push(this.locations[i])
|
||||
}
|
||||
}
|
||||
|
||||
this.trigger('update', results)
|
||||
} else {
|
||||
this.trigger('update', this.locations)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
riot.register('tiny-field-select', Select)
|
||||
riot.register('tiny-field-select-api', Select)
|
||||
@ -0,0 +1,3 @@
|
||||
@import
|
||||
'../node_modules/@tiny-components/plain-ui/src/scss/plain-ui',
|
||||
'styles';
|
||||
@ -0,0 +1,225 @@
|
||||
<tiny-field-select>
|
||||
<div class="field-select">
|
||||
|
||||
<select name="{ props.name }" multiple style="display: none;">
|
||||
<option value="{ selected[ state.keys.value ] }" selected each={ selected in state.selected }></option>
|
||||
</select>
|
||||
|
||||
<div class="field-select__options">
|
||||
<div class="{ getFilterClasses(['field-select__options-filter']) }" if={ props.searchable !== undefined }>
|
||||
<input type="text" class="field-text m-top-0" placeholder={ props.placeholderFilter } onkeyup={ (event) => { handleOnKeyUp(event) }} />
|
||||
<button type="button" class="field-select__options-filter-reset" onclick={ (event) => { handleResetSearchable(event) } }>
|
||||
⮾
|
||||
</button>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="field-select__options-panel">
|
||||
<ul class="field-select__options-list">
|
||||
<li class="{ getItemClasses(['field-select__options-item'], option) }" each={ option in state.options } onclick={ (event) => { handleToggle(event, option) } }>
|
||||
<span class="field-select__options-item-check">
|
||||
☑
|
||||
</span>
|
||||
<span class="field-select__options-item-uncheck">
|
||||
☐
|
||||
</span>
|
||||
{ option[ state.keys.label ] }
|
||||
</li>
|
||||
<li class="field-select__item field-select__item--empty center color-danger" if={ state.options.length === 0 }>
|
||||
∅
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
|
||||
state: {
|
||||
keys: {
|
||||
label: 'label',
|
||||
value: 'value'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
onBeforeMount(props, state) {
|
||||
|
||||
// init state
|
||||
this.state = {
|
||||
...state,
|
||||
selected: [],
|
||||
hasUpdated: false,
|
||||
options: [],
|
||||
query: ''
|
||||
}
|
||||
|
||||
if (this.props.label) {
|
||||
this.state.keys.label = props.label
|
||||
}
|
||||
|
||||
if (this.props.value) {
|
||||
this.state.keys.value = props.value
|
||||
}
|
||||
|
||||
if (this.props.options) {
|
||||
this.state.options = props.options
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
onMounted() {
|
||||
|
||||
// if store update, add data to options
|
||||
if (this.props.store) {
|
||||
this.props.store.on('update', (data) => {
|
||||
|
||||
if (this.state.selected.length > 0) {
|
||||
for (let i = 0; i < this.state.selected.length; i++) {
|
||||
|
||||
let found = false
|
||||
|
||||
for (let j = 0; j < data.length; j++) {
|
||||
if (data[j][ this.state.keys.value ] == this.state.selected[i][ this.state.keys.value ]) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if (found === false) {
|
||||
data.unshift(this.state.selected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.state.options = data
|
||||
this.update()
|
||||
})
|
||||
|
||||
this.props.store.query()
|
||||
}
|
||||
|
||||
// getting closet form-element and add reset
|
||||
this.root.closest('form').addEventListener('reset', () => {
|
||||
this.reset()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
onBeforeUpdate() {
|
||||
|
||||
// props has changed after reset
|
||||
if (this.props.selected && this.props.selected.length > 0 && !this.state.hasUpdated) {
|
||||
this.state.selected = this.props.selected
|
||||
this.state.hasUpdated = true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* reset searchable
|
||||
*
|
||||
*/
|
||||
handleResetSearchable(event) {
|
||||
event.target.previousElementSibling.value = ''
|
||||
this.state.query = ''
|
||||
this.props.store.query(this.state.query)
|
||||
},
|
||||
|
||||
/**
|
||||
* if pressed has ended, call query
|
||||
*
|
||||
*/
|
||||
handleOnKeyUp(event) {
|
||||
this.state.query = event.target.value.trim()
|
||||
this.props.store.query(this.state.query)
|
||||
},
|
||||
|
||||
/**
|
||||
* toggle selected, if not found add to selected
|
||||
*
|
||||
*/
|
||||
handleToggle(event, option) {
|
||||
const index = this.searchSelected(option)
|
||||
|
||||
if (index !== false) {
|
||||
this.state.selected.splice(index, 1)
|
||||
} else {
|
||||
if ((this.props.multiple === undefined && this.state.selected.length > 0)) {
|
||||
this.state.selected = []
|
||||
}
|
||||
|
||||
this.state.selected.push(option)
|
||||
}
|
||||
|
||||
this.update()
|
||||
},
|
||||
|
||||
/**
|
||||
* if item is in selected add css class
|
||||
*
|
||||
*/
|
||||
getItemClasses(classes, option) {
|
||||
const index = this.searchSelected(option)
|
||||
|
||||
if (index !== false) {
|
||||
classes.push('field-select__options-item--selected')
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
},
|
||||
|
||||
/**
|
||||
* if query is not empty show button for reset filter
|
||||
*
|
||||
*/
|
||||
getFilterClasses(classes) {
|
||||
if (this.state.query.length > 0) {
|
||||
classes.push('field-select__options-filter--active')
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
},
|
||||
|
||||
/**
|
||||
* search all selected for value return index or false
|
||||
*
|
||||
* @param options object
|
||||
*/
|
||||
searchSelected(option) {
|
||||
let index = false
|
||||
|
||||
for (let i = 0; i < this.state.selected.length; i++) {
|
||||
if (this.state.selected[i] && this.state.selected[i][ this.state.keys.value ] == option[ this.state.keys.value ]) {
|
||||
index = i
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index
|
||||
},
|
||||
|
||||
/**
|
||||
* reset component
|
||||
*
|
||||
*/
|
||||
reset() {
|
||||
this.state.selected = []
|
||||
this.state.hasUpdated = false
|
||||
|
||||
this.update()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
</tiny-field-select>
|
||||
@ -0,0 +1,97 @@
|
||||
.field-select {
|
||||
|
||||
&__options-panel {
|
||||
overflow-x: scroll;
|
||||
max-height: 15vh;
|
||||
}
|
||||
|
||||
&__options {
|
||||
|
||||
&-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&-item-check, &-item-uncheck {
|
||||
font-size: 1.5rem;
|
||||
vertical-align: middle;
|
||||
line-height: 0;
|
||||
margin: -0.2rem 0.25rem 0 0;
|
||||
}
|
||||
|
||||
&-item-check {
|
||||
color: var(--success);
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-item-uncheck {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&-item--selected {
|
||||
color: white;
|
||||
background-color: var(--active);
|
||||
|
||||
.field-select__options-item-check {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.field-select__options-item-uncheck {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-filter {
|
||||
position: relative;
|
||||
|
||||
&--active {
|
||||
.field-select__options-filter-reset {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-filter-reset {
|
||||
position: absolute;
|
||||
display: none;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--danger);
|
||||
font-size: 1.75rem;
|
||||
margin: 0.1rem 0.3rem;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__options-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
border: 1px solid $color__border;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 0.5rem;
|
||||
|
||||
.icon {
|
||||
margin-top: -3px;
|
||||
margin-right: 5px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
const tinyComponentsWebpack = require('@tiny-components/webpack')
|
||||
const riotRules = require('@tiny-components/webpack/rules/riot')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = tinyComponentsWebpack({
|
||||
example: [ './src/example.js' ],
|
||||
styles: [ './src/example.scss' ],
|
||||
}, {
|
||||
publicPath: '/example/',
|
||||
destination: path.resolve(process.cwd(), 'example'),
|
||||
rules: [ riotRules ],
|
||||
svg: {
|
||||
src: [
|
||||
'./src/icons/*.svg'
|
||||
]
|
||||
},
|
||||
purge: {
|
||||
src: path.join(__dirname, './**')
|
||||
}
|
||||
})
|
||||
Loading…
Reference in new issue