HerrHase 6 days ago
parent 5bfce96e64
commit 5c3d7588f9

120
.gitignore vendored

@ -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}")}}]);

3023
package-lock.json generated

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) } }>
&#11198;
</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">
&#9745;
</span>
<span class="field-select__options-item-uncheck">
&#9744;
</span>
{ option[ state.keys.label ] }
</li>
<li class="field-select__item field-select__item--empty center color-danger" if={ state.options.length === 0 }>
&#8709;
</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…
Cancel
Save