Skip to content

Commit

Permalink
Initial Commit.
Browse files Browse the repository at this point in the history
ConorWebb96 committed Apr 21, 2023
0 parents commit 784ac23
Showing 14 changed files with 4,983 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
32 changes: 32 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Release

on:
push:
branches:
- master
- main

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v1
with:
node-version: 16
- name: Make the production plugin bundle
run: |
release_version=$(cat package.json | jq -r '.version')
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
yarn
yarn build
- name: Perform Github Release
uses: softprops/action-gh-release@v1
with:
name: v${{ env.RELEASE_VERSION }}
tag_name: v${{ env.RELEASE_VERSION }}
generate_release_notes: true
files: |
dist/*.tar.gz
106 changes: 106 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-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/

# TypeScript v1 declaration files
typings/

# 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

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# 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

.idea/
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
.github
rollup.config.js
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Bb-dropdown-button
This is a readme for your new Budibase plugin.

# Description
A button that you can add children to as the dropdown.

Find out more about [Budibase](https://github.com/Budibase/budibase).

## Instructions

To build your new plugin run the following in your Budibase CLI:
```
budi plugins --build
```

You can also re-build everytime you make a change to your plugin with the command:
```
budi plugins --watch
```

18 changes: 18 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Wrapper from "./lib/Wrapper.svelte"
import schema from "./schema.json"
import pkg from "./package.json"

if (window) {
const plugin = { Component: Wrapper, schema, version: pkg.version }
if (!window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
window["##BUDIBASE_CUSTOM_COMPONENTS##"] = []
}
window["##BUDIBASE_CUSTOM_COMPONENTS##"].push(plugin)
if (window.registerCustomComponent) {
window.registerCustomComponent(plugin)
}
}

export const Component = Wrapper
export const version = pkg.version
export { schema }
4 changes: 4 additions & 0 deletions lib/Boundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Boundary from "./Boundary.svelte"
import { createBoundary } from "@crownframework/svelte-error-boundary"

export default createBoundary(Boundary);
51 changes: 51 additions & 0 deletions lib/Boundary.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

<script>
import pkg from "../package.json"
import { getContext } from "svelte"
export let error = null
const { styleable } = getContext("sdk")
const component = getContext("component")
$: styles = {
normal: {},
id: $component.id,
interactive: true
}
</script>

{#if $error}
<div class="error" use:styleable={styles}>
<b>There was an error running the "{pkg.name}" plugin:</b>
<div class="detail">
{$error}
</div>
</div>
{:else}
<slot />
{/if}

<style>
.error {
background: var(--spectrum-global-color-red-400);
color: white;
border-radius: 4px;
padding: 16px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: 8px;
min-width: 400px;
}
.detail {
font-family: monospace;
font-size: 10px;
padding: 8px;
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
max-height: 100px;
overflow: auto;
}
</style>
10 changes: 10 additions & 0 deletions lib/Wrapper.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
import Component from "../src/Component.svelte"
import Boundary from "./Boundary.js"
</script>

<Boundary>
<Component {...$$props}>
<slot/>
</Component>
</Boundary>
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "bb-dropdown-button",
"version": "1.0.0",
"description": "A button that you can add children to as the dropdown.",
"license": "MIT",
"svelte": "index.js",
"module": "dist/plugin.min.js",
"scripts": {
"build": "rollup -c",
"watch": "rollup -cw"
},
"dependencies": {
"@crownframework/svelte-error-boundary": "^1.0.3",
"svelte": "^3.49.0"
},
"devDependencies": {
"@budibase/backend-core": "^2.0.13",
"@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.2.10",
"rollup": "^2.44.0",
"rollup-plugin-copy2": "^0.3.1",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-polyfill-node": "^0.8.0",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-svg": "^2.0.0",
"rollup-plugin-terser": "^7.0.2",
"tar": "^6.1.11"
}
}
122 changes: 122 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import commonjs from "@rollup/plugin-commonjs"
import resolve from "@rollup/plugin-node-resolve"
import svelte from "rollup-plugin-svelte"
import { terser } from "rollup-plugin-terser"
import postcss from "rollup-plugin-postcss"
import svg from "rollup-plugin-svg"
import json from "rollup-plugin-json"
import nodePolyfills from "rollup-plugin-polyfill-node"
import copy from "rollup-plugin-copy2"
import tar from "tar"
import fs from "fs"
import pkg from "./package.json"
import crypto from "crypto"
import { validate } from "@budibase/backend-core/plugins"

const ignoredWarnings = [
"unused-export-let",
"css-unused-selector",
"module-script-reactive-declaration",
"a11y-no-onchange",
]

// Custom plugin to clean the dist folder before building
const clean = () => ({
buildStart() {
const dist = "./dist/"
if (fs.existsSync(dist)) {
fs.readdirSync(dist).forEach(path => {
if (path.endsWith(".tar.gz")) {
fs.unlinkSync(dist + path)
}
})
}
},
})

// Custom plugin to hash the JS bundle and write it in the schema
const hash = () => ({
writeBundle() {
// Generate JS hash
const fileBuffer = fs.readFileSync("dist/plugin.min.js")
const hashSum = crypto.createHash("sha1")
hashSum.update(fileBuffer)
const hex = hashSum.digest("hex")

// Read and parse existing schema from dist folder
const schema = JSON.parse(fs.readFileSync("./dist/schema.json", "utf8"))

// Write updated schema to dist folder, pretty printed as JSON again
const newSchema = {
...schema,
hash: hex,
version: pkg.version,
}
fs.writeFileSync("./dist/schema.json", JSON.stringify(newSchema, null, 2))
},
})

// Custom plugin to bundle up our files after building
const bundle = () => ({
async writeBundle() {
const bundleName = `${pkg.name}-${pkg.version}.tar.gz`
return tar
.c({ gzip: true, cwd: "dist" }, [
"plugin.min.js",
"schema.json",
"package.json",
])
.pipe(fs.createWriteStream(`dist/${bundleName}`))
},
})

const validateSchema = () => ({
buildStart() {
const schema = fs.readFileSync("schema.json", "utf8")
validate(JSON.parse(schema))
}
})

export default {
input: "index.js",
output: {
sourcemap: process.env.ROLLUP_WATCH ? "inline" : false,
format: "iife",
file: "dist/plugin.min.js",
name: "plugin",
globals: {
svelte: "svelte",
"svelte/internal": "svelte_internal",
},
},
external: ["svelte", "svelte/internal"],
plugins: [
validateSchema(),
clean(),
svelte({
emitCss: true,
onwarn: (warning, handler) => {
// Ignore some warnings
if (!ignoredWarnings.includes(warning.code)) {
handler(warning)
}
},
}),
postcss(),
commonjs(),
nodePolyfills(),
resolve({
preferBuiltins: true,
browser: true,
skip: ["svelte", "svelte/internal"],
}),
svg(),
json(),
terser(),
copy({
assets: ["schema.json", "package.json"],
}),
hash(),
bundle(),
],
}
205 changes: 205 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
{
"type": "component",
"metadata": {},
"schema": {
"name": "bb-dropdown-button",
"friendlyName": "Dropdown Button",
"description": "A button that you can add children to as the dropdown.",
"icon": "Group",
"illegalChildren": ["section"],
"hasChildren": true,
"settings": [
{
"type": "text",
"label": "Button Text",
"key": "buttonText",
"defaultValue": "A button"
},
{
"type": "select",
"label": "Dropdown position",
"key": "dPosition",
"showInBar": true,
"barStyle": "picker",
"options": [
{
"label": "Top",
"value": "top"
},
{
"label": "Right",
"value": "right"
},
{
"label": "Bottom",
"value": "bottom"
},
{
"label": "Left",
"value": "left"
}
],
"defaultValue": "bottom"
},
{
"type": "number",
"label": "Dropdown Width",
"key": "width",
"placeholder": 50,
"min": 200,
"max": 1200,
"defaultValue": 200
},
{
"type": "select",
"label": "Variant",
"key": "type",
"showInBar": true,
"options": [
{
"label": "Primary",
"value": "primary"
},
{
"label": "Secondary",
"value": "secondary"
},
{
"label": "Action",
"value": "cta"
},
{
"label": "Warning",
"value": "warning"
},
{
"label": "Over background",
"value": "overBackground"
}
],
"defaultValue": "primary"
},
{
"type": "select",
"label": "Size",
"showInBar": true,
"key": "size",
"options": [
{
"label": "Small",
"value": "S"
},
{
"label": "Medium",
"value": "M"
},
{
"label": "Large",
"value": "L"
},
{
"label": "Extra large",
"value": "XL"
}
],
"defaultValue": "M"
},
{
"type": "boolean",
"label": "Quiet",
"key": "quiet",
"showInBar": true,
"barIcon": "VisibilityOff",
"barTitle": "Quiet variant",
"barSeparator": false
},
{
"section": true,
"name": "Icon",
"settings": [
{
"type": "icon",
"label": "Icon",
"key": "icon"
},
{
"type": "select",
"label": "Icon Size",
"key": "iconSize",
"defaultValue": "ri-1x",
"options": [
{
"value": "ri-xxs",
"label": "XXS"
},
{
"value": "ri-xs",
"label": "XS"
},
{
"value": "ri-sm",
"label": "Small"
},
{
"value": "ri-1x",
"label": "Medium"
},
{
"value": "ri-lg",
"label": "Large"
},
{
"value": "ri-xl",
"label": "XL"
},
{
"value": "ri-2x",
"label": "2XL"
},
{
"value": "ri-3x",
"label": "3XL"
},
{
"value": "ri-4x",
"label": "4XL"
},
{
"value": "ri-5x",
"label": "5XL"
},
{
"value": "ri-6x",
"label": "6XL"
},
{
"value": "ri-7x",
"label": "7XL"
},
{
"value": "ri-8x",
"label": "8XL"
},
{
"value": "ri-9x",
"label": "9XL"
},
{
"value": "ri-10x",
"label": "10XL"
}
]
},
{
"type": "color",
"label": "Icon Color",
"key": "iconColor"
}
]
}
],
"context": {
"type": "schema"
}
}
}
214 changes: 214 additions & 0 deletions src/Component.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<script>
import { getContext, onMount } from "svelte";
const { styleable } = getContext("sdk");
const component = getContext("component");
// schema exports
export let buttonText;
export let dPosition;
export let width;
export let size = "M";
export let type;
export let quiet;
export let newStyles = true
export let disabled = false
// icon exports
export let icon;
export let iconColour;
export let iconSize = "M";
$: iconStyles = {
...$component.styles,
normal: {
...$component.styles.normal,
color: iconColour || "var(--spectrum-global-color-white-900)",
},
}
// preset vars to be used later
let dropdownContentStyle = 'hidden';
let randomString = "";
onMount(() => {
// generate random characters on mounted to asigned to buttons later.
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 10; i++) {
randomString += characters.charAt(Math.floor(Math.random() * characters.length));
}
});
const dropdownButtonClicked = () => {
positionDropdown(); // Positions the dropdown relative to the button
if (dropdownContentStyle === 'hidden') { // If the dropdown is currently hidden
dropdownContentStyle = 'show'; // Show the dropdown
document.addEventListener('click', handleClickAway); // Add a click event listener to detect clicks outside of the dropdown
} else { // If the dropdown is currently shown
dropdownContentStyle = 'hidden'; // Hide the dropdown
document.removeEventListener('click', handleClickAway); // Remove the click event listener
}
};
const handleClickAway = (event) => {
const dropdownContainer = document.getElementById(`dropdown-container-${randomString}`); // Get the dropdown container element
if (!dropdownContainer.contains(event.target)) { // If the clicked element is not a child of the dropdown container
dropdownContentStyle = 'hidden'; // Hide the dropdown
document.removeEventListener('click', handleClickAway); // Remove the click event listener
}
};
const positionDropdown = () => {
// button component variables
const dropdownButton = document.getElementById(`dropdown-button-${randomString}`);
const component = dropdownButton.closest('.component');
const parentOfComponent = component.parentElement.parentElement; // set table cell
const isTable = component.parentElement.parentElement.classList.contains('spectrum-Table-cell'); // check if the button is inside a table
const dropdownContainer = document.getElementById(`dropdown-container-${randomString}`);
const dropdownContent = document.getElementById(`dropdown-content-${randomString}`);
// div const for rects
const containerRect = dropdownContainer.getBoundingClientRect();
const contentRect = dropdownContent.getBoundingClientRect();
let tableRect = null;
// get table rect if inside a table
if (isTable) {
const table = parentOfComponent.closest('.spectrum-Table');
if (table) {
tableRect = table.getBoundingClientRect();
}
}
// spaceing consts
let spaceAbove = containerRect.top - contentRect.height;
let spaceBelow = window.innerHeight - containerRect.bottom - contentRect.height;
let spaceLeft = containerRect.left;
let spaceRight = window.innerWidth - containerRect.right;
// adjust space constants if inside table
if (tableRect) {
spaceAbove = containerRect.top - (tableRect.top + contentRect.height);
spaceBelow = tableRect.bottom - (containerRect.bottom + contentRect.height);
spaceLeft = containerRect.left - tableRect.left;
spaceRight = tableRect.right - containerRect.right;
}
dropdownContent.removeAttribute('style'); // reset style attr
dropdownContent.style.minWidth = width + 'px'; // set width of dropdown
switch (dPosition) {
case 'left':
dropdownContent.style.right = spaceLeft >= width || spaceLeft > spaceRight ? '100%' : 'auto';
dropdownContent.style.left = spaceLeft >= width || spaceLeft > spaceRight ? 'auto' : '100%';
dropdownContent.style.top = spaceBelow >= width || spaceBelow > spaceAbove ? '0%' : 'auto';
dropdownContent.style.bottom = spaceBelow >= width || spaceBelow > spaceAbove ? 'auto' : '0%';
break;
case 'right':
dropdownContent.style.left = spaceRight >= width || spaceRight > spaceLeft ? '100%' : 'auto';
dropdownContent.style.right = spaceRight >= width || spaceRight > spaceLeft ? 'auto' : '100%';
dropdownContent.style.top = spaceBelow >= width || spaceBelow > spaceAbove ? '0%' : 'auto';
dropdownContent.style.bottom = spaceBelow >= width || spaceBelow > spaceAbove ? 'auto' : '0%';
break;
case 'bottom':
dropdownContent.style.top = spaceBelow >= width || spaceBelow > spaceAbove ? '100%' : 'auto';
dropdownContent.style.bottom = spaceBelow >= width || spaceBelow > spaceAbove ? 'auto' : '100%';
dropdownContent.style.left = spaceRight >= width || spaceRight > spaceLeft ? '0%' : 'auto';
dropdownContent.style.right = spaceRight >= width || spaceRight > spaceLeft ? 'auto' : '0%';
break;
default:
dropdownContent.style.bottom = spaceAbove >= width || spaceAbove > spaceBelow ? '100%' : 'auto';
dropdownContent.style.top = spaceAbove >= width || spaceAbove > spaceBelow ? 'auto' : '100%';
dropdownContent.style.left = spaceRight >= width || spaceRight > spaceLeft ? '0%' : 'auto';
dropdownContent.style.right = spaceRight >= width || spaceRight > spaceLeft ? 'auto' : '0%';
break;
}
};
</script>

<!-- class:spectrum-Button--cta={cta}
class:spectrum-Button--primary={primary}
class:spectrum-Button--secondary={secondary}
class:spectrum-Button--warning={warning}
class:spectrum-Button--overBackground={overBackground} -->

<div id="dropdown-container-{randomString}" class="dropdown-container" use:styleable={$component.styles}>
<button
id="dropdown-button-{randomString}" on:click={dropdownButtonClicked}
class:spectrum-Button--quiet={quiet}
class:new-styles={newStyles}
class:disabled
class="spectrum-Button spectrum-Button--size{size.toUpperCase()} spectrum-Button--{type}"
{disabled}
>
<span>{buttonText}</span>
{#if icon != undefined }
<i
use:styleable={iconStyles}
class="{icon} {iconSize}"
/>
{:else}
<svg class="spectrum-Icon spectrum-UIIcon-ChevronDown100 {dropdownContentStyle}">
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
{/if}
</button>
<div id="dropdown-content-{randomString}" class="dropdown-content spectrum-Popover is-open {dropdownContentStyle}">
<slot />
</div>
</div>

<style>
.dropdown-container {
position: relative;
width: fit-content;
}
.dropdown-content {
display: none;
position: absolute;
z-index: 25;
padding: 1rem;
overflow: scroll;
max-width: 80vw;
max-height: 80vh;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.dropdown-content::-webkit-scrollbar {
display: none;
}
.dropdown-content.show {
display: flex!important;
flex-direction: column;
}
/* button styling */
.spectrum-Button--primary.new-styles {
background: var(--spectrum-global-color-gray-800);
border-color: transparent;
color: var(--spectrum-global-color-gray-50);
}
.spectrum-Button--primary.new-styles:hover {
background: var(--spectrum-global-color-gray-900);
}
.spectrum-Button--secondary.new-styles {
background: var(--spectrum-global-color-gray-200);
border-color: transparent;
color: var(--spectrum-global-color-gray-900);
}
.spectrum-Button--secondary.new-styles:not(.disabled):hover {
background: var(--spectrum-global-color-gray-300);
}
.spectrum-Button--secondary.new-styles.disabled {
color: var(--spectrum-global-color-gray-500);
}
.spectrum-Button {
display: flex;
align-items: center;
}
.spectrum-Button span {
display: block;
margin-right: 0.5rem;
}
.spectrum-Icon {
transition: transform 0.5s ease-in-out;
}
.spectrum-Icon.show {
transform: rotate(270deg);
}
</style>
4,166 changes: 4,166 additions & 0 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit 784ac23

Please sign in to comment.