Skip to content

Commit

Permalink
Merge pull request #170 from alley-rs/main
Browse files Browse the repository at this point in the history
0.3.9
  • Loading branch information
thep0y authored Nov 29, 2024
2 parents 69a42de + 97faeef commit c2dc910
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 4 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alley-components",
"version": "0.3.8",
"version": "0.3.9",
"repository": "https://github.com/alley-rs/alley-components",
"keywords": [
"solid",
Expand All @@ -26,13 +26,13 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^22.10.1",
"rollup-plugin-external-globals": "^0.12.1",
"rollup-plugin-external-globals": "^0.13.0",
"sass": "^1.81.0",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"vite": "^5.4.11",
"vite": "^6.0.1",
"vite-plugin-solid": "^2.11.0"
},
"files": [
Expand Down
139 changes: 139 additions & 0 deletions packages/components/select/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
.alley-select {
--alley-select-selector-bg: #fff;
--alley-select-select-affix-padding: 4px;

box-sizing: border-box;
margin: 0;
padding: 0;
color: var(--alley-color-text);
font-size: var(--alley-font-size);
line-height: var(--alley-line-height);
list-style: none;
font-family: var(--alley-font-family);
position: relative;
display: inline-flex;
height: 32px;

&-selector {
border: var(--alley-line-width) var(--alley-line-type)
var(--alley-color-border);
background: var(--alley-select-selector-bg);
width: 100%;
height: 100%;
align-items: center;
margin: 0;
padding: 0 calc(var(--alley-padding-l) - 1px);
position: relative;
transition: all var(--alley-motion-duration-mid)
var(--alley-motion-ease-in-out);
box-sizing: border-box;
color: var(--alley-color-text);
font-size: var(--alley-font-size);
line-height: var(--alley-line-height);
list-style: none;
font-family: inherit;
display: flex;
border-radius: var(--alley-border-radius);
flex: 1 1 auto;
}

&-prefix {
flex: none;
margin-inline-end: var(--alley-select-select-affix-padding);
}

&-placeholder {
color: var(--alley-color-weak);
}

&-arrow {
user-select: none;
display: flex;
align-items: center;
color: var(--alley-color-text-quaternary);
font-style: normal;
line-height: 1;
text-align: center;
text-transform: none;
vertical-align: -0.125em;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: absolute;
top: 50%;
inset-inline-start: auto;
inset-inline-end: calc(var(--alley-padding-l) - 1px);
height: var(--alley-font-size-icon);
margin-top: calc(var(--alley-font-size-icon) * -1 / 2);
font-size: var(--alley-font-size-icon);
pointer-events: none;
transition: opacity var(--alley-motion-duration-slow) ease;
}

&-item {
&-option {
--alley-select-option-height: 32px;
--alley-select-option-padding: 5px 12px;
--alley-select-option-font-size: 14px;
--alley-select-option-line-height: 1.5714285714285714;
--alley-select-option-selected-font-weight: 600;
--alley-select-option-selected-color: rgba(0, 0, 0, 0.88);
--alley-select-option-selected-bg: var(--alley-slate-blue-1);
--alley-select-option-hover-bg: rgba(0, 0, 0, 0.04);

position: relative;
display: block;
min-height: var(--alley-select-option-height);
padding: var(--alley-select-option-padding);
color: var(--alley-color-text);
font-weight: normal;
font-size: var(--alley-select-option-font-size);
line-height: var(--alley-select-option-line-height);
box-sizing: border-box;
transition: background var(--alley-motion-duration-slow) ease;
border-radius: var(--alley-border-radius-sm);

&-selected:not(.alley-select-item-option-disabled) {
color: var(--alley-select-option-selected-color);
font-weight: var(--alley-select-option-selected-font-weight);
background-color: var(--alley-select-option-selected-bg);
}

&:not(.alley-select-item-option-selected):hover {
background-color: var(--alley-select-option-hover-bg);
}
}
}
}

.alley-select-dropdown {
box-sizing: border-box;
margin: 0;
padding: var(--alley-padding-xxs);
color: var(--alley-color-text);
font-size: var(--alley-font-size);
line-height: var(--alley-line-height);
list-style: none;
font-family: var(--alley-font-family);
position: fixed;
top: 0;
z-index: var(--alley-select-z-index-popup);
overflow: hidden;
font-variant: initial;
background-color: var(--alley-color-bg-elevated);
border-radius: var(--alley-border-radius-lg);
outline: none;
box-shadow: var(--alley-box-shadow-secondary);
}

.dark .alley-select {
--alley-select-selector-bg: #141414;

&-item {
&-option {
--alley-select-option-selected-color: rgba(255, 255, 255, 0.85);
--alley-select-option-selected-bg: var(--alley-slate-blue-2);
--alley-select-option-hover-bg: rgba(255, 255, 255, 0.08);
}
}
}
147 changes: 147 additions & 0 deletions packages/components/select/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {
children,
createEffect,
createSignal,
For,
mergeProps,
onCleanup,
Show,
type JSX,
} from "solid-js";
import "./index.scss";
import type { BaseNoChildrenComponentProps, SizeType } from "~/interface";
import { classList } from "~/utils";
import { AiOutlineDown, AiOutlineUp } from "solid-icons/ai";
import { Portal } from "solid-js/web";

interface SelectOption {
label: string;
value: string;
}

interface SelectProps extends BaseNoChildrenComponentProps<HTMLDivElement> {
options: SelectOption[];
placeholder?: string;
defaultValue?: string;
position?: "top" | "bottom";
size?: SizeType;
prefix?: JSX.Element;
suffixIcon?: JSX.Element;
onChange?: (value: string) => void;
}

const baseClass = "alley-select";

const Select = (props: SelectProps) => {
let ref: HTMLDivElement | undefined;

const merged = mergeProps({ position: "bottom" }, props);

const [selectedValue, setSelectedValue] = createSignal(
merged.defaultValue || "",
);
const [isOpen, setIsOpen] = createSignal(false);
const [dropdownStyle, setDropdownStyle] = createSignal(merged.style);

const updateStyle = () => {
const rect = ref!.getBoundingClientRect();
setDropdownStyle((prev) => ({
...(prev ?? {}),
top: `${rect.top + rect.height + 4}px`,
left: `${rect.left}px`,
}));
};

createEffect(() => {
if (isOpen()) {
updateStyle();
window.addEventListener("resize", updateStyle);
window.addEventListener("scroll", updateStyle);
} else {
window.removeEventListener("resize", updateStyle);
window.removeEventListener("scroll", updateStyle);
}
});

onCleanup(() => {
window.removeEventListener("resize", updateStyle);
window.removeEventListener("scroll", updateStyle);
});

const classes = () =>
classList({
base: baseClass,
others: {
[`${baseClass}-${merged.size}`]: !!merged.size,
[`${baseClass}-open`]: isOpen(),
},
});

const toggleDropdown = () => setIsOpen(!isOpen());
const selectOption = (option: SelectOption) => {
setSelectedValue(option.value);
merged.onChange?.(option.value);
setIsOpen(false);
};

const selectedLabel = children(() => {
const selectedOption = merged.options.find(
(option) => option.value === selectedValue(),
);
return selectedOption ? (
<span>{selectedOption.label}</span>
) : (
<span class={`${baseClass}-placeholder`}>{merged.placeholder}</span> || ""

Check failure on line 94 in packages/components/select/index.tsx

View workflow job for this annotation

GitHub Actions / publish

This kind of expression is always truthy.
);
});

return (
<div ref={ref} classList={classes()} style={merged.style}>
<div class={`${baseClass}-selector`} onClick={toggleDropdown}>
<Show when={merged.prefix}>
<div class={`${baseClass}-prefix`}>{merged.prefix}</div>
</Show>
{selectedLabel()}
</div>

<span class={`${baseClass}-arrow`}>
<Show when={!merged.suffixIcon} fallback={merged.suffixIcon}>
<Show when={isOpen()} fallback={<AiOutlineDown />}>
<AiOutlineUp />
</Show>
</Show>
</span>

<Show when={isOpen()}>
<Portal>
<div
classList={{
[`${baseClass}-dropdown`]: true,
[`${baseClass}-${merged.position}`]: true,
}}
style={dropdownStyle()}
>
<ul>
<For each={merged.options}>
{(option) => (
<li
classList={{
[`${baseClass}-item-option`]: true,
[`${baseClass}-item-option-selected`]:
option.value === selectedValue(),
}}
onClick={() => selectOption(option)}
>
{option.label}
</li>
)}
</For>
</ul>
</div>
</Portal>
</Show>
</div>
);
};

export default Select;
1 change: 1 addition & 0 deletions packages/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ input[disabled] {
--alley-color-text-reverse: #1a1a1a;
--alley-color-text-disabled: #6a6a6a;
--alley-color-text-description: rgba(255, 255, 255, 0.45);
--alley-color-text-quaternary: rgba(255, 255, 255, 0.25);

--alley-color-shadow: #000;

Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const children = [
lazy(() => import("./components/switchs.tsx")),
lazy(() => import("./components/dialogs.tsx")),
lazy(() => import("./components/tags.tsx")),
lazy(() => import("./components/select.tsx")),
];

const LazyMenu = lazy(() => import("~/components/menu"));
Expand All @@ -36,6 +37,7 @@ const menus = [
"开关",
"对话框",
"标签",
"选择器",
];

const App = () => {
Expand Down
25 changes: 25 additions & 0 deletions src/components/select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { AiFillApi } from "solid-icons/ai";
import Select from "~/components/select";

const SelectDemo = () => {
const options = [
{ label: "Option 1", value: "1" },
{ label: "Option 2", value: "2" },
{ label: "Option 3", value: "3" },
];

return (
<Select
options={options}
placeholder="请选择"
defaultValue="1"
position="bottom"
prefix="demo"
suffixIcon={<AiFillApi />}
onChange={(value) => console.log("Selected:", value)}
style={{ width: "200px" }}
/>
);
};

export default SelectDemo;

0 comments on commit c2dc910

Please sign in to comment.