forked from tsai/budibase
463 changed files with 8402 additions and 5215 deletions
@ -0,0 +1,61 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
const os = require("os") |
|||
const exec = require("child_process").exec |
|||
const fs = require("fs") |
|||
const platform = os.platform() |
|||
|
|||
const windows = platform === "win32" |
|||
const mac = platform === "darwin" |
|||
const linux = platform === "linux" |
|||
|
|||
function execute(command) { |
|||
return new Promise(resolve => { |
|||
exec(command, (err, stdout) => resolve(linux ? !!stdout : true)) |
|||
}) |
|||
} |
|||
|
|||
async function commandExistsUnix(command) { |
|||
const unixCmd = `command -v ${command} 2>/dev/null && { echo >&1 ${command}; exit 0; }` |
|||
return execute(command) |
|||
} |
|||
|
|||
async function commandExistsWindows(command) { |
|||
if (/[\x00-\x1f<>:"|?*]/.test(command)) { |
|||
return false |
|||
} |
|||
return execute(`where ${command}`) |
|||
} |
|||
|
|||
function commandExists(command) { |
|||
return windows ? commandExistsWindows(command) : commandExistsUnix(command) |
|||
} |
|||
|
|||
async function init() { |
|||
const docker = commandExists("docker") |
|||
const dockerCompose = commandExists("docker-compose") |
|||
if (docker && dockerCompose) { |
|||
console.log("Docker installed - continuing.") |
|||
return |
|||
} |
|||
if (mac) { |
|||
console.log( |
|||
"Please install docker by visiting: https://docs.docker.com/docker-for-mac/install/" |
|||
) |
|||
} else if (windows) { |
|||
console.log( |
|||
"Please install docker by visiting: https://docs.docker.com/docker-for-windows/install/" |
|||
) |
|||
} else if (linux) { |
|||
console.log("Beginning automated linux installation.") |
|||
await execute(`./hosting/scripts/linux/get-docker.sh`) |
|||
await execute(`./hosting/scripts/linux/get-docker-compose.sh`) |
|||
} else { |
|||
console.error( |
|||
"Platform unknown - please look online for information about installing docker for our OS." |
|||
) |
|||
} |
|||
console.log("Once installation complete please re-run the setup script.") |
|||
process.exit(-1) |
|||
} |
|||
init() |
|||
@ -1,18 +1,27 @@ |
|||
{ |
|||
"name": "@budibase/auth", |
|||
"version": "0.0.1", |
|||
"version": "0.18.6", |
|||
"description": "Authentication middlewares for budibase builder and apps", |
|||
"main": "src/index.js", |
|||
"author": "Budibase", |
|||
"license": "AGPL-3.0", |
|||
"dependencies": { |
|||
"aws-sdk": "^2.901.0", |
|||
"bcryptjs": "^2.4.3", |
|||
"ioredis": "^4.27.1", |
|||
"jsonwebtoken": "^8.5.1", |
|||
"koa-passport": "^4.1.4", |
|||
"node-fetch": "^2.6.1", |
|||
"passport-google-auth": "^1.0.2", |
|||
"passport-google-oauth": "^2.0.0", |
|||
"passport-jwt": "^4.0.0", |
|||
"passport-local": "^1.0.0", |
|||
"uuid": "^8.3.2" |
|||
"sanitize-s3-objectkey": "^0.0.1", |
|||
"tar-fs": "^2.1.1", |
|||
"uuid": "^8.3.2", |
|||
"zlib": "^1.0.5" |
|||
}, |
|||
"devDependencies": { |
|||
"ioredis-mock": "^5.5.5" |
|||
} |
|||
} |
|||
|
|||
@ -1,5 +1,19 @@ |
|||
function isTest() { |
|||
return ( |
|||
process.env.NODE_ENV === "jest" || |
|||
process.env.NODE_ENV === "cypress" || |
|||
process.env.JEST_WORKER_ID != null |
|||
) |
|||
} |
|||
|
|||
module.exports = { |
|||
JWT_SECRET: process.env.JWT_SECRET, |
|||
COUCH_DB_URL: process.env.COUCH_DB_URL, |
|||
SALT_ROUNDS: process.env.SALT_ROUNDS, |
|||
REDIS_URL: process.env.REDIS_URL, |
|||
REDIS_PASSWORD: process.env.REDIS_PASSWORD, |
|||
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, |
|||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, |
|||
MINIO_URL: process.env.MINIO_URL, |
|||
isTest, |
|||
} |
|||
|
|||
@ -0,0 +1,240 @@ |
|||
const sanitize = require("sanitize-s3-objectkey") |
|||
const AWS = require("aws-sdk") |
|||
const stream = require("stream") |
|||
const fetch = require("node-fetch") |
|||
const tar = require("tar-fs") |
|||
const zlib = require("zlib") |
|||
const { promisify } = require("util") |
|||
const { join } = require("path") |
|||
const fs = require("fs") |
|||
const env = require("../environment") |
|||
const { budibaseTempDir, ObjectStoreBuckets } = require("./utils") |
|||
const { v4 } = require("uuid") |
|||
|
|||
const streamPipeline = promisify(stream.pipeline) |
|||
// use this as a temporary store of buckets that are being created
|
|||
const STATE = { |
|||
bucketCreationPromises: {}, |
|||
} |
|||
|
|||
const CONTENT_TYPE_MAP = { |
|||
html: "text/html", |
|||
css: "text/css", |
|||
js: "application/javascript", |
|||
} |
|||
const STRING_CONTENT_TYPES = [ |
|||
CONTENT_TYPE_MAP.html, |
|||
CONTENT_TYPE_MAP.css, |
|||
CONTENT_TYPE_MAP.js, |
|||
] |
|||
|
|||
function publicPolicy(bucketName) { |
|||
return { |
|||
Version: "2012-10-17", |
|||
Statement: [ |
|||
{ |
|||
Effect: "Allow", |
|||
Principal: { |
|||
AWS: ["*"], |
|||
}, |
|||
Action: "s3:GetObject", |
|||
Resource: [`arn:aws:s3:::${bucketName}/*`], |
|||
}, |
|||
], |
|||
} |
|||
} |
|||
|
|||
const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS, ObjectStoreBuckets.GLOBAL] |
|||
|
|||
/** |
|||
* Gets a connection to the object store using the S3 SDK. |
|||
* @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from. |
|||
* @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. |
|||
* @constructor |
|||
*/ |
|||
exports.ObjectStore = bucket => { |
|||
AWS.config.update({ |
|||
accessKeyId: env.MINIO_ACCESS_KEY, |
|||
secretAccessKey: env.MINIO_SECRET_KEY, |
|||
}) |
|||
const config = { |
|||
s3ForcePathStyle: true, |
|||
signatureVersion: "v4", |
|||
params: { |
|||
Bucket: bucket, |
|||
}, |
|||
} |
|||
if (env.MINIO_URL) { |
|||
config.endpoint = env.MINIO_URL |
|||
} |
|||
return new AWS.S3(config) |
|||
} |
|||
|
|||
/** |
|||
* Given an object store and a bucket name this will make sure the bucket exists, |
|||
* if it does not exist then it will create it. |
|||
*/ |
|||
exports.makeSureBucketExists = async (client, bucketName) => { |
|||
try { |
|||
await client |
|||
.headBucket({ |
|||
Bucket: bucketName, |
|||
}) |
|||
.promise() |
|||
} catch (err) { |
|||
const promises = STATE.bucketCreationPromises |
|||
if (promises[bucketName]) { |
|||
await promises[bucketName] |
|||
} else if (err.statusCode === 404) { |
|||
// bucket doesn't exist create it
|
|||
promises[bucketName] = client |
|||
.createBucket({ |
|||
Bucket: bucketName, |
|||
}) |
|||
.promise() |
|||
await promises[bucketName] |
|||
delete promises[bucketName] |
|||
// public buckets are quite hidden in the system, make sure
|
|||
// no bucket is set accidentally
|
|||
if (PUBLIC_BUCKETS.includes(bucketName)) { |
|||
await client |
|||
.putBucketPolicy({ |
|||
Bucket: bucketName, |
|||
Policy: JSON.stringify(publicPolicy(bucketName)), |
|||
}) |
|||
.promise() |
|||
} |
|||
} else { |
|||
throw err |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Uploads the contents of a file given the required parameters, useful when |
|||
* temp files in use (for example file uploaded as an attachment). |
|||
*/ |
|||
exports.upload = async ({ bucket, filename, path, type, metadata }) => { |
|||
const extension = [...filename.split(".")].pop() |
|||
const fileBytes = fs.readFileSync(path) |
|||
|
|||
const objectStore = exports.ObjectStore(bucket) |
|||
await exports.makeSureBucketExists(objectStore, bucket) |
|||
|
|||
const config = { |
|||
// windows file paths need to be converted to forward slashes for s3
|
|||
Key: sanitize(filename).replace(/\\/g, "/"), |
|||
Body: fileBytes, |
|||
ContentType: type || CONTENT_TYPE_MAP[extension.toLowerCase()], |
|||
} |
|||
if (metadata) { |
|||
config.Metadata = metadata |
|||
} |
|||
return objectStore.upload(config).promise() |
|||
} |
|||
|
|||
/** |
|||
* Similar to the upload function but can be used to send a file stream |
|||
* through to the object store. |
|||
*/ |
|||
exports.streamUpload = async (bucket, filename, stream) => { |
|||
const objectStore = exports.ObjectStore(bucket) |
|||
await exports.makeSureBucketExists(objectStore, bucket) |
|||
|
|||
const params = { |
|||
Bucket: bucket, |
|||
Key: sanitize(filename).replace(/\\/g, "/"), |
|||
Body: stream, |
|||
} |
|||
return objectStore.upload(params).promise() |
|||
} |
|||
|
|||
/** |
|||
* retrieves the contents of a file from the object store, if it is a known content type it |
|||
* will be converted, otherwise it will be returned as a buffer stream. |
|||
*/ |
|||
exports.retrieve = async (bucket, filepath) => { |
|||
const objectStore = exports.ObjectStore(bucket) |
|||
const params = { |
|||
Bucket: bucket, |
|||
Key: sanitize(filepath).replace(/\\/g, "/"), |
|||
} |
|||
const response = await objectStore.getObject(params).promise() |
|||
// currently these are all strings
|
|||
if (STRING_CONTENT_TYPES.includes(response.ContentType)) { |
|||
return response.Body.toString("utf8") |
|||
} else { |
|||
return response.Body |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Same as retrieval function but puts to a temporary file. |
|||
*/ |
|||
exports.retrieveToTmp = async (bucket, filepath) => { |
|||
const data = await exports.retrieve(bucket, filepath) |
|||
const outputPath = join(budibaseTempDir(), v4()) |
|||
fs.writeFileSync(outputPath, data) |
|||
return outputPath |
|||
} |
|||
|
|||
exports.deleteFolder = async (bucket, folder) => { |
|||
const client = exports.ObjectStore(bucket) |
|||
const listParams = { |
|||
Bucket: bucket, |
|||
Prefix: folder, |
|||
} |
|||
|
|||
let response = await client.listObjects(listParams).promise() |
|||
if (response.Contents.length === 0) { |
|||
return |
|||
} |
|||
const deleteParams = { |
|||
Bucket: bucket, |
|||
Delete: { |
|||
Objects: [], |
|||
}, |
|||
} |
|||
|
|||
response.Contents.forEach(content => { |
|||
deleteParams.Delete.Objects.push({ Key: content.Key }) |
|||
}) |
|||
|
|||
response = await client.deleteObjects(deleteParams).promise() |
|||
// can only empty 1000 items at once
|
|||
if (response.Deleted.length === 1000) { |
|||
return exports.deleteFolder(bucket, folder) |
|||
} |
|||
} |
|||
|
|||
exports.uploadDirectory = async (bucket, localPath, bucketPath) => { |
|||
let uploads = [] |
|||
const files = fs.readdirSync(localPath, { withFileTypes: true }) |
|||
for (let file of files) { |
|||
const path = join(bucketPath, file.name) |
|||
const local = join(localPath, file.name) |
|||
if (file.isDirectory()) { |
|||
uploads.push(exports.uploadDirectory(bucket, local, path)) |
|||
} else { |
|||
uploads.push( |
|||
exports.streamUpload(bucket, path, fs.createReadStream(local)) |
|||
) |
|||
} |
|||
} |
|||
await Promise.all(uploads) |
|||
} |
|||
|
|||
exports.downloadTarball = async (url, bucket, path) => { |
|||
const response = await fetch(url) |
|||
if (!response.ok) { |
|||
throw new Error(`unexpected response ${response.statusText}`) |
|||
} |
|||
|
|||
const tmpPath = join(budibaseTempDir(), path) |
|||
await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) |
|||
if (!env.isTest()) { |
|||
await exports.uploadDirectory(bucket, tmpPath, path) |
|||
} |
|||
// return the temporary path incase there is a use for it
|
|||
return tmpPath |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
const { join } = require("path") |
|||
const { tmpdir } = require("os") |
|||
|
|||
exports.ObjectStoreBuckets = { |
|||
BACKUPS: "backups", |
|||
APPS: "prod-budi-app-assets", |
|||
TEMPLATES: "templates", |
|||
GLOBAL: "global", |
|||
} |
|||
|
|||
exports.budibaseTempDir = function () { |
|||
return join(tmpdir(), ".budibase") |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
const env = require("../environment") |
|||
// ioredis mock is all in memory
|
|||
const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") |
|||
const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") |
|||
|
|||
const CLUSTERED = false |
|||
|
|||
// for testing just generate the client once
|
|||
let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null |
|||
|
|||
/** |
|||
* Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise |
|||
* will return the ioredis client which will be ready to use. |
|||
* @return {Promise<object>} The ioredis client. |
|||
*/ |
|||
function init() { |
|||
return new Promise((resolve, reject) => { |
|||
// testing uses a single in memory client
|
|||
if (env.isTest()) { |
|||
return resolve(CLIENT) |
|||
} |
|||
// if a connection existed, close it and re-create it
|
|||
if (CLIENT) { |
|||
CLIENT.disconnect() |
|||
CLIENT = null |
|||
} |
|||
const { opts, host, port } = getRedisOptions(CLUSTERED) |
|||
if (CLUSTERED) { |
|||
CLIENT = new Redis.Cluster([{ host, port }], opts) |
|||
} else { |
|||
CLIENT = new Redis(opts) |
|||
} |
|||
CLIENT.on("end", err => { |
|||
reject(err) |
|||
}) |
|||
CLIENT.on("error", err => { |
|||
reject(err) |
|||
}) |
|||
CLIENT.on("connect", () => { |
|||
resolve(CLIENT) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Utility function, takes a redis stream and converts it to a promisified response - |
|||
* this can only be done with redis streams because they will have an end. |
|||
* @param stream A redis stream, specifically as this type of stream will have an end. |
|||
* @return {Promise<object>} The final output of the stream |
|||
*/ |
|||
function promisifyStream(stream) { |
|||
return new Promise((resolve, reject) => { |
|||
const outputKeys = new Set() |
|||
stream.on("data", keys => { |
|||
keys.forEach(key => { |
|||
outputKeys.add(key) |
|||
}) |
|||
}) |
|||
stream.on("error", err => { |
|||
reject(err) |
|||
}) |
|||
stream.on("end", async () => { |
|||
const keysArray = Array.from(outputKeys) |
|||
try { |
|||
let getPromises = [] |
|||
for (let key of keysArray) { |
|||
getPromises.push(CLIENT.get(key)) |
|||
} |
|||
const jsonArray = await Promise.all(getPromises) |
|||
resolve( |
|||
keysArray.map(key => ({ |
|||
key: removeDbPrefix(key), |
|||
value: JSON.parse(jsonArray.shift()), |
|||
})) |
|||
) |
|||
} catch (err) { |
|||
reject(err) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
class RedisWrapper { |
|||
constructor(db) { |
|||
this._db = db |
|||
} |
|||
|
|||
async init() { |
|||
this._client = await init() |
|||
return this |
|||
} |
|||
|
|||
async finish() { |
|||
this._client.disconnect() |
|||
} |
|||
|
|||
async scan() { |
|||
const db = this._db, |
|||
client = this._client |
|||
let stream |
|||
if (CLUSTERED) { |
|||
let node = client.nodes("master") |
|||
stream = node[0].scanStream({ match: db + "-*", count: 100 }) |
|||
} else { |
|||
stream = client.scanStream({ match: db + "-*", count: 100 }) |
|||
} |
|||
return promisifyStream(stream) |
|||
} |
|||
|
|||
async get(key) { |
|||
const db = this._db, |
|||
client = this._client |
|||
let response = await client.get(addDbPrefix(db, key)) |
|||
// overwrite the prefixed key
|
|||
if (response != null && response.key) { |
|||
response.key = key |
|||
} |
|||
// if its not an object just return the response
|
|||
try { |
|||
return JSON.parse(response) |
|||
} catch (err) { |
|||
return response |
|||
} |
|||
} |
|||
|
|||
async store(key, value, expirySeconds = null) { |
|||
const db = this._db, |
|||
client = this._client |
|||
if (typeof value === "object") { |
|||
value = JSON.stringify(value) |
|||
} |
|||
const prefixedKey = addDbPrefix(db, key) |
|||
await client.set(prefixedKey, value) |
|||
if (expirySeconds) { |
|||
await client.expire(prefixedKey, expirySeconds) |
|||
} |
|||
} |
|||
|
|||
async delete(key) { |
|||
const db = this._db, |
|||
client = this._client |
|||
await client.del(addDbPrefix(db, key)) |
|||
} |
|||
|
|||
async clear() { |
|||
const db = this._db |
|||
let items = await this.scan(db) |
|||
await Promise.all(items.map(obj => this.delete(db, obj.key))) |
|||
} |
|||
} |
|||
|
|||
module.exports = RedisWrapper |
|||
@ -0,0 +1,46 @@ |
|||
const env = require("../environment") |
|||
|
|||
const SLOT_REFRESH_MS = 2000 |
|||
const CONNECT_TIMEOUT_MS = 10000 |
|||
const SEPARATOR = "-" |
|||
const REDIS_URL = !env.REDIS_URL ? "localhost:6379" : env.REDIS_URL |
|||
const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD |
|||
|
|||
exports.Databases = { |
|||
PW_RESETS: "pwReset", |
|||
INVITATIONS: "invitation", |
|||
} |
|||
|
|||
exports.getRedisOptions = (clustered = false) => { |
|||
const [host, port] = REDIS_URL.split(":") |
|||
const opts = { |
|||
connectTimeout: CONNECT_TIMEOUT_MS, |
|||
} |
|||
if (clustered) { |
|||
opts.redisOptions = {} |
|||
opts.redisOptions.tls = {} |
|||
opts.redisOptions.password = REDIS_PASSWORD |
|||
opts.slotsRefreshTimeout = SLOT_REFRESH_MS |
|||
opts.dnsLookup = (address, callback) => callback(null, address) |
|||
} else { |
|||
opts.host = host |
|||
opts.port = port |
|||
opts.password = REDIS_PASSWORD |
|||
} |
|||
return { opts, host, port } |
|||
} |
|||
|
|||
exports.addDbPrefix = (db, key) => { |
|||
return `${db}${SEPARATOR}${key}` |
|||
} |
|||
|
|||
exports.removeDbPrefix = key => { |
|||
let parts = key.split(SEPARATOR) |
|||
if (parts.length >= 2) { |
|||
parts.shift() |
|||
return parts.join(SEPARATOR) |
|||
} else { |
|||
// return the only part
|
|||
return parts[0] |
|||
} |
|||
} |
|||
@ -1,12 +1,53 @@ |
|||
<script> |
|||
import "@spectrum-css/avatar/dist/index-vars.css" |
|||
let sizes = new Map([ |
|||
["XXS", "--spectrum-alias-avatar-size-50"], |
|||
["XS", "--spectrum-alias-avatar-size-75"], |
|||
["S", "--spectrum-alias-avatar-size-200"], |
|||
["M", "--spectrum-alias-avatar-size-300"], |
|||
["L", "--spectrum-alias-avatar-size-500"], |
|||
["XL", "--spectrum-alias-avatar-size-600"], |
|||
["XXL", "--spectrum-alias-avatar-size-700"], |
|||
]) |
|||
export let size = "M" |
|||
export let url = "" |
|||
export let disabled = false |
|||
export let name = "John Doe" |
|||
|
|||
function getInitials(name) { |
|||
let parts = name.split(" ") |
|||
return parts.map(name => name[0]).join("") |
|||
} |
|||
</script> |
|||
|
|||
<img |
|||
class:is-disabled={disabled} |
|||
class="spectrum-Avatar" |
|||
src={url} |
|||
alt="Avatar" |
|||
/> |
|||
{#if url} |
|||
<img |
|||
class:is-disabled={disabled} |
|||
class="spectrum-Avatar" |
|||
src={url} |
|||
alt="Avatar" |
|||
style="width: var({sizes.get(size)}); height: var({sizes.get(size)});" |
|||
/> |
|||
{:else} |
|||
<div |
|||
class:is-disabled={disabled} |
|||
style="width: var({sizes.get(size)}); height: var({sizes.get( |
|||
size |
|||
)}); font-size: calc(var({sizes.get(size)}) / 2)" |
|||
> |
|||
{getInitials(name)} |
|||
</div> |
|||
{/if} |
|||
|
|||
<style> |
|||
div { |
|||
color: white; |
|||
display: grid; |
|||
place-items: center; |
|||
font-weight: 500; |
|||
background: #3aab87; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
user-select: none; |
|||
} |
|||
</style> |
|||
|
|||
@ -1,15 +1,19 @@ |
|||
<script> |
|||
import "@spectrum-css/buttongroup/dist/index-vars.css" |
|||
export let vertical = false |
|||
import "@spectrum-css/buttongroup/dist/index-vars.css" |
|||
export let vertical = false |
|||
|
|||
function group(element) { |
|||
const buttons = Array.from(element.getElementsByTagName('button')) |
|||
buttons.forEach(button => { |
|||
button.classList.add('spectrum-ButtonGroup-item') |
|||
}) |
|||
} |
|||
function group(element) { |
|||
const buttons = Array.from(element.getElementsByTagName("button")) |
|||
buttons.forEach(button => { |
|||
button.classList.add("spectrum-ButtonGroup-item") |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<div use:group class="spectrum-ButtonGroup" class:spectrum-ButtonGroup--vertical={vertical}> |
|||
<slot /> |
|||
</div> |
|||
<div |
|||
use:group |
|||
class="spectrum-ButtonGroup" |
|||
class:spectrum-ButtonGroup--vertical={vertical} |
|||
> |
|||
<slot /> |
|||
</div> |
|||
|
|||
@ -1,14 +1,16 @@ |
|||
<script> |
|||
import "@spectrum-css/fieldlabel/dist/index-vars.css" |
|||
|
|||
export let size = "M" |
|||
</script> |
|||
|
|||
<label |
|||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeL spectrum-Form-itemLabel`}> |
|||
<label class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}> |
|||
<slot /> |
|||
</label> |
|||
|
|||
<style> |
|||
label { |
|||
padding: 0; |
|||
white-space: nowrap; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,26 @@ |
|||
<script> |
|||
export let wide = false |
|||
</script> |
|||
|
|||
<div class:wide> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
div { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: flex-start; |
|||
align-items: stretch; |
|||
max-width: 80ch; |
|||
margin: 0 auto; |
|||
padding: calc(var(--spacing-xl) * 2); |
|||
} |
|||
|
|||
.wide { |
|||
max-width: none; |
|||
margin: 0; |
|||
padding: var(--spacing-xl) calc(var(--spacing-xl) * 2) |
|||
calc(var(--spacing-xl) * 2) calc(var(--spacing-xl) * 2); |
|||
} |
|||
</style> |
|||
@ -1,7 +1,7 @@ |
|||
<script> |
|||
import "@spectrum-css/menu/dist/index-vars.css" |
|||
import "@spectrum-css/menu/dist/index-vars.css" |
|||
</script> |
|||
|
|||
<ul class="spectrum-Menu" role="menu"> |
|||
<slot /> |
|||
</ul> |
|||
<slot /> |
|||
</ul> |
|||
|
|||
@ -1,9 +1,10 @@ |
|||
<script> |
|||
export let heading |
|||
export let heading |
|||
</script> |
|||
|
|||
<li role="presentation"> |
|||
<span class="spectrum-Menu-sectionHeading">{heading}</span> |
|||
<ul class="spectrum-Menu" role="group"> |
|||
<slot /> |
|||
</ul> |
|||
</li> |
|||
<span class="spectrum-Menu-sectionHeading">{heading}</span> |
|||
<ul class="spectrum-Menu" role="group"> |
|||
<slot /> |
|||
</ul> |
|||
</li> |
|||
|
|||
@ -1 +1 @@ |
|||
<li class="spectrum-Menu-divider" role="separator"></li> |
|||
<li class="spectrum-Menu-divider" role="separator" /> |
|||
|
|||
@ -1,29 +1,62 @@ |
|||
<script> |
|||
// WIP! Does not yet work. |
|||
import "@spectrum-css/progresscircle/dist/index-vars.css" |
|||
import { tweened } from 'svelte/motion'; |
|||
import { cubicOut } from 'svelte/easing'; |
|||
import "@spectrum-css/progresscircle/dist/index-vars.css" |
|||
|
|||
export let value = false |
|||
export let small; |
|||
export let large; |
|||
export let size = "M" |
|||
function convertSize(size) { |
|||
switch (size) { |
|||
case "S": |
|||
return "small" |
|||
case "L": |
|||
return "large" |
|||
default: |
|||
return |
|||
} |
|||
} |
|||
|
|||
export let overBackground; |
|||
export let value = false |
|||
export let minValue = 0 |
|||
export let maxValue = 100 |
|||
|
|||
let subMask1Style |
|||
let subMask2Style |
|||
$: calculateSubMasks(value) |
|||
|
|||
function calculateSubMasks(value) { |
|||
if (value) { |
|||
let percentage = ((value - minValue) / (maxValue - minValue)) * 100 |
|||
let angle |
|||
if (percentage > 0 && percentage <= 50) { |
|||
angle = -180 + (percentage / 50) * 180 |
|||
subMask1Style = `transform: rotate(${angle}deg);` |
|||
subMask2Style = "transform: rotate(-180deg);" |
|||
} else if (percentage > 50) { |
|||
angle = -180 + ((percentage - 50) / 50) * 180 |
|||
subMask1Style = "transform: rotate(0deg);" |
|||
subMask2Style = `transform: rotate(${angle}deg);` |
|||
} |
|||
} |
|||
} |
|||
|
|||
export let overBackground |
|||
</script> |
|||
|
|||
<div class:spectrum-ProgressBar--indeterminate={!value} class:spectrum-ProgressCircle--small={small} class:spectrum-ProgressCircle--large={large} class="spectrum-ProgressCircle"> |
|||
<div class="spectrum-ProgressCircle-track"></div> |
|||
<div class="spectrum-ProgressCircle-fills"> |
|||
<div class="spectrum-ProgressCircle-fillMask1"> |
|||
<div class="spectrum-ProgressCircle-fillSubMask1"> |
|||
<div class="spectrum-ProgressCircle-fill"></div> |
|||
</div> |
|||
<div |
|||
on:click |
|||
class:spectrum-ProgressBar--indeterminate={!value} |
|||
class:spectrum-ProgressCircle--overBackground={overBackground} |
|||
class="spectrum-ProgressCircle spectrum-ProgressCircle--{convertSize(size)}" |
|||
> |
|||
<div class="spectrum-ProgressCircle-track" /> |
|||
<div class="spectrum-ProgressCircle-fills"> |
|||
<div class="spectrum-ProgressCircle-fillMask1"> |
|||
<div class="spectrum-ProgressCircle-fillSubMask1" style={subMask1Style}> |
|||
<div class="spectrum-ProgressCircle-fill" /> |
|||
</div> |
|||
<div class="spectrum-ProgressCircle-fillMask2"> |
|||
<div class="spectrum-ProgressCircle-fillSubMask2"> |
|||
<div class="spectrum-ProgressCircle-fill"></div> |
|||
</div> |
|||
</div> |
|||
<div class="spectrum-ProgressCircle-fillMask2"> |
|||
<div class="spectrum-ProgressCircle-fillSubMask2" style={subMask2Style}> |
|||
<div class="spectrum-ProgressCircle-fill" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
@ -1,30 +1,44 @@ |
|||
<script> |
|||
import { getContext } from 'svelte' |
|||
const multilevel = getContext('sidenav-type'); |
|||
export let href = ""; |
|||
export let external = false; |
|||
export let heading = "" |
|||
export let icon = ""; |
|||
export let selected = false; |
|||
export let disabled = false; |
|||
|
|||
import { getContext } from "svelte" |
|||
const multilevel = getContext("sidenav-type") |
|||
export let href = "" |
|||
export let external = false |
|||
export let heading = "" |
|||
export let icon = "" |
|||
export let selected = false |
|||
export let disabled = false |
|||
</script> |
|||
|
|||
<li class="spectrum-SideNav-item" class:is-selected={selected} class:is-disabled={disabled}> |
|||
{#if heading} |
|||
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">{heading}</h2> |
|||
{/if} |
|||
<a target={external ? '_blank' : '_self'} {href} class="spectrum-SideNav-itemLink" aria-current="page"> |
|||
{#if icon} |
|||
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-SideNav-itemIcon" focusable="false" aria-hidden="true"> |
|||
<use xlink:href="#spectrum-icon-18-{icon}" /> |
|||
</svg> |
|||
{/if} |
|||
<slot /> |
|||
</a> |
|||
{#if multilevel && $$slots.subnav} |
|||
<ul class="spectrum-SideNav"> |
|||
<slot name="subnav" /> |
|||
</ul> |
|||
<li |
|||
class="spectrum-SideNav-item" |
|||
class:is-selected={selected} |
|||
class:is-disabled={disabled} |
|||
> |
|||
{#if heading} |
|||
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}"> |
|||
{heading} |
|||
</h2> |
|||
{/if} |
|||
<a |
|||
target={external ? "_blank" : ""} |
|||
{href} |
|||
class="spectrum-SideNav-itemLink" |
|||
aria-current="page" |
|||
> |
|||
{#if icon} |
|||
<svg |
|||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-SideNav-itemIcon" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-icon-18-{icon}" /> |
|||
</svg> |
|||
{/if} |
|||
</li> |
|||
<slot /> |
|||
</a> |
|||
{#if multilevel && $$slots.subnav} |
|||
<ul class="spectrum-SideNav"> |
|||
<slot name="subnav" /> |
|||
</ul> |
|||
{/if} |
|||
</li> |
|||
|
|||
@ -1,12 +1,12 @@ |
|||
<script> |
|||
import { setContext } from 'svelte' |
|||
import "@spectrum-css/sidenav/dist/index-vars.css" |
|||
export let multilevel = false; |
|||
setContext('sidenav-type', multilevel) |
|||
import { setContext } from "svelte" |
|||
import "@spectrum-css/sidenav/dist/index-vars.css" |
|||
export let multilevel = false |
|||
setContext("sidenav-type", multilevel) |
|||
</script> |
|||
|
|||
<nav> |
|||
<ul class="spectrum-SideNav" class:spectrum-SideNav--multiLevel={multilevel}> |
|||
<slot /> |
|||
</ul> |
|||
</nav> |
|||
</nav> |
|||
|
|||
@ -1,72 +0,0 @@ |
|||
<script> |
|||
export let extraSmall = false, |
|||
small = false, |
|||
medium = false, |
|||
large = false, |
|||
extraLarge = false, |
|||
white = false, |
|||
grey = false, |
|||
black = false, |
|||
lh = false |
|||
</script> |
|||
|
|||
<p |
|||
class="bb-body" |
|||
class:extraSmall |
|||
class:small |
|||
class:medium |
|||
class:large |
|||
class:extraLarge |
|||
class:white |
|||
class:grey |
|||
class:black |
|||
class:lh> |
|||
<slot /> |
|||
</p> |
|||
|
|||
<style> |
|||
.bb-body { |
|||
font-family: var(--font-sans); |
|||
font-weight: 400; |
|||
text-rendering: var(--text-render); |
|||
color: var(--ink); |
|||
font-size: var(--font-size-m); |
|||
line-height: 0; |
|||
} |
|||
|
|||
.extraSmall { |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
|
|||
.small { |
|||
font-size: var(--font-size-s); |
|||
} |
|||
|
|||
.medium { |
|||
font-size: var(--font-size-m); |
|||
} |
|||
|
|||
.large { |
|||
font-size: var(--font-size-l); |
|||
} |
|||
|
|||
.extraLarge { |
|||
font-size: var(--font-size-xl); |
|||
} |
|||
|
|||
.white { |
|||
color: white; |
|||
} |
|||
|
|||
.grey { |
|||
color: var(--grey-6); |
|||
} |
|||
|
|||
.black { |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.lh { |
|||
line-height: 1.75; |
|||
} |
|||
</style> |
|||
@ -1,175 +0,0 @@ |
|||
<script> |
|||
import { View } from "svench"; |
|||
</script> |
|||
|
|||
<h1>Borders</h1> |
|||
<p>Budibase has 2 border variables, light and dark.</p> |
|||
<p><strong>Light</strong> is for layouts.</p> |
|||
<p><strong>Dark</strong> is for components.</p> |
|||
<code>border: var(--border-light);</code> |
|||
<div class="border-light"></div> |
|||
<code>border: var(--border-dark);</code> |
|||
<div class="border-dark"></div> |
|||
|
|||
<hr> |
|||
|
|||
<h2>Border Radius</h2> |
|||
<p>Budibase has 5 border-radius variables:</p> |
|||
|
|||
<table> |
|||
<tr> |
|||
<th>Name</th> |
|||
<th>Var</th> |
|||
<th>rem</th> |
|||
<th>px</th> |
|||
<th>Visual</th> |
|||
</tr> |
|||
<tr> |
|||
<td>Extra Small</td> |
|||
<td>var(--border-radius-xs)</td> |
|||
<td>0.125</td> |
|||
<td>2</td> |
|||
<td><div class="border-radius-xs">Extra small</div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>Small</td> |
|||
<td>var(--border-radius-s)</td> |
|||
<td>0.35</td> |
|||
<td>5.6px</td> |
|||
<td><div class="border-radius-s">Small</div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>Medium</td> |
|||
<td>var(--border-radius-m)</td> |
|||
<td>0.5</td> |
|||
<td>8</td> |
|||
<td><div class="border-radius-m">Medium</div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>Large</td> |
|||
<td>var(--border-radius-l)</td> |
|||
<td>1</td> |
|||
<td>16</td> |
|||
<td><div class="border-radius-l">Large</div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>Extra Large</td> |
|||
<td>var(--border-radius-xl)</td> |
|||
<td>100</td> |
|||
<td>1600</td> |
|||
<td><div class="border-radius-xl">Extra large</div></td> |
|||
</tr> |
|||
</table> |
|||
|
|||
<style> |
|||
|
|||
table { |
|||
font-family: var(--font-sans); |
|||
border-collapse: collapse; |
|||
width: 100%; |
|||
} |
|||
|
|||
code { |
|||
display: inline-block; |
|||
box-sizing: border-box; |
|||
padding: var(--spacing-s) var(--spacing-l); |
|||
margin: var(--spacing-xl) 0 var(--spacing-s) 0; |
|||
background-color: var(--grey-2); |
|||
color: var(--red-dark); |
|||
border-radius: var(--spacing-xs); |
|||
} |
|||
|
|||
td, th { |
|||
border: 1px solid var(--grey-4); |
|||
text-align: left; |
|||
padding: var(--spacing-m); |
|||
width: 20%; |
|||
} |
|||
|
|||
tr:nth-child(even) { |
|||
background-color: var(--grey-3); |
|||
} |
|||
|
|||
hr { |
|||
margin: var(--layout-xl) 0px; |
|||
} |
|||
|
|||
.border-light { |
|||
width: 40rem; |
|||
height: 10rem; |
|||
background-color: white; |
|||
border: var(--border-light); |
|||
} |
|||
|
|||
.border-dark { |
|||
width: 40rem; |
|||
height: 10rem; |
|||
background-color: white; |
|||
border: var(--border-dark); |
|||
} |
|||
|
|||
.border-radius-xs { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: white; |
|||
font-family: var(--font-sans); |
|||
width: 8rem; |
|||
height: 3rem; |
|||
background-color: black; |
|||
border-radius: var(--border-radius-xs); |
|||
margin-right: var(--spacing-l); |
|||
} |
|||
|
|||
.border-radius-s { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: white; |
|||
font-family: var(--font-sans); |
|||
width: 8rem; |
|||
height: 3rem; |
|||
background-color: black; |
|||
border-radius: var(--border-radius-s); |
|||
margin-right: var(--spacing-l); |
|||
} |
|||
|
|||
.border-radius-m { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: white; |
|||
font-family: var(--font-sans); |
|||
width: 8rem; |
|||
height: 3rem; |
|||
background-color: black; |
|||
border-radius: var(--border-radius-m); |
|||
margin-right: var(--spacing-l); |
|||
} |
|||
|
|||
.border-radius-l { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: white; |
|||
font-family: var(--font-sans); |
|||
width: 8rem; |
|||
height: 3rem; |
|||
background-color: black; |
|||
border-radius: var(--border-radius-l); |
|||
margin-right: var(--spacing-l); |
|||
} |
|||
|
|||
.border-radius-xl { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: white; |
|||
font-family: var(--font-sans); |
|||
width: 8rem; |
|||
height: 3rem; |
|||
background-color: black; |
|||
border-radius: var(--border-radius-xl); |
|||
margin-right: var(--spacing-l); |
|||
} |
|||
</style> |
|||
|
Before Width: | Height: | Size: 4.2 KiB |
@ -1,201 +0,0 @@ |
|||
<script> |
|||
import { View } from "svench"; |
|||
</script> |
|||
|
|||
<View name="Base"> |
|||
<div class="white">white</div> |
|||
<div class="grey1">var(--grey-1)</div> |
|||
<div class="grey2">var(--grey-2)</div> |
|||
<div class="grey3">var(--grey-3)</div> |
|||
<div class="grey4">var(--grey-4)</div> |
|||
<div class="grey5">var(--grey-5)</div> |
|||
<div class="grey6">var(--grey-6)</div> |
|||
<div class="grey7">var(--grey-7)</div> |
|||
<div class="grey8">var(--grey-8)</div> |
|||
<div class="grey9">var(--grey-9)</div> |
|||
<div class="ink">var(--ink)</div> |
|||
</View> |
|||
|
|||
<View name="Blue"> |
|||
<div class="blue-light">var(--blue-light)</div> |
|||
<div class="blue">var(--blue)</div> |
|||
<div class="blue-dark">var(--blue-dark)</div> |
|||
</View> |
|||
|
|||
<View name="Yellow"> |
|||
<div class="yellow-light">var(--yellow-light)</div> |
|||
<div class="yellow">var(--yellow)</div> |
|||
<div class="yellow-dark">var(--yellow-dark)</div> |
|||
</View> |
|||
|
|||
<View name="Red"> |
|||
<div class="red-light">var(--red-light)</div> |
|||
<div class="red">var(--red)</div> |
|||
<div class="red-dark">var(--red-dark)</div> |
|||
</View> |
|||
|
|||
<View name="Orange"> |
|||
<div class="orange-light">var(--orange-light)</div> |
|||
<div class="orange">var(--orange)</div> |
|||
<div class="orange-dark">var(--orange-dark)</div> |
|||
</View> |
|||
|
|||
<View name="Green"> |
|||
<div class="green-light">var(--green-light)</div> |
|||
<div class="green">var(--green)</div> |
|||
<div class="green-dark">var(--green-dark)</div> |
|||
</View> |
|||
|
|||
<View name="Purple"> |
|||
<div class="purple-light">var(--purple-light)</div> |
|||
<div class="purple">var(--purple)</div> |
|||
<div class="purple-dark">var(--purple-dark)</div> |
|||
</View> |
|||
|
|||
<style> |
|||
div { |
|||
height: 100px; |
|||
width: 800px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-family: var(--font-sans); |
|||
} |
|||
|
|||
.white { |
|||
background-color: white; |
|||
} |
|||
|
|||
.grey1 { |
|||
background-color: var(--grey-1); |
|||
} |
|||
|
|||
.grey1 { |
|||
background-color: var(--grey-1); |
|||
} |
|||
|
|||
.grey2 { |
|||
background-color: var(--grey-2); |
|||
} |
|||
|
|||
.grey3 { |
|||
background-color: var(--grey-3); |
|||
} |
|||
|
|||
.grey4 { |
|||
background-color: var(--grey-4); |
|||
} |
|||
|
|||
.grey5 { |
|||
background-color: var(--grey-5); |
|||
} |
|||
|
|||
.grey6 { |
|||
background-color: var(--grey-6); |
|||
} |
|||
|
|||
.grey7 { |
|||
background-color: var(--grey-7); |
|||
color: white; |
|||
} |
|||
|
|||
.grey8 { |
|||
background-color: var(--grey-8); |
|||
color: white; |
|||
} |
|||
|
|||
.grey9 { |
|||
background-color: var(--grey-9); |
|||
color: white; |
|||
} |
|||
|
|||
.ink { |
|||
background-color: var(--ink); |
|||
color: white; |
|||
} |
|||
|
|||
.blue { |
|||
background-color: var(--blue); |
|||
color: white; |
|||
} |
|||
|
|||
.blue-light { |
|||
background-color: var(--blue-light); |
|||
} |
|||
|
|||
.blue-dark { |
|||
background-color: var(--blue-dark); |
|||
color: white; |
|||
} |
|||
|
|||
.yellow { |
|||
background-color: var(--yellow); |
|||
color: white; |
|||
} |
|||
|
|||
.yellow-light { |
|||
background-color: var(--yellow-light); |
|||
} |
|||
|
|||
.yellow-dark { |
|||
background-color: var(--yellow-dark); |
|||
color: white; |
|||
} |
|||
|
|||
.red { |
|||
background-color: var(--red); |
|||
color: white; |
|||
} |
|||
|
|||
.red-light { |
|||
background-color: var(--red-light); |
|||
} |
|||
|
|||
.red-dark { |
|||
background-color: var(--red-dark); |
|||
color: white; |
|||
} |
|||
|
|||
.orange { |
|||
background-color: var(--orange); |
|||
color: white; |
|||
} |
|||
|
|||
.orange-light { |
|||
background-color: var(--orange-light); |
|||
} |
|||
|
|||
.orange-dark { |
|||
background-color: var(--orange-dark); |
|||
color: white; |
|||
} |
|||
|
|||
.green { |
|||
background-color: var(--green); |
|||
color: white; |
|||
} |
|||
|
|||
.green-light { |
|||
background-color: var(--green-light); |
|||
} |
|||
|
|||
.green-dark { |
|||
background-color: var(--green-dark); |
|||
color: white; |
|||
} |
|||
|
|||
.purple { |
|||
background-color: var(--purple); |
|||
color: white; |
|||
} |
|||
|
|||
.purple-light { |
|||
background-color: var(--purple-light); |
|||
} |
|||
|
|||
.purple-dark { |
|||
background-color: var(--purple-dark); |
|||
color: white; |
|||
} |
|||
|
|||
</style> |
|||
@ -1,74 +0,0 @@ |
|||
<script> |
|||
export let extraSmall = false, |
|||
small = false, |
|||
medium = false, |
|||
large = false, |
|||
extraLarge = false, |
|||
white = false, |
|||
grey = false, |
|||
black = false, |
|||
lh = false |
|||
</script> |
|||
|
|||
<h1 |
|||
class="bb-heading" |
|||
class:extraSmall |
|||
class:small |
|||
class:medium |
|||
class:large |
|||
class:extraLarge |
|||
class:white |
|||
class:grey |
|||
class:black |
|||
class:lh> |
|||
<slot /> |
|||
</h1> |
|||
|
|||
<style> |
|||
.bb-heading { |
|||
font-family: var(--font-sans); |
|||
font-weight: 600; |
|||
text-rendering: var(--text-render); |
|||
color: var(--ink); |
|||
font-size: var(--heading-font-size-m); |
|||
line-height: 0; |
|||
} |
|||
|
|||
.extraSmall { |
|||
font-size: var(--heading-font-size-xs); |
|||
} |
|||
|
|||
.small { |
|||
font-size: var(--heading-font-size-s); |
|||
} |
|||
|
|||
.medium { |
|||
font-size: var(--heading-font-size-m); |
|||
} |
|||
|
|||
.large { |
|||
font-size: var(--heading-font-size-l); |
|||
font-weight: 700; |
|||
} |
|||
|
|||
.extraLarge { |
|||
font-size: var(--heading-font-size-xl); |
|||
font-weight: 700; |
|||
} |
|||
|
|||
.white { |
|||
color: white; |
|||
} |
|||
|
|||
.grey { |
|||
color: var(--grey-6); |
|||
} |
|||
|
|||
.black { |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.lh { |
|||
line-height: 1.3; |
|||
} |
|||
</style> |
|||
@ -1,16 +0,0 @@ |
|||
<script> |
|||
import BudibaseLogo from "./Budibase-logo.svelte" |
|||
</script> |
|||
|
|||
<div class="home-logo"> |
|||
<BudibaseLogo /> |
|||
</div> |
|||
|
|||
<style> |
|||
.home-logo { |
|||
cursor: pointer; |
|||
height: 40px; |
|||
width: 100px; |
|||
margin-bottom: 20px; |
|||
} |
|||
</style> |
|||
@ -1,8 +0,0 @@ |
|||
<script> |
|||
import { View } from "svench"; |
|||
import Logo from "./Logo.svelte"; |
|||
</script> |
|||
|
|||
<View name="default"> |
|||
<Logo /> |
|||
</View> |
|||
@ -1,191 +0,0 @@ |
|||
<script> |
|||
import { View } from "svench"; |
|||
</script> |
|||
|
|||
<h1>2 Scales, 1 Spatial System</h1> |
|||
<p>Budibase has 2 scales, spacing and layout, and follows a 4pt grid system.</p> |
|||
<p><strong>Spacing</strong> is used for smaller, more refined spacing needs, specifically within the context of a component.</p> |
|||
<p><strong>Layout</strong> is used for laying out your page and positing components within that page.</p> |
|||
<p>Spacing and layout will mostly exist alongside margin and padding.</p> |
|||
<p><code>margin: var(--spacing-s);</code></p> |
|||
<code>padding: var(--spacing-s) var(--spacing-l);</code> |
|||
|
|||
<hr> |
|||
|
|||
<h2>Spacing</h2> |
|||
|
|||
<table> |
|||
<tr> |
|||
<th>Var</th> |
|||
<th>rem</th> |
|||
<th>px</th> |
|||
<th>Visual</th> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--spacing-xs)</td> |
|||
<td>0.25</td> |
|||
<td>4</td> |
|||
<td><div class="xs"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--spacing-s)</td> |
|||
<td>0.5</td> |
|||
<td>8</td> |
|||
<td><div class="s"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--spacing-m)</td> |
|||
<td>.75</td> |
|||
<td>12</td> |
|||
<td><div class="m"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--spacing-l)</td> |
|||
<td>1</td> |
|||
<td>16</td> |
|||
<td><div class="l"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--spacing-xl)</td> |
|||
<td>1.25</td> |
|||
<td>20</td> |
|||
<td><div class="xl"></div></td> |
|||
</tr> |
|||
</table> |
|||
|
|||
<hr> |
|||
|
|||
<h2>Layout</h2> |
|||
|
|||
<table> |
|||
<tr> |
|||
<th>Var</th> |
|||
<th>rem</th> |
|||
<th>px</th> |
|||
<th>Visual</th> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--layout-xs)</td> |
|||
<td>1.25</td> |
|||
<td>20</td> |
|||
<td><div class="layout-xs"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--layout-s)</td> |
|||
<td>1.5</td> |
|||
<td>24</td> |
|||
<td><div class="layout-s"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--layout-m)</td> |
|||
<td>2</td> |
|||
<td>32</td> |
|||
<td><div class="layout-m"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--layout-l)</td> |
|||
<td>3</td> |
|||
<td>48</td> |
|||
<td><div class="layout-l"></div></td> |
|||
</tr> |
|||
<tr> |
|||
<td>var(--layout-xl)</td> |
|||
<td>4</td> |
|||
<td>64</td> |
|||
<td><div class="layout-xl"></div></td> |
|||
</tr> |
|||
</table> |
|||
|
|||
<style> |
|||
|
|||
table { |
|||
font-family: var(--font-sans); |
|||
border-collapse: collapse; |
|||
width: 100%; |
|||
} |
|||
|
|||
code { |
|||
display: inline-block; |
|||
box-sizing: border-box; |
|||
padding: var(--spacing-s) var(--spacing-l); |
|||
margin: var(--spacing-s) 0; |
|||
background-color: var(--grey-2); |
|||
color: var(--red-dark); |
|||
border-radius: var(--spacing-xs); |
|||
} |
|||
|
|||
td, th { |
|||
border: 1px solid var(--grey-4); |
|||
text-align: left; |
|||
padding: var(--spacing-m); |
|||
width: 25%; |
|||
} |
|||
|
|||
tr:nth-child(even) { |
|||
background-color: var(--grey-3); |
|||
} |
|||
|
|||
hr { |
|||
margin: var(--layout-xl) 0px; |
|||
} |
|||
|
|||
.xs { |
|||
height: var(--spacing-xs); |
|||
width: var(--spacing-xs); |
|||
background-color: var(--purple); |
|||
} |
|||
|
|||
.s { |
|||
height: var(--spacing-s); |
|||
width: var(--spacing-s); |
|||
background-color: var(--purple); |
|||
} |
|||
|
|||
.m { |
|||
height: var(--spacing-m); |
|||
width: var(--spacing-m); |
|||
background-color: var(--purple); |
|||
} |
|||
|
|||
.l { |
|||
height: var(--spacing-l); |
|||
width: var(--spacing-l); |
|||
background-color: var(--purple); |
|||
} |
|||
|
|||
.xl { |
|||
height: var(--spacing-xl); |
|||
width: var(--spacing-xl); |
|||
background-color: var(--purple); |
|||
} |
|||
|
|||
.layout-xs { |
|||
height: var(--layout-xs); |
|||
width: var(--layout-xs); |
|||
background-color: var(--blue); |
|||
} |
|||
|
|||
.layout-s { |
|||
height: var(--layout-s); |
|||
width: var(--layout-s); |
|||
background-color: var(--blue); |
|||
} |
|||
|
|||
.layout-m { |
|||
height: var(--layout-m); |
|||
width: var(--layout-m); |
|||
background-color: var(--blue); |
|||
} |
|||
|
|||
.layout-l { |
|||
height: var(--layout-l); |
|||
width: var(--layout-l); |
|||
background-color: var(--blue); |
|||
} |
|||
|
|||
.layout-xl { |
|||
height: var(--layout-xl); |
|||
width: var(--layout-xl); |
|||
background-color: var(--blue); |
|||
} |
|||
</style> |
|||
@ -1,227 +0,0 @@ |
|||
<script> |
|||
import { View } from "svench"; |
|||
import Heading from "./Heading.svelte"; |
|||
import Body from "./Body.svelte"; |
|||
import Label from "./Label.svelte"; |
|||
|
|||
const username = "bbuserexample"; |
|||
</script> |
|||
|
|||
<style> |
|||
.typography-container { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
} |
|||
|
|||
.typography-column { |
|||
padding: var(--spacing-l); |
|||
} |
|||
|
|||
.typography-column--grey { |
|||
background-color: var(--grey-5); |
|||
} |
|||
|
|||
.label-wrapper { |
|||
padding: var(--spacing-m); |
|||
} |
|||
</style> |
|||
|
|||
<h1 class="bb-heading bb-heading--large bb-heading--heavy">Typography</h1> |
|||
<p> |
|||
Budibase uses Inter as its typeface and three types can be used, |
|||
<strong>heading</strong> |
|||
, |
|||
<strong>body</strong> |
|||
and |
|||
<strong>label</strong> |
|||
</p> |
|||
<p>Each type has 5 sizes, xs, s, m l and xl.</p> |
|||
<p>Each type is available in the colors white, grey or black.</p> |
|||
|
|||
<View name="Heading"> |
|||
<div class="typography-container"> |
|||
<div class="typography-column"> |
|||
<Heading extraLarge black>Heading Extra Large Black</Heading> |
|||
|
|||
<Heading large black>Heading Large Black</Heading> |
|||
|
|||
<Heading medium black>Heading Medium Black</Heading> |
|||
|
|||
<Heading small black>Heading Small Black</Heading> |
|||
|
|||
<Heading extraSmall black>Heading Extra Small Black</Heading> |
|||
</div> |
|||
|
|||
<div class="typography-column"> |
|||
<Heading extraLarge grey>Heading Extra Large Grey</Heading> |
|||
|
|||
<Heading large grey>Heading Large Grey</Heading> |
|||
|
|||
<Heading medium grey>Heading Medium Grey</Heading> |
|||
|
|||
<Heading small grey>Heading Small Grey</Heading> |
|||
|
|||
<Heading extraSmall grey>Heading Extra Small Grey</Heading> |
|||
</div> |
|||
|
|||
<div class="typography-column typography-column--grey"> |
|||
<Heading extraLarge white>Heading Extra Large White</Heading> |
|||
|
|||
<Heading large white>Heading Large White</Heading> |
|||
|
|||
<Heading medium white>Heading Medium White</Heading> |
|||
|
|||
<Heading small white>Heading Small White</Heading> |
|||
|
|||
<Heading extraSmall white>Heading Extra Small White</Heading> |
|||
</div> |
|||
|
|||
<div class="typography-column"> |
|||
<Heading extraLarge black lh>Heading Extra Large Black Line Height</Heading> |
|||
|
|||
<Heading large black lh>Heading Large Black Line Height</Heading> |
|||
|
|||
<Heading medium black lh>Heading Medium Black Line Height</Heading> |
|||
|
|||
<Heading small black lh>Heading Small Black Line Height</Heading> |
|||
|
|||
<Heading extraSmall black lh>Heading Extra Small Black Line Height</Heading> |
|||
</div> |
|||
</div> |
|||
</View> |
|||
|
|||
<View name="Body"> |
|||
<div class="typography-container"> |
|||
<div class="typography-column"> |
|||
<Body extraLarge black>Body Extra Large Black</Body> |
|||
|
|||
<Body large black>Body Large Black</Body> |
|||
|
|||
<Body medium black>Body Medium Black</Body> |
|||
|
|||
<Body small black>Body Small Black</Body> |
|||
|
|||
<Body extraSmall black>Body Extra Small Black</Body> |
|||
</div> |
|||
|
|||
<div class="typography-column"> |
|||
<Body extraLarge lh black>Body Extra Large Black Line-height</Body> |
|||
|
|||
<Body large lh black>Body Large Black Line-height</Body> |
|||
|
|||
<Body medium lh black>Body Medium Black Line-height</Body> |
|||
|
|||
<Body small lh black>Body Small Black Line-height</Body> |
|||
|
|||
<Body extraSmall lh black>Body Extra Small Black Line-height</Body> |
|||
</div> |
|||
|
|||
<div class="typography-column"> |
|||
<Body extraLarge grey>Body Extra Large Grey</Body> |
|||
|
|||
<Body large grey>Body Large Grey</Body> |
|||
|
|||
<Body medium grey>Body Medium Grey</Body> |
|||
|
|||
<Body small grey>Body Small Grey</Body> |
|||
|
|||
<Body extraSmall grey>Body Extra Small Grey</Body> |
|||
</div> |
|||
|
|||
<div class="typography-column typography-column--grey"> |
|||
<Body extraLarge white>Body Extra Large White</Body> |
|||
|
|||
<Body large white>Body Large White</Body> |
|||
|
|||
<Body medium white>Body Medium White</Body> |
|||
|
|||
<Body small white>Body Small White</Body> |
|||
|
|||
<Body extraSmall white>Body Extra Small White</Body> |
|||
</div> |
|||
</div> |
|||
</View> |
|||
|
|||
<View name="Label"> |
|||
<div class="typography-container"> |
|||
<div class="label-wrapper"> |
|||
<Label extraLarge black forAttr={username}>Label Extra Large Black</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label large black forAttr={username}>Label Large Black</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label medium black forAttr={username}>Label Medium Black</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label small black forAttr={username}>Label Small Black</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label extraSmall black forAttr={username}>Label Extra Small Black</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="typography-container"> |
|||
<div class="label-wrapper"> |
|||
<Label extraLarge grey forAttr={username}>Label Extra Large Grey</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label large grey forAttr={username}>Label Large Grey</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label medium grey forAttr={username}>Label Medium Grey</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label small grey forAttr={username}>Label Small Grey</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label extraSmall grey forAttr={username}>Label Extra Small Grey</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="typography-container typography-column--grey"> |
|||
<div class="label-wrapper"> |
|||
<Label extraLarge white forAttr={username}>Label Extra Large White</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label large white forAttr={username}>Label Large White</Label> |
|||
|
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label medium white forAttr={username}>Label Medium White</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label small white forAttr={username}>Label Small White</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
|
|||
<div class="label-wrapper"> |
|||
<Label extraSmall white forAttr={username}>Label Extra Small White</Label> |
|||
<input type="text" /> |
|||
</div> |
|||
</div> |
|||
</View> |
|||
@ -1,24 +1,34 @@ |
|||
<script> |
|||
import Avatar from '../Avatar/Avatar.svelte' |
|||
import ClearButton from '../ClearButton/ClearButton.svelte' |
|||
export let icon = ""; |
|||
export let avatar = ""; |
|||
export let invalid = false; |
|||
export let disabled = false; |
|||
export let closable = false; |
|||
import Avatar from "../Avatar/Avatar.svelte" |
|||
import ClearButton from "../ClearButton/ClearButton.svelte" |
|||
export let icon = "" |
|||
export let avatar = "" |
|||
export let invalid = false |
|||
export let disabled = false |
|||
export let closable = false |
|||
</script> |
|||
|
|||
<div class:is-invalid={invalid} class:is-disabled={disabled} class="spectrum-Tags-item" role="listitem"> |
|||
<div |
|||
class:is-invalid={invalid} |
|||
class:is-disabled={disabled} |
|||
class="spectrum-Tags-item" |
|||
role="listitem" |
|||
> |
|||
{#if avatar} |
|||
<Avatar url={avatar} /> |
|||
{/if} |
|||
{#if icon} |
|||
<svg class="spectrum-Icon spectrum-Icon--sizeS" focusable="false" aria-hidden="true" aria-label="Tag"> |
|||
<svg |
|||
class="spectrum-Icon spectrum-Icon--sizeS" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
aria-label="Tag" |
|||
> |
|||
<use xlink:href="#spectrum-icon-24-{icon}" /> |
|||
</svg> |
|||
{/if} |
|||
<span class="spectrum-Tags-itemLabel"><slot /></span> |
|||
{#if closable} |
|||
<ClearButton on:click /> |
|||
<ClearButton on:click /> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
|
|||
@ -1,7 +1,7 @@ |
|||
<script> |
|||
import "@spectrum-css/tags/dist/index-vars.css" |
|||
import "@spectrum-css/tags/dist/index-vars.css" |
|||
</script> |
|||
|
|||
<div class="spectrum-Tags" role="list" aria-label="list"> |
|||
<slot /> |
|||
</div> |
|||
<slot /> |
|||
</div> |
|||
|
|||
@ -1,28 +1,40 @@ |
|||
<script> |
|||
export let selected = false; |
|||
export let open = false; |
|||
export let title; |
|||
export let icon; |
|||
export let selected = false |
|||
export let open = false |
|||
export let title |
|||
export let icon |
|||
</script> |
|||
|
|||
<li |
|||
class:is-selected={selected} class:is-open={open} class="spectrum-TreeView-item"> |
|||
<a on:click class="spectrum-TreeView-itemLink" href="#"> |
|||
{#if $$slots.default} |
|||
<svg class="spectrum-Icon spectrum-UIIcon-ChevronRight100 spectrum-TreeView-itemIndicator" focusable="false" aria-hidden="true"> |
|||
<use xlink:href="#spectrum-css-icon-Chevron100" /> |
|||
</svg> |
|||
{/if} |
|||
{#if icon} |
|||
<svg class="spectrum-TreeView-itemIcon spectrum-Icon spectrum-Icon--sizeM" focusable="false" aria-hidden="true" aria-label="Layers"> |
|||
<use xlink:href="#spectrum-icon-18-{icon}" /> |
|||
</svg> |
|||
{/if} |
|||
<span class="spectrum-TreeView-itemLabel">{title}</span> |
|||
</a> |
|||
{#if $$slots.default} |
|||
<ul class="spectrum-TreeView"> |
|||
<slot /> |
|||
</ul> |
|||
{/if} |
|||
</li> |
|||
class:is-selected={selected} |
|||
class:is-open={open} |
|||
class="spectrum-TreeView-item" |
|||
> |
|||
<a on:click class="spectrum-TreeView-itemLink" href="#"> |
|||
{#if $$slots.default} |
|||
<svg |
|||
class="spectrum-Icon spectrum-UIIcon-ChevronRight100 spectrum-TreeView-itemIndicator" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-css-icon-Chevron100" /> |
|||
</svg> |
|||
{/if} |
|||
{#if icon} |
|||
<svg |
|||
class="spectrum-TreeView-itemIcon spectrum-Icon spectrum-Icon--sizeM" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
aria-label="Layers" |
|||
> |
|||
<use xlink:href="#spectrum-icon-18-{icon}" /> |
|||
</svg> |
|||
{/if} |
|||
<span class="spectrum-TreeView-itemLabel">{title}</span> |
|||
</a> |
|||
{#if $$slots.default} |
|||
<ul class="spectrum-TreeView"> |
|||
<slot /> |
|||
</ul> |
|||
{/if} |
|||
</li> |
|||
|
|||
@ -1,11 +1,16 @@ |
|||
<script> |
|||
import "@spectrum-css/treeview/dist/index-vars.css" |
|||
import "@spectrum-css/treeview/dist/index-vars.css" |
|||
|
|||
export let quiet = false |
|||
export let standalone = true |
|||
export let width = '250px'; |
|||
export let quiet = false |
|||
export let standalone = true |
|||
export let width = "250px" |
|||
</script> |
|||
|
|||
<ul class:spectrum-TreeView--standalone={standalone} class:spectrum-TreeView--quiet={quiet} class="spectrum-TreeView" style="width: {width}"> |
|||
<slot /> |
|||
</ul> |
|||
<ul |
|||
class:spectrum-TreeView--standalone={standalone} |
|||
class:spectrum-TreeView--quiet={quiet} |
|||
class="spectrum-TreeView" |
|||
style="width: {width}" |
|||
> |
|||
<slot /> |
|||
</ul> |
|||
|
|||
@ -1,29 +1,22 @@ |
|||
<script> |
|||
import "@spectrum-css/typography/dist/index-vars.css" |
|||
import "@spectrum-css/typography/dist/index-vars.css" |
|||
|
|||
// Sizes |
|||
export let xxxl = false; |
|||
export let xxl = false; |
|||
export let xl = false; |
|||
export let l = false; |
|||
export let m = false; |
|||
export let s = false; |
|||
export let xs = false; |
|||
export let xxs = false; |
|||
|
|||
export let serif = false; |
|||
$: useDefault = ![xxxl, xxl, xl, l, m, s, xs, xxs].includes(true) |
|||
export let size = "M" |
|||
export let serif = false |
|||
export let noPadding = false |
|||
</script> |
|||
|
|||
<p class="spectrum-Body" |
|||
class:spectrum-Body--serif={serif} |
|||
class:spectrum-Body--sizeXXXL={xxxl} |
|||
class:spectrum-Body--sizeXXL={xxl} |
|||
class:spectrum-Body--sizeXL={xl} |
|||
class:spectrum-Body--sizeL={l} |
|||
class:spectrum-Body--sizeM={m || useDefault} |
|||
class:spectrum-Body--sizeS={s} |
|||
class:spectrum-Body--sizeXS={xs} |
|||
class:spectrum-Body--sizeXXS={xxs}> |
|||
<slot /> |
|||
</p> |
|||
<p |
|||
class:noPadding |
|||
class="spectrum-Body spectrum-Body--size{size}" |
|||
class:spectrum-Body--serif={serif} |
|||
> |
|||
<slot /> |
|||
</p> |
|||
|
|||
<style> |
|||
.noPadding { |
|||
padding: 0; |
|||
margin: 0; |
|||
} |
|||
</style> |
|||
|
|||
@ -1,20 +1,10 @@ |
|||
<script> |
|||
import "@spectrum-css/typography/dist/index-vars.css" |
|||
import "@spectrum-css/typography/dist/index-vars.css" |
|||
|
|||
// Sizes |
|||
export let xl = false; |
|||
export let l = false; |
|||
export let m = false; |
|||
export let s = false; |
|||
export let xs = false; |
|||
$: useDefault = ![xl, l, m, s, xs].includes(true) |
|||
// Sizes |
|||
export let size = "M" |
|||
</script> |
|||
|
|||
<code class="spectrum-Code" |
|||
class:spectrum-Code--sizeXL={xl} |
|||
class:spectrum-Code--sizeL={l} |
|||
class:spectrum-Code--sizeM={m || useDefault} |
|||
class:spectrum-Code--sizeS={s} |
|||
class:spectrum-Code--sizeXS={xs}> |
|||
<slot /> |
|||
</code> |
|||
<code class="spectrum-Code spectrum-Code--size{size}"> |
|||
<slot /> |
|||
</code> |
|||
|
|||
@ -1,21 +1,15 @@ |
|||
<script> |
|||
import "@spectrum-css/typography/dist/index-vars.css" |
|||
import "@spectrum-css/typography/dist/index-vars.css" |
|||
|
|||
// Sizes |
|||
export let xl = false; |
|||
export let l = false; |
|||
export let m = false; |
|||
export let s = false; |
|||
// Sizes |
|||
export let size = "M" |
|||
|
|||
export let serif = false; |
|||
$: useDefault = ![xl, l, m, s].includes(true) |
|||
export let serif = false |
|||
</script> |
|||
|
|||
<p class="spectrum-Detail" |
|||
class:spectrum-Detail--serif={serif} |
|||
class:spectrum-Detail--sizeXL={xl} |
|||
class:spectrum-Detail--sizeL={l} |
|||
class:spectrum-Detail--sizeM={m || useDefault} |
|||
class:spectrum-Detail--sizeS={s}> |
|||
<slot /> |
|||
</p> |
|||
<p |
|||
class="spectrum-Detail spectrum-Detail--size{size}" |
|||
class:spectrum-Detail--serif={serif} |
|||
> |
|||
<slot /> |
|||
</p> |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue