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} + {Array.isArray(alt) + {/each} +
+{:else} + {Array.isArray(alt) +{/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) => ( + {Array.isArray(alt) + ))} +
+ ) : ( + {Array.isArray(alt) + ) + +} + +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 @@ + + 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 ( + + ) +} + +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 => ( + + ))} +
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} + + {/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) => ( + + ))} +
+ ) +} + +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(' ') +--- + + 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 @@ + + + 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 ( + + ) + +} + +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'
+ +
@@ -44,6 +54,24 @@ import Alert from '@components/Alert/Alert.astro' }]} /> + + You can create alert boxes. + + + + + + Badge + @@ -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(`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(`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/*"] } } }