diff --git a/.vscode/settings.json b/.vscode/settings.json
index 192798b..ac20139 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,7 @@
{
"svelte.plugin.svelte.compilerWarnings": {
"a11y-click-events-have-key-events": "ignore",
- "a11y-no-static-element-interactions": "ignore"
+ "a11y-no-static-element-interactions": "ignore",
+ "a11y-no-noninteractive-element-interactions": "ignore"
}
}
diff --git a/README.md b/README.md
index 5737cfe..8a8a2b9 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,18 @@ The `Setup` mixin can also accept the following options:
|-----------|---------------|---------|
| `includeResets` | `true` | Include reset styles. Set to `false` if you want to use your own CSS resets. |
| `includeHelperClasses` | `true` | Adds global helper classes for CSS. All global helper classes are defined [here](https://github.com/Frontendland/webcoreui/tree/main/src/scss/global). |
+| `includeElementStyles` | `true` | Adds styles for native HTML elements, such as `code`, `pre`, or `ul`.
+
+Default component styles can also be changed by overriding the following CSS variables:
+
+```css
+:root {
+ --w-avatar-border: #000;
+ --w-rating-color: #FFF;
+ --w-rating-empty-color: #BBB;
+ --w-rating-size: 18px;
+}
+```
### Using Components
@@ -133,8 +145,13 @@ import { Accordion } from 'webcoreui/react'
- [Accordion](https://github.com/Frontendland/webcoreui/tree/main/src/components/Accordion)
- [Alert](https://github.com/Frontendland/webcoreui/tree/main/src/components/Alert)
-- [ConditionalWrapper](https://github.com/Frontendland/webcoreui/tree/main/src/components/ConditionalWrapper)
+- [Avatar](https://github.com/Frontendland/webcoreui/tree/main/src/components/Avatar)
- [Badge](https://github.com/Frontendland/webcoreui/tree/main/src/components/Badge)
- [Button](https://github.com/Frontendland/webcoreui/tree/main/src/components/Button)
- [Card](https://github.com/Frontendland/webcoreui/tree/main/src/components/Card)
+- [Checkbox](https://github.com/Frontendland/webcoreui/tree/main/src/components/Checkbox)
+- [ConditionalWrapper](https://github.com/Frontendland/webcoreui/tree/main/src/components/ConditionalWrapper)
- [Icon](https://github.com/Frontendland/webcoreui/tree/main/src/components/Icon)
+- [Radio](https://github.com/Frontendland/webcoreui/tree/main/src/components/Radio)
+- [Rating](https://github.com/Frontendland/webcoreui/tree/main/src/components/Rating)
+- [Switch](https://github.com/Frontendland/webcoreui/tree/main/src/components/Switch)
diff --git a/package.json b/package.json
index a51a021..9ab943e 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,13 @@
{
"name": "webcoreui",
"type": "module",
- "version": "0.0.6",
+ "version": "0.0.8",
"scripts": {
"dev": "astro dev",
"build": "astro check && astro build",
"build:package": "node scripts/build.js",
- "test": "echo \"Error: no test specified\""
+ "test": "echo \"Error: no test specified\"",
+ "create-component": "node scripts/createComponent.js"
},
"dependencies": {
"@astrojs/check": "0.7.0",
diff --git a/public/img/avatar0.png b/public/img/avatar0.png
new file mode 100644
index 0000000..e3c517a
Binary files /dev/null and b/public/img/avatar0.png differ
diff --git a/public/img/avatar1.png b/public/img/avatar1.png
new file mode 100644
index 0000000..80c4df8
Binary files /dev/null and b/public/img/avatar1.png differ
diff --git a/public/img/avatar2.png b/public/img/avatar2.png
new file mode 100644
index 0000000..7112c5c
Binary files /dev/null and b/public/img/avatar2.png differ
diff --git a/public/img/avatar3.png b/public/img/avatar3.png
new file mode 100644
index 0000000..bd485b3
Binary files /dev/null and b/public/img/avatar3.png differ
diff --git a/public/img/avatar4.png b/public/img/avatar4.png
new file mode 100644
index 0000000..d282787
Binary files /dev/null and b/public/img/avatar4.png differ
diff --git a/scripts/createComponent.js b/scripts/createComponent.js
new file mode 100644
index 0000000..0216063
--- /dev/null
+++ b/scripts/createComponent.js
@@ -0,0 +1,147 @@
+import fs from 'fs'
+
+const componentFlag = process.argv[2]
+
+if (!componentFlag) {
+ console.log("⚠️ Component name is missing. Use npm run create-component MyComponent.")
+ process.exit()
+}
+
+const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1)
+
+const component = capitalize(componentFlag)
+const lowerCaseComponent = component.toLowerCase()
+const rootPath = 'src/components'
+
+if (fs.existsSync(`${rootPath}/${component}`)) {
+ console.log(`⚠️ Component ${component} already exists. Please choose another name.`)
+ process.exit()
+}
+
+const format = template => template.trim().replace(new RegExp('^[ \\t]{8}', 'gm'), '')
+
+const templates = {
+ astro: `
+ ---
+ import type { ${component}Props } from './${lowerCaseComponent}'
+ import './${lowerCaseComponent}.scss'
+
+ interface Props extends ${component}Props {}
+
+ const {
+ className
+ } = Astro.props
+
+ const classes = [
+ 'w-${lowerCaseComponent}',
+ className
+ ]
+ ---
+ `,
+ svelte: `
+
+ `,
+ react: `
+ import React from 'react'
+ import type { ${component}Props } from './${lowerCaseComponent}'
+
+ import './${lowerCaseComponent}.scss'
+
+ const ${component} = ({
+ className
+ }: ${component}Props) => {
+ const classes = [
+ 'w-${lowerCaseComponent}',
+ className
+ ].filter(Boolean).join(' ')
+
+ return
${component}
+ }
+
+ export default ${component}
+ `,
+ types: `
+ export type ${component}Props = {
+ className?: string
+ }
+ `,
+ styles: `
+ @import '../../scss/config.scss';
+
+ .w-${lowerCaseComponent} {
+
+ }
+ `,
+ page: `
+ ---
+ import Layout from '@static/Layout.astro'
+ import ComponentWrapper from '@static/ComponentWrapper.astro'
+
+ import Astro${component} from '@components/${component}/${component}.astro'
+ import Svelte${component} from '@components/${component}/${component}.svelte'
+ import React${component} from '@components/${component}/${component}.tsx'
+
+ const sections = [
+ {
+ title: 'Astro ${lowerCaseComponent}s',
+ component: Astro${component}
+ },
+ {
+ title: 'Svelte ${lowerCaseComponent}s',
+ component: Svelte${component}
+ },
+ {
+ title: 'React ${lowerCaseComponent}s',
+ component: React${component}
+ }
+ ]
+ ---
+
+
+ ${component}
+
+
+ {sections.map(section => (
+ {section.title}
+
+
+
+
+
+ ))}
+
+ `
+}
+
+fs.mkdirSync(`${rootPath}/${component}`)
+
+fs.writeFileSync(`${rootPath}/${component}/${component}.astro`, format(templates.astro))
+fs.writeFileSync(`${rootPath}/${component}/${component}.svelte`, format(templates.svelte))
+fs.writeFileSync(`${rootPath}/${component}/${component}.tsx`, format(templates.react))
+fs.writeFileSync(`${rootPath}/${component}/${lowerCaseComponent}.ts`, format(templates.types))
+fs.writeFileSync(`${rootPath}/${component}/${lowerCaseComponent}.scss`, format(templates.styles))
+fs.writeFileSync(`src/pages/${lowerCaseComponent}.astro`, format(templates.page))
+
+console.log(`✅ Component ${component} created at ${rootPath}/${component}.`)
diff --git a/src/components/Accordion/Accordion.astro b/src/components/Accordion/Accordion.astro
index 87820ad..76d1ca8 100644
--- a/src/components/Accordion/Accordion.astro
+++ b/src/components/Accordion/Accordion.astro
@@ -1,6 +1,7 @@
---
import type { AccordionProps } from './accordion'
import ArrowDown from '../../icons/arrow-down.svg?raw'
+import './accordion.scss'
interface Props extends AccordionProps {}
@@ -38,7 +39,3 @@ const {
})
})
-
-
diff --git a/src/components/Accordion/Accordion.svelte b/src/components/Accordion/Accordion.svelte
index 98fa12e..8564db5 100644
--- a/src/components/Accordion/Accordion.svelte
+++ b/src/components/Accordion/Accordion.svelte
@@ -1,6 +1,7 @@
+
+{#if Array.isArray(img)}
+
+ {#each img as img, index}
+
+ {/each}
+
+{:else}
+
+{/if}
diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx
new file mode 100644
index 0000000..74e5ed9
--- /dev/null
+++ b/src/components/Avatar/Avatar.tsx
@@ -0,0 +1,63 @@
+import React from 'react'
+import type { AvatarProps } from './avatar'
+import './avatar.scss'
+
+const Avatar = ({
+ img,
+ alt = 'Avatar',
+ size = 40,
+ lazy = true,
+ borderColor,
+ borderless,
+ reverse,
+ className,
+}: AvatarProps) => {
+ const classes = [
+ 'w-avatar',
+ borderless && 'borderless',
+ className
+ ].filter(Boolean).join(' ')
+
+ const groupStyles = [
+ 'w-avatar-group',
+ reverse && 'reverse'
+ ].filter(Boolean).join(' ')
+
+ const borderColorStyle = borderColor
+ ? { '--w-avatar-border': borderColor } as React.CSSProperties
+ : undefined
+
+ return Array.isArray(img) ? (
+
+ {img.map((img, index) => (
+
+ ))}
+
+ ) : (
+
+ )
+
+}
+
+export default Avatar
diff --git a/src/components/Avatar/avatar.scss b/src/components/Avatar/avatar.scss
new file mode 100644
index 0000000..934f33c
--- /dev/null
+++ b/src/components/Avatar/avatar.scss
@@ -0,0 +1,35 @@
+@import '../../scss/config.scss';
+
+.w-avatar {
+ border-radius: 100%;
+
+ &:not(.borderless) {
+ border: 3px solid var(--w-avatar-border);
+ }
+}
+
+.w-avatar-group {
+ display: inline-flex;
+ align-items: center;
+
+ &.reverse {
+ flex-direction: row-reverse;
+
+ img {
+ z-index: 1;
+ }
+
+ img:not(:first-child) {
+ margin-right: -10px;
+ margin-left: 0;
+ }
+ }
+
+ img:not(:first-child) {
+ margin-left: -10px;
+ }
+
+ img {
+ z-index: var(--w-avatar-index);
+ }
+}
diff --git a/src/components/Avatar/avatar.ts b/src/components/Avatar/avatar.ts
new file mode 100644
index 0000000..6864d2b
--- /dev/null
+++ b/src/components/Avatar/avatar.ts
@@ -0,0 +1,10 @@
+export type AvatarProps = {
+ img: string | string[]
+ alt?: string | string[]
+ size?: number | number[]
+ lazy?: boolean
+ borderColor?: string
+ borderless?: boolean
+ reverse?: boolean
+ className?: string
+}
diff --git a/src/components/Badge/Badge.astro b/src/components/Badge/Badge.astro
index 8069fa1..433affb 100644
--- a/src/components/Badge/Badge.astro
+++ b/src/components/Badge/Badge.astro
@@ -1,5 +1,6 @@
---
import type { BadgeProps } from './badge'
+import './badge.scss'
interface Props extends BadgeProps {
interactive?: boolean
@@ -20,8 +21,3 @@ const classes = [
-
-
-
diff --git a/src/components/Badge/Badge.svelte b/src/components/Badge/Badge.svelte
index e8ac191..d99a169 100644
--- a/src/components/Badge/Badge.svelte
+++ b/src/components/Badge/Badge.svelte
@@ -1,5 +1,6 @@
+
+
+
+
+
+ {@html check}
+
+ {#if label}
+
+ {@html label}
+
+ {/if}
+
+ {#if label && subText}
+ {@html subText}
+ {/if}
+
diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx
new file mode 100644
index 0000000..55d80d9
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.tsx
@@ -0,0 +1,69 @@
+import React from 'react'
+import type { CheckboxProps } from './checkbox'
+import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
+
+import check from '../../icons/check.svg?raw'
+
+import './checkbox.scss'
+
+type ReactCheckboxProps = {
+ onClick?: () => any
+} & CheckboxProps
+
+const Checkbox = ({
+ checked,
+ label,
+ subText,
+ disabled,
+ boxed,
+ color,
+ onClick
+}: ReactCheckboxProps) => {
+ const classes = [
+ 'w-checkbox',
+ boxed && 'boxed',
+ label && subText && 'col'
+ ].filter(Boolean).join(' ')
+
+ const style = color
+ ? { '--w-checkbox-color': color } as React.CSSProperties
+ : undefined
+
+ return (
+
+ (
+
+ {children}
+
+ )}
+ >
+
+
+ {label && (
+
+ )}
+
+ {label && subText && (
+
+ )}
+
+ )
+}
+
+export default Checkbox
diff --git a/src/components/Checkbox/checkbox.scss b/src/components/Checkbox/checkbox.scss
new file mode 100644
index 0000000..7072536
--- /dev/null
+++ b/src/components/Checkbox/checkbox.scss
@@ -0,0 +1,85 @@
+@import '../../scss/config.scss';
+
+.w-checkbox {
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 16px;
+
+ &.col {
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ gap: 5px;
+
+ .checkbox-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+ }
+
+ &.boxed {
+ border: 1px solid #252525;
+ border-radius: 5px;
+ padding: 20px;
+ }
+
+ input {
+ display: none;
+
+ &:checked + span {
+ background-color: var(--w-checkbox-color);
+
+ svg {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ display: block;
+ color: #252525;
+ width: 10px;
+ height: 10px;
+ }
+ }
+
+ &:disabled + span {
+ background-color: #333;
+ border-color: #333;
+ cursor: no-drop;
+ }
+ }
+
+ a {
+ text-decoration: underline;
+ }
+
+ .check {
+ display: inline-block;
+ width: 15px;
+ height: 15px;
+ border: 1px solid var(--w-checkbox-color);
+ border-radius: 2px;
+ position: relative;
+
+ svg {
+ display: none;
+ }
+ }
+
+ .sub-text {
+ margin-left: 25px;
+ font-size: 14px;
+ color: #BBB;
+
+ a {
+ @include Transition(color);
+ color: #BBB;
+
+ &:hover {
+ color: #FFF;
+ }
+ }
+ }
+}
diff --git a/src/components/Checkbox/checkbox.ts b/src/components/Checkbox/checkbox.ts
new file mode 100644
index 0000000..51b073f
--- /dev/null
+++ b/src/components/Checkbox/checkbox.ts
@@ -0,0 +1,8 @@
+export type CheckboxProps = {
+ checked?: boolean
+ label?: string
+ subText?: string
+ disabled?: boolean
+ boxed?: boolean
+ color?: string
+}
diff --git a/src/components/Radio/Radio.astro b/src/components/Radio/Radio.astro
new file mode 100644
index 0000000..1be6d93
--- /dev/null
+++ b/src/components/Radio/Radio.astro
@@ -0,0 +1,57 @@
+---
+import type { RadioProps } from './radio'
+import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.astro'
+
+import './radio.scss'
+
+interface Props extends RadioProps {}
+
+const {
+ name,
+ items,
+ color,
+ inline,
+ className
+} = Astro.props
+
+const classes = [
+ 'w-radio',
+ inline && 'inline',
+ className
+]
+
+const style = color
+ ? `--w-radio-color: ${color};`
+ : null
+---
+
+
+ {items.map(item => (
+
+
+
+ children
+
+
+
+
+
+
+
+ {item.subText && (
+
+
+
+ )}
+
+ ))}
+
diff --git a/src/components/Radio/Radio.svelte b/src/components/Radio/Radio.svelte
new file mode 100644
index 0000000..a592244
--- /dev/null
+++ b/src/components/Radio/Radio.svelte
@@ -0,0 +1,56 @@
+
+
+
+ {#each items as item}
+
+
+
+
+
+ {@html item.label}
+
+
+ {#if item.subText}
+
+ {@html item.subText}
+
+ {/if}
+
+ {/each}
+
diff --git a/src/components/Radio/Radio.tsx b/src/components/Radio/Radio.tsx
new file mode 100644
index 0000000..bdf88e0
--- /dev/null
+++ b/src/components/Radio/Radio.tsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import type { RadioProps } from './radio'
+
+import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
+
+import './radio.scss'
+
+type ReactRadioProps = {
+ onChange?: () => any
+} & RadioProps
+
+const Radio = ({
+ name,
+ items,
+ color,
+ inline,
+ className,
+ onChange
+}: ReactRadioProps) => {
+ const classes = [
+ 'w-radio',
+ inline && 'inline',
+ className
+ ].filter(Boolean).join(' ')
+
+ const style = color
+ ? { '--w-radio-color': color } as React.CSSProperties
+ : undefined
+
+ return (
+
+ {items.map((item, index) => (
+
+ (
+
+ {children}
+
+ )}
+ >
+
+
+
+
+ {item.subText && (
+
+ )}
+
+ ))}
+
+ )
+}
+
+export default Radio
+
diff --git a/src/components/Radio/radio.scss b/src/components/Radio/radio.scss
new file mode 100644
index 0000000..682f56d
--- /dev/null
+++ b/src/components/Radio/radio.scss
@@ -0,0 +1,92 @@
+@import '../../scss/config.scss';
+
+.w-radio {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+
+ &.inline {
+ flex-direction: row;
+ }
+
+ label {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ cursor: pointer;
+ font-size: 16px;
+
+ &.disabled {
+ cursor: no-drop;
+
+ input + span::after {
+ background: #BBB;
+ }
+ }
+
+ &.col {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ }
+
+ input {
+ display: none;
+
+ + span::after {
+ @include Transition(transform);
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) scale(0);
+ width: 8px;
+ height: 8px;
+ border-radius: 100%;
+ background: var(--w-radio-color);
+ }
+
+ &:checked + span::after {
+ transform: translate(-50%, -50%) scale(1);
+ }
+
+ &:disabled + span {
+ background-color: #333;
+ border-color: #333;
+ }
+ }
+
+ a {
+ text-decoration: underline;
+ }
+
+ .radio-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+
+ .radio {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ border-radius: 100%;
+ border: 1px solid var(--w-radio-color);
+ position: relative;
+ }
+
+ .sub-text {
+ margin-left: 25px;
+ font-size: 14px;
+ color: #BBB;
+
+ a {
+ @include Transition(color);
+ color: #BBB;
+
+ &:hover {
+ color: #FFF;
+ }
+ }
+ }
+}
diff --git a/src/components/Radio/radio.ts b/src/components/Radio/radio.ts
new file mode 100644
index 0000000..2e3d764
--- /dev/null
+++ b/src/components/Radio/radio.ts
@@ -0,0 +1,13 @@
+export type RadioProps = {
+ items: {
+ label: string
+ value: string
+ subText?: string
+ selected?: boolean
+ disabled?: boolean
+ }[]
+ name: string
+ color?: string
+ inline?: boolean
+ className?: string
+}
diff --git a/src/components/Rating/Rating.astro b/src/components/Rating/Rating.astro
new file mode 100644
index 0000000..0f8f0bf
--- /dev/null
+++ b/src/components/Rating/Rating.astro
@@ -0,0 +1,66 @@
+---
+import type { RatingProps } from './rating'
+import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.astro'
+
+import './rating.scss'
+
+interface Props extends RatingProps {}
+
+const {
+ score,
+ total = 5,
+ showText,
+ text = '{0} out of {1}',
+ showEmpty = true,
+ outline = true,
+ reviewCount,
+ reviewText = '{0} reviews',
+ reviewLink,
+ reviewTarget,
+ color,
+ emptyColor,
+ size,
+ className
+} = Astro.props
+
+const classes = [
+ 'w-rating',
+ outline && 'outline',
+ className
+]
+
+const styles = [
+ color && `--w-rating-color: ${color};`,
+ size && `--w-rating-size: ${size}px;`,
+ emptyColor && `--w-rating-empty-color: ${emptyColor};`
+].filter(Boolean).join(' ')
+
+const translatedText = text
+ .replace('{0}', `${score}`)
+ .replace('{1}', `${total}`)
+
+const translatedReviewText = reviewText.replace('{0}', `${reviewCount}`)
+---
+
+
+ {Array(score).fill('★').join('')}
+ {showEmpty && (
+
+ {Array(total - score).fill('★').join('')}
+
+ )}
+ {showText && (
+
+ {translatedText}
+
+ )}
+ {reviewCount && '•'}
+ {reviewCount && (
+
+
+ children
+
+ {translatedReviewText}
+
+ )}
+
diff --git a/src/components/Rating/Rating.svelte b/src/components/Rating/Rating.svelte
new file mode 100644
index 0000000..646a4e9
--- /dev/null
+++ b/src/components/Rating/Rating.svelte
@@ -0,0 +1,70 @@
+
+
+
+
+ {Array(score).fill('★').join('')}
+ {#if showEmpty}
+
+ {Array((total || 5) - score).fill('★').join('')}
+
+ {/if}
+
+ {#if showText}
+
+ {translatedText}
+
+ {/if}
+
+ {#if reviewCount}
+ {'•'}
+
+ {translatedReviewText}
+
+ {/if}
+
diff --git a/src/components/Rating/Rating.tsx b/src/components/Rating/Rating.tsx
new file mode 100644
index 0000000..ae4c14a
--- /dev/null
+++ b/src/components/Rating/Rating.tsx
@@ -0,0 +1,67 @@
+import React from 'react'
+import type { RatingProps } from './rating'
+import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
+import './rating.scss'
+
+const Rating = ({
+ score,
+ total = 5,
+ showText,
+ text = '{0} out of {1}',
+ showEmpty = true,
+ outline = true,
+ reviewCount,
+ reviewText = '{0} reviews',
+ reviewLink,
+ reviewTarget,
+ color,
+ emptyColor,
+ size,
+ className
+}: RatingProps) => {
+ const classes = [
+ 'w-rating',
+ outline && 'outline',
+ className
+ ].filter(Boolean).join(' ')
+
+ const styles = {
+ ...(color && { '--w-rating-color': color }),
+ ...(size && { '--w-rating-size': `${size}px` }),
+ ...(emptyColor && { '--w-rating-empty-color': emptyColor })
+ } as React.CSSProperties
+
+ const translatedText = text
+ .replace('{0}', `${score}`)
+ .replace('{1}', `${total}`)
+
+ const translatedReviewText = reviewText.replace('{0}', `${reviewCount}`)
+
+ return (
+
+ {Array(score).fill('★').join('')}
+ {showEmpty && (
+
+ {Array(total - score).fill('★').join('')}
+
+ )}
+ {showText && (
+
+ {translatedText}
+
+ )}
+ {reviewCount && '•'}
+ {reviewCount && (
+ (
+
+ {children}
+
+ )}>
+ {translatedReviewText}
+
+ )}
+
+ )
+}
+
+export default Rating
diff --git a/src/components/Rating/rating.scss b/src/components/Rating/rating.scss
new file mode 100644
index 0000000..da2e80c
--- /dev/null
+++ b/src/components/Rating/rating.scss
@@ -0,0 +1,37 @@
+@import '../../scss/config.scss';
+
+.w-rating {
+ display: inline-flex;
+ align-items: center;
+ color: var(--w-rating-color);
+ font-size: var(--w-rating-size);
+
+ &.outline .empty {
+ transform: scale(.88);
+ color: black;
+ text-shadow: -1px 0 var(--w-rating-color), 0 1px var(--w-rating-color), 1px 0 var(--w-rating-color), 0 -1px var(--w-rating-color);
+ letter-spacing: 2px;
+ }
+
+ .empty {
+ color: var(--w-rating-empty-color);
+
+ &.ten-star {
+ margin-left: -3px;
+ }
+ }
+
+ a {
+ text-decoration: underline;
+ }
+
+ .text {
+ font-size: 16px;
+ color: #BBB;
+ margin-left: 5px;
+
+ &.m {
+ margin-right: 5px;
+ }
+ }
+}
diff --git a/src/components/Rating/rating.ts b/src/components/Rating/rating.ts
new file mode 100644
index 0000000..b5af3d3
--- /dev/null
+++ b/src/components/Rating/rating.ts
@@ -0,0 +1,16 @@
+export type RatingProps = {
+ score: number
+ total?: number
+ showText?: boolean
+ text?: string
+ showEmpty?: boolean
+ outline?: boolean
+ reviewCount?: number
+ reviewText?: string
+ reviewLink?: string
+ reviewTarget?: string
+ color?: string
+ emptyColor?: string
+ size?: number
+ className?: string
+}
diff --git a/src/components/Switch/Switch.astro b/src/components/Switch/Switch.astro
new file mode 100644
index 0000000..9a8e153
--- /dev/null
+++ b/src/components/Switch/Switch.astro
@@ -0,0 +1,38 @@
+---
+import type { SwitchProps } from './switch'
+import './switch.scss'
+
+interface Props extends SwitchProps {}
+
+const {
+ label,
+ toggled,
+ offColor,
+ onColor,
+ reverse,
+ small,
+ square,
+ disabled,
+ className
+} = Astro.props
+
+const classes = [
+ 'w-switch',
+ reverse && 'reverse',
+ small && 'small',
+ square && 'square',
+ disabled && 'disabled',
+ className
+]
+
+const styles = [
+ offColor && `--w-switch-off-color: ${offColor};`,
+ onColor && `--w-switch-on-color: ${onColor};`
+].filter(Boolean).join(' ')
+---
+
+
+
+
+ {label && {label} }
+
diff --git a/src/components/Switch/Switch.svelte b/src/components/Switch/Switch.svelte
new file mode 100644
index 0000000..de268bd
--- /dev/null
+++ b/src/components/Switch/Switch.svelte
@@ -0,0 +1,42 @@
+
+
+
+
+
+ {#if label}
+ {label}
+ {/if}
+
diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx
new file mode 100644
index 0000000..791d591
--- /dev/null
+++ b/src/components/Switch/Switch.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import type { SwitchProps } from './switch'
+import './switch.scss'
+
+type ReactSwitchProps = {
+ onClick?: () => any
+} & SwitchProps
+
+const Switch = ({
+ label,
+ toggled,
+ offColor,
+ onColor,
+ reverse,
+ small,
+ square,
+ disabled,
+ className,
+ onClick
+}: ReactSwitchProps) => {
+ const classes = [
+ 'w-switch',
+ reverse && 'reverse',
+ small && 'small',
+ square && 'square',
+ disabled && 'disabled',
+ className
+ ].filter(Boolean).join(' ')
+
+ const styles = {
+ ...(offColor && { '--w-switch-off-color': offColor }),
+ ...(onColor && { '--w-switch-on-color': onColor })
+ } as React.CSSProperties
+
+ return (
+
+
+
+ {label && {label} }
+
+ )
+
+}
+
+export default Switch
diff --git a/src/components/Switch/switch.scss b/src/components/Switch/switch.scss
new file mode 100644
index 0000000..29249d2
--- /dev/null
+++ b/src/components/Switch/switch.scss
@@ -0,0 +1,84 @@
+@import '../../scss/config.scss';
+
+.w-switch {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+
+ &.reverse {
+ flex-direction: row-reverse;
+ }
+
+ &.disabled .toggle {
+ cursor: no-drop;
+ background: #333;
+
+ &::before {
+ background: #252525;
+ }
+ }
+
+ &.small {
+ input:checked + span::before {
+ transform: translateX(20px);
+ }
+
+ .toggle {
+ width: 40px;
+ height: 20px;
+
+ &::before {
+ height: 14px;
+ width: 14px;
+ }
+ }
+
+ .label {
+ font-size: 14px;
+ }
+ }
+
+ &.square .toggle {
+ border-radius: 5px;
+
+ &::before {
+ border-radius: 5px;
+ }
+ }
+
+ input {
+ display: none;
+
+ &:checked + span {
+ background-color: var(--w-switch-on-color);
+
+ &::before {
+ transform: translateX(30px);
+ }
+ }
+ }
+
+ .toggle {
+ @include Transition(background);
+ position: relative;
+ width: 60px;
+ height: 30px;
+ background: var(--w-switch-off-color);
+ border-radius: 30px;
+
+ &::before {
+ content: "";
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ left: 3px;
+ bottom: 3px;
+ background: #000;
+ border-radius: 50%;
+ transition: 0.3s;
+ }
+ }
+}
+
+
diff --git a/src/components/Switch/switch.ts b/src/components/Switch/switch.ts
new file mode 100644
index 0000000..9706b4e
--- /dev/null
+++ b/src/components/Switch/switch.ts
@@ -0,0 +1,11 @@
+export type SwitchProps = {
+ label?: string
+ toggled?: boolean
+ offColor?: string
+ onColor?: string
+ reverse?: boolean
+ small?: boolean
+ square?: boolean
+ disabled?: boolean
+ className?: string
+}
diff --git a/src/icons/check.svg b/src/icons/check.svg
index f5a1876..5442e5a 100644
--- a/src/icons/check.svg
+++ b/src/icons/check.svg
@@ -1,4 +1,3 @@
-
-
+
diff --git a/src/icons/circle-check.svg b/src/icons/circle-check.svg
new file mode 100644
index 0000000..f5a1876
--- /dev/null
+++ b/src/icons/circle-check.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/pages/accordion.astro b/src/pages/accordion.astro
index a0df97f..43c0c9c 100644
--- a/src/pages/accordion.astro
+++ b/src/pages/accordion.astro
@@ -3,7 +3,7 @@ import Layout from '@static/Layout.astro'
import ComponentWrapper from '@static/ComponentWrapper.astro'
import AstroAccordion from '@components/Accordion/Accordion.astro'
-import SvelteAccorion from '@components/Accordion/Accordion.svelte'
+import SvelteAccordion from '@components/Accordion/Accordion.svelte'
import ReactAccordion from '@components/Accordion/Accordion.tsx'
const accordionItems = [{
@@ -27,11 +27,11 @@ const accordionItems = [{
-
+
-
+
diff --git a/src/pages/avatar.astro b/src/pages/avatar.astro
new file mode 100644
index 0000000..8272c67
--- /dev/null
+++ b/src/pages/avatar.astro
@@ -0,0 +1,117 @@
+---
+import Layout from '@static/Layout.astro'
+import ComponentWrapper from '@static/ComponentWrapper.astro'
+
+import AstroAvatar from '@components/Avatar/Avatar.astro'
+import SvelteAvatar from '@components/Avatar/Avatar.svelte'
+import ReactAvatar from '@components/Avatar/Avatar.tsx'
+
+const sections = [
+ {
+ title: 'Astro avatars',
+ component: AstroAvatar
+ },
+ {
+ title: 'Svelte avatars',
+ component: SvelteAvatar
+ },
+ {
+ title: 'React avatars',
+ component: ReactAvatar
+ }
+]
+
+const group = [
+ "/img/avatar0.png",
+ "/img/avatar1.png",
+ "/img/avatar2.png",
+ "/img/avatar3.png",
+ "/img/avatar4.png"
+]
+---
+
+
+ Avatar
+
+
+
+
+
+
+
+ Badge in Svelte
+
+
+
+
+
+ Badge in React
+
+
+
+
+ {sections.map(section => (
+ {section.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
diff --git a/src/pages/card.astro b/src/pages/card.astro
index 1f25f03..82ba0de 100644
--- a/src/pages/card.astro
+++ b/src/pages/card.astro
@@ -42,7 +42,7 @@ const sections = [
{section.title}
- Card with title
+ Compact card with title
diff --git a/src/pages/checkbox.astro b/src/pages/checkbox.astro
new file mode 100644
index 0000000..552eaa0
--- /dev/null
+++ b/src/pages/checkbox.astro
@@ -0,0 +1,88 @@
+---
+import Layout from '@static/Layout.astro'
+import ComponentWrapper from '@static/ComponentWrapper.astro'
+
+import AstroCheckbox from '@components/Checkbox/Checkbox.astro'
+import SvelteCheckbox from '@components/Checkbox/Checkbox.svelte'
+import ReactCheckbox from '@components/Checkbox/Checkbox.tsx'
+
+const sections = [
+ {
+ title: 'Astro checkboxes',
+ component: AstroCheckbox
+ },
+ {
+ title: 'Svelte checkboxes',
+ component: SvelteCheckbox
+ },
+ {
+ title: 'React checkboxes',
+ component: ReactCheckbox
+ }
+]
+---
+
+
+ Checkbox
+
+
+ {sections.map(section => (
+ {section.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 0eb7320..dd3b222 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -2,11 +2,16 @@
import Layout from '@static/Layout.astro'
import CardWrapper from '@static/CardWrapper.astro'
-import Button from '@components/Button/Button.astro'
import Accordion from '@components/Accordion/Accordion.astro'
-import Icon from '@components/Icon/Icon.astro'
-import Badge from '@components/Badge/Badge.astro'
import Alert from '@components/Alert/Alert.astro'
+import Avatar from '@components/Avatar/Avatar.astro'
+import Badge from '@components/Badge/Badge.astro'
+import Button from '@components/Button/Button.astro'
+import Icon from '@components/Icon/Icon.astro'
+import Radio from '@components/Radio/Radio.astro'
+import Rating from '@components/Rating/Rating.astro'
+import Switch from '@components/Switch/Switch.astro'
+import Checkbox from '@components/Checkbox/Checkbox.astro'
---
@@ -23,11 +28,16 @@ import Alert from '@components/Alert/Alert.astro'
GitHub
+
+ Svelte Playground
+
+
+ React Playground
+
@@ -44,6 +54,24 @@ import Alert from '@components/Alert/Alert.astro'
}]}
/>
+
+ You can create alert boxes.
+
+
+
+
+
+ Badge
+
Primary
Secondary
@@ -51,14 +79,47 @@ import Alert from '@components/Alert/Alert.astro'
Paragraph inside a card
+
+
+
-
+
+
+
+
+
-
- Badge
+
+
-
- You can create alert boxes.
+
+
+
+
+
@@ -83,6 +144,9 @@ import Alert from '@components/Alert/Alert.astro'
.cta {
text-align: center;
+ display: flex;
+ justify-content: center;
+ gap: 10px;
}
.grid {
diff --git a/src/pages/radio.astro b/src/pages/radio.astro
new file mode 100644
index 0000000..097360e
--- /dev/null
+++ b/src/pages/radio.astro
@@ -0,0 +1,124 @@
+---
+import Layout from '@static/Layout.astro'
+import ComponentWrapper from '@static/ComponentWrapper.astro'
+
+import AstroRadio from '@components/Radio/Radio.astro'
+import SvelteRadio from '@components/Radio/Radio.svelte'
+import ReactRadio from '@components/Radio/Radio.tsx'
+
+const sections = [
+ {
+ title: 'Astro radios',
+ component: AstroRadio
+ },
+ {
+ title: 'Svelte radios',
+ component: SvelteRadio
+ },
+ {
+ title: 'React radios',
+ component: ReactRadio
+ }
+]
+---
+
+
+ Radio
+
+
+ {sections.map((section, index) => (
+ {section.title}
+
+
+ link', value: 'md' },
+ { label: 'Large', value: 'lg' }
+ ]}
+ name={`radio1-${index}`}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ businesses', value: 'standard' },
+ { label: 'Premium', subText: 'For enterprise solutions', value: 'premium' }
+ ]}
+ name={`radio4-${index}`}
+ />
+
+
+
+
+
+
+
+
+
+
+ ))}
+
diff --git a/src/pages/rating.astro b/src/pages/rating.astro
new file mode 100644
index 0000000..edec679
--- /dev/null
+++ b/src/pages/rating.astro
@@ -0,0 +1,136 @@
+---
+import Layout from '@static/Layout.astro'
+import ComponentWrapper from '@static/ComponentWrapper.astro'
+
+import AstroRating from '@components/Rating/Rating.astro'
+import SvelteRating from '@components/Rating/Rating.svelte'
+import ReactRating from '@components/Rating/Rating.tsx'
+
+const sections = [
+ {
+ title: 'Astro ratings',
+ component: AstroRating
+ },
+ {
+ title: 'Svelte ratings',
+ component: SvelteRating
+ },
+ {
+ title: 'React ratings',
+ component: ReactRating
+ }
+]
+---
+
+
+ Rating
+
+
+ {sections.map(section => (
+ {section.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
diff --git a/src/pages/react.astro b/src/pages/react.astro
new file mode 100644
index 0000000..2ba5fc4
--- /dev/null
+++ b/src/pages/react.astro
@@ -0,0 +1,10 @@
+---
+import Layout from '@static/Layout.astro'
+import ReactPlayground from '@playground/ReactPlayground.tsx'
+---
+
+
+ React Playground
+ For interactive elements
+
+
diff --git a/src/pages/svelte.astro b/src/pages/svelte.astro
new file mode 100644
index 0000000..e11293a
--- /dev/null
+++ b/src/pages/svelte.astro
@@ -0,0 +1,10 @@
+---
+import Layout from '@static/Layout.astro'
+import SveltePlayground from '@playground/SveltePlayground.svelte'
+---
+
+
+ Svelte Playground
+ For interactive elements
+
+
diff --git a/src/pages/switch.astro b/src/pages/switch.astro
new file mode 100644
index 0000000..f9e14aa
--- /dev/null
+++ b/src/pages/switch.astro
@@ -0,0 +1,94 @@
+---
+import Layout from '@static/Layout.astro'
+import ComponentWrapper from '@static/ComponentWrapper.astro'
+
+import AstroSwitch from '@components/Switch/Switch.astro'
+import SvelteSwitch from '@components/Switch/Switch.svelte'
+import ReactSwitch from '@components/Switch/Switch.tsx'
+
+const sections = [
+ {
+ title: 'Astro ratings',
+ component: AstroSwitch
+ },
+ {
+ title: 'Svelte ratings',
+ component: SvelteSwitch
+ },
+ {
+ title: 'React ratings',
+ component: ReactSwitch
+ }
+]
+---
+
+
+ Switch
+
+
+ {sections.map(section => (
+ {section.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
diff --git a/src/playground/ReactPlayground.tsx b/src/playground/ReactPlayground.tsx
new file mode 100644
index 0000000..2dd9327
--- /dev/null
+++ b/src/playground/ReactPlayground.tsx
@@ -0,0 +1,63 @@
+import React from 'react'
+
+import Card from '@components/Card/Card.tsx'
+import Accordion from '@components/Accordion/Accordion.tsx'
+import Badge from '@components/Badge/Badge.tsx'
+import Button from '@components/Button/Button.tsx'
+import Checkbox from '@components/Checkbox/Checkbox.tsx'
+import Radio from '@components/Radio/Radio.tsx'
+import Switch from '@components/Switch/Switch.tsx'
+
+const ReactPlayground = () => {
+ return (
+
+
+
+
+
+
+ console.log('👋')}>Click me
+
+
+
+ console.log('👋')} theme="alert">
+ Click me
+
+
+
+
+ console.log(`checked: ${e.target.checked}`)}
+ />
+
+
+
+ console.log('changed to:', e.target.value)}
+ />
+
+
+
+ console.log(`switched: ${e.target.checked}`)}
+ />
+
+
+ )
+
+}
+
+export default ReactPlayground
diff --git a/src/playground/SveltePlayground.svelte b/src/playground/SveltePlayground.svelte
new file mode 100644
index 0000000..85cbce6
--- /dev/null
+++ b/src/playground/SveltePlayground.svelte
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+ console.log('👋')}>Click me
+
+
+
+ console.log('👋')} theme="alert">
+ Click me
+
+
+
+
+ console.log(`checked: ${e.target.checked}`)}
+ />
+
+
+
+ console.log('changed to:', e.target.value)}
+ />
+
+
+
+ console.log(`switched: ${e.target.checked}`)}
+ />
+
+
diff --git a/src/scss/setup.scss b/src/scss/setup.scss
index b365295..b162828 100644
--- a/src/scss/setup.scss
+++ b/src/scss/setup.scss
@@ -6,6 +6,17 @@ $config: (
includeElementStyles: true
);
+:root {
+ --w-avatar-border: #000;
+ --w-rating-color: #FFF;
+ --w-rating-empty-color: #BBB;
+ --w-rating-size: 18px;
+ --w-switch-off-color: #252525;
+ --w-switch-on-color: #FFF;
+ --w-checkbox-color: #FFF;
+ --w-radio-color: #FFF;
+}
+
@function config($key) {
@return map.get($config, $key);
}
diff --git a/svelte.config.js b/svelte.config.js
index a087de8..9462eb0 100644
--- a/svelte.config.js
+++ b/svelte.config.js
@@ -8,6 +8,7 @@ export default {
const ignoreWarnings = [
'a11y-click-events-have-key-events',
'a11y-no-static-element-interactions',
+ 'a11y-no-noninteractive-element-interactions',
'.accordion-title'
]
diff --git a/tsconfig.json b/tsconfig.json
index fb48aa1..7bfdd80 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,7 +5,8 @@
"types": ["vite/client"],
"paths": {
"@components/*": ["src/components/*"],
- "@static/*": ["src/static/*"]
+ "@static/*": ["src/static/*"],
+ "@playground/*": ["src/playground/*"]
}
}
}