Skip to content

Commit

Permalink
dropdown-dialogコンポーネントを追加
Browse files Browse the repository at this point in the history
  • Loading branch information
oki07 committed Dec 10, 2024
1 parent 27af83c commit 5c69f18
Show file tree
Hide file tree
Showing 5 changed files with 502 additions and 0 deletions.
27 changes: 27 additions & 0 deletions src/components/dropdownDialog/dropdown-dialog.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.base {
position: relative;
}

.dialog {
position: absolute;
z-index: 1;
min-width: 560px;
margin-block-start: 8px;
padding: 24px;
background: var(--color-semantic-surface-regular-1);
border: 1px solid var(--color-semantic-border-semi-weak);
border-radius: 5px;
box-shadow: 0 3px 12px 0 var(--color-semantic-elevation-regular);
font-size: 12px;
line-height: 1.6;
}

.dialog.position__left {
left: 0;
right: auto;
}

.dialog.position__right {
left: auto;
right: 0;
}
162 changes: 162 additions & 0 deletions src/components/dropdownDialog/sp-dropdown-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// @ts-ignore
import resetStyle from "@acab/reset.css?inline" assert { type: "css" };
// @ts-ignore
import foundationStyle from "../foundation.css?inline" assert { type: "css" };
// @ts-ignore
import dropdownDialogStyle from "./dropdown-dialog.css?inline" assert { type: "css" };
import "../button/sp-button";

type Position = "left" | "right";

const positions: Position[] = ["left", "right"];

function isValidPosition(value: string): value is Position {
return positions.some((position) => position === value);
}

const styles = new CSSStyleSheet();
styles.replaceSync(`${resetStyle} ${foundationStyle} ${dropdownDialogStyle}`);

export class SpDropdownDialog extends HTMLElement {
#baseElement = document.createElement("div");
#buttonElement = document.createElement("sp-button");
#dialogElement = document.createElement("div");
#dialogSlotElement = document.createElement("slot");

#open: boolean = false;
#disabled: boolean = false;
#position: Position = "left";

set label(value: string) {
this.#buttonElement.text = value;
}

get open() {
return this.#open;
}
set open(value: boolean) {
this.#open = value;

if (value) {
this.#buttonElement.setAttribute("selected", "");
} else {
this.#buttonElement.removeAttribute("selected");
}

this.#updateDialogDisplay();
}

get disabled() {
return this.#disabled;
}
set disabled(value: boolean) {
this.#disabled = value;
this.#buttonElement.disabled = value;
this.#updateDialogDisplay();
}

get position() {
return this.#position;
}
set position(value: Position) {
if (value === "left") {
this.#dialogElement.classList.add("position__left");
this.#dialogElement.classList.remove("position__right");
} else {
this.#dialogElement.classList.add("position__right");
this.#dialogElement.classList.remove("position__left");
}

this.#position = value;
}

static get observedAttributes() {
return ["label", "open", "disabled", "position"];
}

constructor() {
super();

const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, styles];

this.open = false;
this.disabled = false;
this.position = "left";
}

connectedCallback() {
this.#buttonElement.setAttribute("part", "button");
this.#buttonElement.addEventListener(
"click",
this.#handleClickButton.bind(this),
);

this.#baseElement.appendChild(this.#buttonElement);

this.#dialogElement.classList.add("dialog");
this.#dialogElement.role = "dialog";
this.#dialogElement.appendChild(this.#dialogSlotElement);

window.addEventListener("click", this.#handleClickOutside.bind(this));

this.#baseElement.appendChild(this.#dialogElement);
this.#baseElement.classList.add("base");

this.shadowRoot?.appendChild(this.#baseElement);
}

disconnectedCallback() {
window.removeEventListener("click", this.#handleClickOutside.bind(this));
}

attributeChangedCallback(name: string, oldValue: string, newValue: string) {
if (oldValue === newValue) return;
switch (name) {
case "label":
this.label = newValue;
break;
case "open":
this.open = newValue === "true" || newValue === "";
break;
case "disabled":
this.disabled = newValue === "true" || newValue === "";
break;
case "position":
if (isValidPosition(newValue)) {
this.position = newValue;
} else {
console.warn(`${newValue}は無効なposition属性です。`);
this.position = "left";
}
}
}

#handleClickButton(event: MouseEvent) {
event.stopPropagation();

this.open = !this.open;
}

#handleClickOutside(event: MouseEvent) {
event.stopPropagation();

if (!this.contains(event.target as Node)) {
this.open = false;
}
}

#updateDialogDisplay() {
this.#dialogElement.style.display =
this.open && !this.disabled ? "block" : "none";
}
}

declare global {
interface HTMLElementTagNameMap {
"sp-dropdown-dialog": SpDropdownDialog;
}
}

customElements.get("sp-dropdown-dialog") ||
customElements.define("sp-dropdown-dialog", SpDropdownDialog);
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { SpCheckboxText } from "./components/checkbox/sp-checkbox-text";
export { SpCheckboxList } from "./components/checkbox/sp-checkbox-list";
export { SpIcon } from "./components/icon/sp-icon";
export { SpRadioButtonTextGroup } from "./components/radio/sp-radio-button-text-group";
export { SpDropdownDialog } from "./components/dropdownDialog/sp-dropdown-dialog";
67 changes: 67 additions & 0 deletions stories/dropdownDialog/sp-dropdown-dialog.story.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import "@sp-design/token/lib/speeda-tokens.css";
import type { Meta, StoryObj } from "@storybook/web-components";
import { html } from "lit";
import "../../src/components/dropdownDialog/sp-dropdown-dialog";

const meta: Meta = {
component: "sp-dropdown-dialog",
argTypes: {},
args: {},
};
export default meta;

type Story = StoryObj;

export const Basic: Story = {
render: () => html`
<sp-dropdown-dialog label="ダイアログを表示">
<h1>ダイアログのタイトル</h1>
ダイアログの内容
</sp-dropdown-dialog>
`,
};

export const LongText: Story = {
render: () => html`
<sp-dropdown-dialog label="検索式を表示">
<h1>検索式</h1>
<div>S001 FI:G08G1/16?</div>
<div>S002 FI:B60W30?+B60W40?+B60W50?</div>
<div>S003 FI:B60?+G01C21/?+G08G1/?+G05D1/?</div>
<div>
S004
全文:?先進運転支援?+?高度運転支援?+?advanced?*?driver-assistance?*?systems?
</div>
<div>
S005
名称+要約+請求項:?自動運転?+?自動走行?+?自律運転?+?自律走行?+?オートクルーズ?+?衝突被害軽減?+?車間距離制御?+?アダプティブクルーズコントロール?+?アダプティブフロントライティング?+?車線維持支援?+?車線逸脱防止?+?車線逸脱警告?+?死角検出?+?死角検知?+?死角モニタ?+?クロストラフィックアラート?+?駐車支援?+?パーキングアシスト?+?トラフィックジャムアシスト?+?渋滞運転支援?+?ナイトビジョン?+?暗視?
</div>
<div>S006 名称+要約+請求項:[?自動?*?ブレーキ?,?制動?]W3</div>
<div>
S007 名称+要約+請求項:[?歩行者?,?標識?,?居眠?*?検知?,?検出?,?認識?]W3
</div>
<div>S008 名称+要約+請求項:[?前方?*?衝突?]W3</div>
<div>
S009 名称+要約+請求項:[?運転者?,?運転手?,?ドライバ?*?監視?,?モニタ?]W3
</div>
<div>S010 論理式:S001+S002+S003*(S004+S005+S006+S007+S008+S009)</div>
</sp-dropdown-dialog>
`,
};

export const RightPosition: Story = {
render: () => html`
<div style="display: flex; justify-content: end">
<sp-dropdown-dialog label="ダイアログを表示" open position="right">
<h1>ダイアログのタイトル</h1>
ダイアログの内容
</sp-dropdown-dialog>
</div>
`,
};

export const Disabled: Story = {
render: () => html`
<sp-dropdown-dialog label="ダイアログを表示" disabled></sp-dropdown-dialog>
`,
};
Loading

0 comments on commit 5c69f18

Please sign in to comment.