├── src ├── _scss │ ├── w98 │ │ ├── _cursors.scss │ │ ├── var │ │ │ ├── var.scss │ │ │ ├── colors.scss │ │ │ └── uris.scss │ │ ├── cursors │ │ │ ├── NO-min.png │ │ │ ├── X-min.png │ │ │ ├── Copy-min.png │ │ │ ├── Hand-min.png │ │ │ ├── Help-min.png │ │ │ ├── Link-min.png │ │ │ ├── Move-min.png │ │ │ ├── Text-min.png │ │ │ ├── Wait-min.png │ │ │ ├── Arrow-min.png │ │ │ ├── Circle-min.png │ │ │ ├── Cross-min.png │ │ │ ├── DND-ask-min.png │ │ │ ├── SizeAll-min.png │ │ │ ├── UpArrow-min.png │ │ │ ├── VText-min.png │ │ │ ├── ZoomIn-min.png │ │ │ ├── ZoomOut-min.png │ │ │ ├── Circle-min-1.png │ │ │ ├── ColRezise-min.png │ │ │ ├── Crosshair-min.png │ │ │ ├── DND-copy-min.png │ │ │ ├── DND-link-min.png │ │ │ ├── DownArrow-min.png │ │ │ ├── LeftArrow-min.png │ │ │ ├── LeftRight-min.png │ │ │ ├── RowResize-min.png │ │ │ ├── textSmall-min.png │ │ │ ├── AngleUpLeft-min.png │ │ │ ├── AngleUpRight-min.png │ │ │ ├── AppStarting-min.png │ │ │ ├── ArrowRight-min.png │ │ │ ├── HandPointer-min.png │ │ │ ├── Handsqueezed-min.png │ │ │ ├── Handwriting-min.png │ │ │ ├── RightArrow-min.png │ │ │ ├── UpDownArrow-min.png │ │ │ ├── UpLeftArrow-min.png │ │ │ ├── UpRightArrow-min.png │ │ │ ├── DownLeftArrow-min.png │ │ │ ├── DownRightArrow-min.png │ │ │ └── LeftRightArrow-min.png │ │ ├── fonts │ │ │ ├── ms98_11.ttf │ │ │ ├── MSSansSerif.woff2 │ │ │ ├── ms98s11a10d02.ttf │ │ │ └── MSSansSerifBold.woff2 │ │ ├── index.scss │ │ ├── mixins │ │ │ └── box-shadows.scss │ │ ├── functions │ │ │ └── box-shadows.scss │ │ ├── _icons.scss │ │ ├── _inputs.scss │ │ ├── _window.scss │ │ └── _task-bar.scss │ └── w98_simple.scss ├── images │ ├── uris.css │ ├── help.gif │ ├── close.gif │ ├── close.png │ ├── restore.gif │ ├── restore.png │ ├── arrow-up.gif │ ├── icons │ │ ├── cut.png │ │ ├── back.png │ │ ├── copy.png │ │ ├── delete.png │ │ ├── paste.png │ │ ├── undo.png │ │ ├── upDir.png │ │ ├── views.png │ │ ├── forward.png │ │ ├── properties.png │ │ ├── backDisabled.png │ │ └── forwardDisabled.png │ ├── maximize.gif │ ├── maximize.png │ ├── minimize.gif │ ├── minimize.png │ ├── radio-off.gif │ ├── radio-on.gif │ ├── uris.css.map │ ├── windows98.gif │ ├── arrow-down.gif │ ├── arrow-left.gif │ ├── arrow-right.gif │ ├── cursor-pixel.gif │ ├── menu-checked.gif │ ├── menu-radio.gif │ ├── more-options.gif │ ├── rgba0-0-0-50.png │ ├── rgba0-0-0-75.png │ ├── start-trans.png │ ├── close-disabled.gif │ ├── help-disabled.gif │ ├── rgba0-0-180-75.gif │ ├── rgba0-0-180-75.png │ ├── rgba70-70-180.gif │ ├── cursor-pixel-x2.gif │ ├── radio-on-disabled.gif │ ├── rgba-204-204-204.gif │ ├── arrow-down-disabled.gif │ ├── radio-off-disabled.gif │ ├── rgba-204-204-204-50.png │ ├── rgba-204-204-204-75.png │ ├── rgba-204-204-204-85.png │ ├── rgba-255-255-255-50.png │ ├── arrow-right-inverted.gif │ ├── menu-checked-disabled.gif │ └── uris.scss ├── components │ ├── TaskBar │ │ ├── index.js │ │ ├── _TaskBar.scss │ │ ├── Notifier.jsx │ │ ├── __tests__ │ │ │ ├── Notifier.spec.jsx │ │ │ ├── Notifications.spec.jsx │ │ │ └── TaskBar.spec.jsx │ │ ├── Notifications.jsx │ │ └── TaskBar.jsx │ ├── MenuBar │ │ ├── index.js │ │ ├── _MenuBar.scss │ │ ├── MenuBar.jsx │ │ └── index.spec.js │ ├── FormRadio │ │ ├── index.js │ │ ├── _Radio.scss │ │ ├── Radio.jsx │ │ └── Radio.spec.jsx │ ├── Frame │ │ ├── index.js │ │ ├── _Frame.scss │ │ ├── Frame.jsx │ │ └── Frame.spec.jsx │ ├── StartMenu │ │ ├── index.js │ │ ├── StartMenu.spec.jsx │ │ └── StartMenu.jsx │ ├── ButtonForm │ │ ├── index.js │ │ ├── _ButtonForm.scss │ │ ├── ButtonForm.jsx │ │ └── FormButton.spec.jsx │ ├── ButtonNav │ │ ├── index.js │ │ ├── _ButtonNav.scss │ │ ├── ButtonNav.jsx │ │ └── NavButton.spec.jsx │ ├── FormInputText │ │ ├── index.js │ │ ├── _InputText.scss │ │ ├── InputText.spec.jsx │ │ └── InputText.jsx │ ├── FormSelectBox │ │ ├── index.js │ │ ├── SelectBox.spec.jsx │ │ ├── SelectBox.jsx │ │ └── _SelectBox.scss │ ├── FormSelectDISABLED │ │ ├── index.js │ │ ├── README.md │ │ ├── Select.spec.jsx │ │ └── _Select.scss │ ├── FormToggle │ │ ├── index.js │ │ ├── Toggle.jsx │ │ └── Toggle.spec.jsx │ ├── Window │ │ ├── index.js │ │ ├── WindowAbstract.spec.jsx │ │ ├── _WindowAbstract.scss │ │ └── Window.jsx │ ├── ButtonStart │ │ ├── index.js │ │ ├── _StartButton.scss │ │ ├── ButtonStart.jsx │ │ └── ButtonStart.spec.jsx │ ├── FormCheckbox │ │ ├── index.js │ │ ├── _Checkbox.scss │ │ ├── Checkbox.jsx │ │ └── Checkbox.spec.jsx │ ├── FormFakeSelect │ │ ├── index.js │ │ ├── FakeSelect.jsx │ │ └── _FakeSelect.scss │ ├── WindowAlert │ │ ├── index.js │ │ ├── _WindowAlert.scss │ │ ├── WindowAlert.spec.jsx │ │ └── WindowAlert.jsx │ ├── ExplorerView │ │ ├── index.js │ │ ├── styles │ │ │ ├── ExplorerView.css.map │ │ │ ├── ExplorerView.scss │ │ │ └── ExplorerView.css │ │ ├── __tests__ │ │ │ └── ExplorerView.spec.jsx │ │ └── ExplorerView.jsx │ ├── Icon │ │ ├── _AbstractIcon.scss │ │ ├── index.js │ │ ├── AbstractIcon.spec.jsx │ │ └── Icon.jsx │ ├── IconListIcon │ │ ├── index.js │ │ ├── _ListIcon.scss │ │ ├── ListIcon.spec.jsx │ │ └── IconListIcon.jsx │ ├── WindowAction │ │ ├── index.js │ │ ├── assets │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ └── 5.png │ │ ├── _styles.scss │ │ └── WindowAction.jsx │ ├── ButtonProgram │ │ ├── index.js │ │ ├── _ButtonProgram.scss │ │ ├── ButtonProgram.jsx │ │ └── ProgramButton.spec.jsx │ ├── WindowProgram │ │ ├── index.js │ │ ├── _WindowProgram.scss │ │ ├── WindowProgram.spec.jsx │ │ └── WindowProgram.jsx │ ├── Button │ │ ├── _AbstractButton.scss │ │ ├── index.js │ │ ├── Button.jsx │ │ └── Button.spec.jsx │ ├── ButtonIconLarge │ │ ├── index.js │ │ ├── _ButtonIconLarge.scss │ │ ├── ButtonIconLarge.jsx │ │ └── LargeIconButton.spec.jsx │ ├── ButtonIconSmall │ │ ├── index.js │ │ ├── _ButtonIconSmall.scss │ │ ├── ButtonIconSmall.jsx │ │ └── SmallIconButton.spec.jsx │ ├── DetailsSection │ │ ├── index.js │ │ ├── _details-section.scss │ │ ├── DetailsSection.jsx │ │ └── index.spec.js │ ├── IconExplorerIcon │ │ ├── index.js │ │ ├── _ExplorerIcon.scss │ │ ├── ExplorerIcon.spec.jsx │ │ └── IconExplorerIcon.jsx │ ├── StandardMenuHOC │ │ ├── index.js │ │ └── StandardMenuHOC.jsx │ ├── WindowExplorer │ │ ├── index.js │ │ ├── WindowExplorer.spec.jsx │ │ ├── WindowExplorer.jsx │ │ └── _WindowExplorer.scss │ ├── ResizableIconsList │ │ ├── index.js │ │ ├── _options-list.scss │ │ └── ResizableIconsList.jsx │ ├── FormSelectBoxSimple │ │ ├── index.js │ │ ├── _SelectMultipleSimple.scss │ │ ├── SelectMultipleSimple.spec.jsx │ │ └── SelectMultipleSimple.jsx │ ├── StandardMenu │ │ ├── index.js │ │ ├── _StandardMenu.scss │ │ ├── StandardMenuItem.jsx │ │ └── StandardMenu.jsx │ └── Theme.jsx ├── setupTests.js └── index.js ├── static.json ├── .storybook ├── stories │ ├── directory_closed.png │ ├── scrollbar.stories.jsx │ ├── desktop.stories.jsx │ ├── start.stories.jsx │ ├── icons.stories.jsx │ ├── buttons.stories.jsx │ ├── taskbar.stories.jsx │ ├── contextMenu.stories.jsx │ └── windows.stories.jsx ├── addons.old.js ├── preview.jsx ├── old.webpack.old.config.js ├── main.js ├── config.old.js └── manager-head.html ├── .travis.yml ├── playroom ├── FrameComponent.js └── index.js ├── .codeclimate.yml ├── .editorconfig ├── rename.sh ├── jest.config.js ├── .babelrc ├── .eslintrc.js ├── LICENSE ├── .gitignore ├── rollup.config.babel.js ├── .stylelintrc.json ├── playroom.config.js ├── TODO.md └── package.json /src/_scss/w98/_cursors.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/uris.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /*# sourceMappingURL=uris.css.map */ 4 | -------------------------------------------------------------------------------- /src/_scss/w98/var/var.scss: -------------------------------------------------------------------------------- 1 | $window-padding: 3px; 2 | $base-font-size: 11px; 3 | -------------------------------------------------------------------------------- /src/components/TaskBar/index.js: -------------------------------------------------------------------------------- 1 | import TaskBar from './TaskBar'; 2 | export default TaskBar; 3 | -------------------------------------------------------------------------------- /src/images/help.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/help.gif -------------------------------------------------------------------------------- /src/components/MenuBar/index.js: -------------------------------------------------------------------------------- 1 | import MenuBar from './MenuBar'; 2 | 3 | export default MenuBar; 4 | -------------------------------------------------------------------------------- /src/images/close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/close.gif -------------------------------------------------------------------------------- /src/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/close.png -------------------------------------------------------------------------------- /src/images/restore.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/restore.gif -------------------------------------------------------------------------------- /src/images/restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/restore.png -------------------------------------------------------------------------------- /static.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": ".out", 3 | "routes": { 4 | "/**": "index.html" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/components/FormRadio/index.js: -------------------------------------------------------------------------------- 1 | import FormRadio from './Radio'; 2 | 3 | export default FormRadio; 4 | -------------------------------------------------------------------------------- /src/components/Frame/index.js: -------------------------------------------------------------------------------- 1 | import WindowFrame from './Frame'; 2 | 3 | export default WindowFrame; 4 | -------------------------------------------------------------------------------- /src/components/StartMenu/index.js: -------------------------------------------------------------------------------- 1 | import StartMenu from './StartMenu'; 2 | export default StartMenu; 3 | -------------------------------------------------------------------------------- /src/images/arrow-up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/arrow-up.gif -------------------------------------------------------------------------------- /src/images/icons/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/cut.png -------------------------------------------------------------------------------- /src/images/maximize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/maximize.gif -------------------------------------------------------------------------------- /src/images/maximize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/maximize.png -------------------------------------------------------------------------------- /src/images/minimize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/minimize.gif -------------------------------------------------------------------------------- /src/images/minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/minimize.png -------------------------------------------------------------------------------- /src/images/radio-off.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/radio-off.gif -------------------------------------------------------------------------------- /src/images/radio-on.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/radio-on.gif -------------------------------------------------------------------------------- /src/images/uris.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":[],"names":[],"mappings":"","file":"uris.css"} -------------------------------------------------------------------------------- /src/images/windows98.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/windows98.gif -------------------------------------------------------------------------------- /src/components/ButtonForm/index.js: -------------------------------------------------------------------------------- 1 | import ButtonForm from './ButtonForm'; 2 | 3 | export default ButtonForm; 4 | -------------------------------------------------------------------------------- /src/components/ButtonNav/index.js: -------------------------------------------------------------------------------- 1 | import ButtonNav from './ButtonNav'; 2 | 3 | export default ButtonNav; 4 | -------------------------------------------------------------------------------- /src/components/FormInputText/index.js: -------------------------------------------------------------------------------- 1 | import InputText from './InputText'; 2 | 3 | export default InputText; 4 | -------------------------------------------------------------------------------- /src/components/FormSelectBox/index.js: -------------------------------------------------------------------------------- 1 | import SelectBox from './SelectBox'; 2 | 3 | export default SelectBox; 4 | -------------------------------------------------------------------------------- /src/components/FormSelectDISABLED/index.js: -------------------------------------------------------------------------------- 1 | import Select from './Select'; 2 | 3 | export default Select; 4 | -------------------------------------------------------------------------------- /src/components/FormToggle/index.js: -------------------------------------------------------------------------------- 1 | import FormToggle from './Toggle'; 2 | 3 | export default FormToggle; 4 | -------------------------------------------------------------------------------- /src/components/Window/index.js: -------------------------------------------------------------------------------- 1 | import WindowAbstract from './Window'; 2 | 3 | export default WindowAbstract; 4 | -------------------------------------------------------------------------------- /src/images/arrow-down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/arrow-down.gif -------------------------------------------------------------------------------- /src/images/arrow-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/arrow-left.gif -------------------------------------------------------------------------------- /src/images/arrow-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/arrow-right.gif -------------------------------------------------------------------------------- /src/images/cursor-pixel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/cursor-pixel.gif -------------------------------------------------------------------------------- /src/images/icons/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/back.png -------------------------------------------------------------------------------- /src/images/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/copy.png -------------------------------------------------------------------------------- /src/images/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/delete.png -------------------------------------------------------------------------------- /src/images/icons/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/paste.png -------------------------------------------------------------------------------- /src/images/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/undo.png -------------------------------------------------------------------------------- /src/images/icons/upDir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/upDir.png -------------------------------------------------------------------------------- /src/images/icons/views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/views.png -------------------------------------------------------------------------------- /src/images/menu-checked.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/menu-checked.gif -------------------------------------------------------------------------------- /src/images/menu-radio.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/menu-radio.gif -------------------------------------------------------------------------------- /src/images/more-options.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/more-options.gif -------------------------------------------------------------------------------- /src/images/rgba0-0-0-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba0-0-0-50.png -------------------------------------------------------------------------------- /src/images/rgba0-0-0-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba0-0-0-75.png -------------------------------------------------------------------------------- /src/images/start-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/start-trans.png -------------------------------------------------------------------------------- /src/components/ButtonStart/index.js: -------------------------------------------------------------------------------- 1 | import ButtonStart from './ButtonStart'; 2 | 3 | export default ButtonStart; 4 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/index.js: -------------------------------------------------------------------------------- 1 | import FormCheckbox from './Checkbox'; 2 | 3 | export default FormCheckbox; 4 | -------------------------------------------------------------------------------- /src/components/FormFakeSelect/index.js: -------------------------------------------------------------------------------- 1 | import FakeSelect from './FakeSelect'; 2 | 3 | export default FakeSelect; 4 | -------------------------------------------------------------------------------- /src/components/WindowAlert/index.js: -------------------------------------------------------------------------------- 1 | import WindowAlert from './WindowAlert'; 2 | 3 | export default WindowAlert; 4 | -------------------------------------------------------------------------------- /src/images/close-disabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/close-disabled.gif -------------------------------------------------------------------------------- /src/images/help-disabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/help-disabled.gif -------------------------------------------------------------------------------- /src/images/icons/forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/forward.png -------------------------------------------------------------------------------- /src/images/rgba0-0-180-75.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba0-0-180-75.gif -------------------------------------------------------------------------------- /src/images/rgba0-0-180-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba0-0-180-75.png -------------------------------------------------------------------------------- /src/images/rgba70-70-180.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba70-70-180.gif -------------------------------------------------------------------------------- /src/_scss/w98/cursors/NO-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/NO-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/X-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/X-min.png -------------------------------------------------------------------------------- /src/_scss/w98/fonts/ms98_11.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/fonts/ms98_11.ttf -------------------------------------------------------------------------------- /src/components/ExplorerView/index.js: -------------------------------------------------------------------------------- 1 | import ExplorerView from './ExplorerView'; 2 | 3 | export default ExplorerView; 4 | -------------------------------------------------------------------------------- /src/components/Icon/_AbstractIcon.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_icons"; 2 | 3 | .icon { 4 | @include icon(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/IconListIcon/index.js: -------------------------------------------------------------------------------- 1 | import IconListIcon from './IconListIcon'; 2 | 3 | export default IconListIcon; 4 | -------------------------------------------------------------------------------- /src/components/WindowAction/index.js: -------------------------------------------------------------------------------- 1 | import WindowAction from './WindowAction'; 2 | 3 | export default WindowAction; 4 | -------------------------------------------------------------------------------- /src/images/cursor-pixel-x2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/cursor-pixel-x2.gif -------------------------------------------------------------------------------- /src/images/icons/properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/properties.png -------------------------------------------------------------------------------- /src/images/radio-on-disabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/radio-on-disabled.gif -------------------------------------------------------------------------------- /src/images/rgba-204-204-204.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba-204-204-204.gif -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Copy-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Copy-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Hand-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Hand-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Help-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Help-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Link-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Link-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Move-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Move-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Text-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Text-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Wait-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Wait-min.png -------------------------------------------------------------------------------- /src/components/ButtonProgram/index.js: -------------------------------------------------------------------------------- 1 | import ButtonProgram from './ButtonProgram'; 2 | 3 | export default ButtonProgram; 4 | -------------------------------------------------------------------------------- /src/components/Frame/_Frame.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_window"; 2 | 3 | .Frame { 4 | @include window-basic(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/WindowProgram/index.js: -------------------------------------------------------------------------------- 1 | import WindowProgram from './WindowProgram'; 2 | 3 | export default WindowProgram; 4 | -------------------------------------------------------------------------------- /src/images/arrow-down-disabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/arrow-down-disabled.gif -------------------------------------------------------------------------------- /src/images/icons/backDisabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/backDisabled.png -------------------------------------------------------------------------------- /src/images/radio-off-disabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/radio-off-disabled.gif -------------------------------------------------------------------------------- /src/images/rgba-204-204-204-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba-204-204-204-50.png -------------------------------------------------------------------------------- /src/images/rgba-204-204-204-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba-204-204-204-75.png -------------------------------------------------------------------------------- /src/images/rgba-204-204-204-85.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba-204-204-204-85.png -------------------------------------------------------------------------------- /src/images/rgba-255-255-255-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/rgba-255-255-255-50.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Arrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Arrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Circle-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Circle-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Cross-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Cross-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/DND-ask-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/DND-ask-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/SizeAll-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/SizeAll-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/UpArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/UpArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/VText-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/VText-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/ZoomIn-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/ZoomIn-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/ZoomOut-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/ZoomOut-min.png -------------------------------------------------------------------------------- /src/_scss/w98/fonts/MSSansSerif.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/fonts/MSSansSerif.woff2 -------------------------------------------------------------------------------- /src/_scss/w98/fonts/ms98s11a10d02.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/fonts/ms98s11a10d02.ttf -------------------------------------------------------------------------------- /src/components/Button/_AbstractButton.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_buttons"; 2 | 3 | .btn { 4 | @include button(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ButtonIconLarge/index.js: -------------------------------------------------------------------------------- 1 | import ButtonIconLarge from './ButtonIconLarge'; 2 | 3 | export default ButtonIconLarge; 4 | -------------------------------------------------------------------------------- /src/components/ButtonIconSmall/index.js: -------------------------------------------------------------------------------- 1 | import ButtonIconSmall from './ButtonIconSmall'; 2 | 3 | export default ButtonIconSmall; 4 | -------------------------------------------------------------------------------- /src/components/DetailsSection/index.js: -------------------------------------------------------------------------------- 1 | import DetailsSection from './DetailsSection'; 2 | 3 | export default DetailsSection; 4 | -------------------------------------------------------------------------------- /src/components/IconExplorerIcon/index.js: -------------------------------------------------------------------------------- 1 | import ExplorerIcon from './IconExplorerIcon'; 2 | 3 | export default ExplorerIcon; 4 | -------------------------------------------------------------------------------- /src/components/MenuBar/_MenuBar.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_window"; 2 | 3 | .MenuBar { 4 | @include window-menu(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/StandardMenuHOC/index.js: -------------------------------------------------------------------------------- 1 | import withStandardMenu from './StandardMenuHOC'; 2 | export default withStandardMenu; 3 | -------------------------------------------------------------------------------- /src/components/WindowExplorer/index.js: -------------------------------------------------------------------------------- 1 | import WindowExplorer from './WindowExplorer'; 2 | 3 | export default WindowExplorer; 4 | -------------------------------------------------------------------------------- /src/images/arrow-right-inverted.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/arrow-right-inverted.gif -------------------------------------------------------------------------------- /src/images/icons/forwardDisabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/icons/forwardDisabled.png -------------------------------------------------------------------------------- /src/images/menu-checked-disabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/images/menu-checked-disabled.gif -------------------------------------------------------------------------------- /.storybook/stories/directory_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/.storybook/stories/directory_closed.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Circle-min-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Circle-min-1.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/ColRezise-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/ColRezise-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Crosshair-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Crosshair-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/DND-copy-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/DND-copy-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/DND-link-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/DND-link-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/DownArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/DownArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/LeftArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/LeftArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/LeftRight-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/LeftRight-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/RowResize-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/RowResize-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/textSmall-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/textSmall-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/AngleUpLeft-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/AngleUpLeft-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/AngleUpRight-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/AngleUpRight-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/AppStarting-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/AppStarting-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/ArrowRight-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/ArrowRight-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/HandPointer-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/HandPointer-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Handsqueezed-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Handsqueezed-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/Handwriting-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/Handwriting-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/RightArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/RightArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/UpDownArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/UpDownArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/UpLeftArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/UpLeftArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/UpRightArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/UpRightArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/fonts/MSSansSerifBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/fonts/MSSansSerifBold.woff2 -------------------------------------------------------------------------------- /src/components/ButtonNav/_ButtonNav.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_buttons"; 2 | 3 | .btn.ButtonNav { 4 | @include button-nav(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/IconListIcon/_ListIcon.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_icons"; 2 | 3 | .icon.ListIcon { 4 | @include icon-list(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/TaskBar/_TaskBar.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_task-bar"; 2 | 3 | .TaskBar { 4 | @include task-bar(".TaskBar"); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/WindowAction/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/components/WindowAction/assets/1.png -------------------------------------------------------------------------------- /src/components/WindowAction/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/components/WindowAction/assets/2.png -------------------------------------------------------------------------------- /src/components/WindowAction/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/components/WindowAction/assets/3.png -------------------------------------------------------------------------------- /src/components/WindowAction/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/components/WindowAction/assets/4.png -------------------------------------------------------------------------------- /src/components/WindowAction/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/components/WindowAction/assets/5.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/DownLeftArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/DownLeftArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/DownRightArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/DownRightArrow-min.png -------------------------------------------------------------------------------- /src/_scss/w98/cursors/LeftRightArrow-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/padraigfl/packard-belle/HEAD/src/_scss/w98/cursors/LeftRightArrow-min.png -------------------------------------------------------------------------------- /src/components/ButtonForm/_ButtonForm.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_buttons"; 2 | 3 | .btn.ButtonForm { 4 | @include button-form(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/FormInputText/_InputText.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_inputs.scss"; 2 | 3 | .InputText { 4 | @include input-text(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ResizableIconsList/index.js: -------------------------------------------------------------------------------- 1 | import ResizableIconsList from './ResizableIconsList'; 2 | 3 | export default ResizableIconsList; 4 | -------------------------------------------------------------------------------- /src/components/WindowAlert/_WindowAlert.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_window"; 2 | 3 | .WindowAlert { 4 | @include window-alert(); 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | - "10" 6 | after_success: 7 | - npm run coverage 8 | - npm run coveralls 9 | -------------------------------------------------------------------------------- /src/components/ButtonStart/_StartButton.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_buttons"; 2 | 3 | .btn.StartButton { 4 | @include button-start(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/FormSelectBoxSimple/index.js: -------------------------------------------------------------------------------- 1 | import SelectMultipleSimple from './SelectMultipleSimple'; 2 | 3 | export default SelectMultipleSimple; 4 | -------------------------------------------------------------------------------- /src/components/Icon/index.js: -------------------------------------------------------------------------------- 1 | import AbstractIcon from './Icon'; 2 | export const iconProps = AbstractIcon.propTypes; 3 | export default AbstractIcon; 4 | -------------------------------------------------------------------------------- /src/components/ButtonProgram/_ButtonProgram.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_buttons"; 2 | 3 | .btn.ButtonProgram { 4 | @include button-program(); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/IconExplorerIcon/_ExplorerIcon.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_icons"; 2 | 3 | .icon.ExplorerIcon { 4 | @include icon-explorer(); 5 | } 6 | -------------------------------------------------------------------------------- /playroom/FrameComponent.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from 'react'; 3 | 4 | export default props =>
{props.children}
; 5 | -------------------------------------------------------------------------------- /src/components/Button/index.js: -------------------------------------------------------------------------------- 1 | import AbstractButton from './Button'; 2 | export const commonButtonPropTypes = AbstractButton.propTypes; 3 | export default AbstractButton; 4 | -------------------------------------------------------------------------------- /src/components/StandardMenu/index.js: -------------------------------------------------------------------------------- 1 | import StandardMenu from './StandardMenu'; 2 | export const standardMenuProps = StandardMenu.propTypes; 3 | export default StandardMenu; 4 | -------------------------------------------------------------------------------- /.storybook/addons.old.js: -------------------------------------------------------------------------------- 1 | // import '@storybook/addon-options/register'; 2 | // import '@storybook/addon-notes/register'; 3 | // import '@storybook/addon-storysource/register'; 4 | -------------------------------------------------------------------------------- /src/components/DetailsSection/_details-section.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_window"; 2 | 3 | .DetailsSection, 4 | .window__section { 5 | @include window-settings-section(); 6 | } 7 | -------------------------------------------------------------------------------- /src/components/FormRadio/_Radio.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_inputs.scss"; 2 | 3 | .Radio { 4 | display: inline-block; 5 | input[type="radio"] { 6 | @include radio(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | const Enzyme = require('enzyme'); 2 | const Adapter = require('enzyme-adapter-react-16'); 3 | 4 | require('raf/polyfill'); 5 | 6 | Enzyme.configure({ adapter: new Adapter() }); 7 | 8 | -------------------------------------------------------------------------------- /src/components/ButtonIconSmall/_ButtonIconSmall.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_buttons"; 2 | @import "../../_scss/w98/var/uris.scss"; 3 | 4 | .btn.ButtonIconSmall { 5 | @include button-small-icon(); 6 | } 7 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/_Checkbox.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_inputs.scss"; 2 | 3 | .Checkbox { 4 | display: inline-block; 5 | input[type="checkbox"] { 6 | @include checkbox(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_scss/w98/index.scss: -------------------------------------------------------------------------------- 1 | @import "./_theme"; 2 | @import "./buttons/index"; 3 | @import "./menu/index"; 4 | @import "./inputs/index"; 5 | @import "./icons/index"; 6 | @import "./task-bar"; 7 | @import "./window"; 8 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | 5 | ratings: 6 | paths: 7 | - src/** 8 | 9 | exclude_paths: 10 | - .out/** 11 | - .storybook/** 12 | - build/** 13 | - src/**/*.spec.js 14 | -------------------------------------------------------------------------------- /src/components/FormSelectBoxSimple/_SelectMultipleSimple.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_inputs.scss"; 2 | 3 | .SelectMultipleSimple { 4 | select[multiple] { 5 | @include select-multiple-simple(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | insert_newline_at_end_of_file = true 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/components/ExplorerView/styles/ExplorerView.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["ExplorerView.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEF;EACE;EACA","file":"ExplorerView.css"} -------------------------------------------------------------------------------- /src/_scss/w98/mixins/box-shadows.scss: -------------------------------------------------------------------------------- 1 | @import "../var/colors"; 2 | @import "../functions/box-shadows"; 3 | 4 | @mixin shadow-input { 5 | box-shadow: inset -1px -1px 0px #ffffff, inset 1px 1px 0px 0px $darkgrey, 6 | inset -2px -2px 0px $grey, inset 2px 2px 0px 0px $black; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/FormRadio/Radio.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Toggle from '../FormToggle/Toggle'; 3 | 4 | import './_Radio.scss'; 5 | 6 | const Radio = props => ; 7 | 8 | Radio.propTypes = Toggle.propTypes; 9 | 10 | export default Radio; 11 | -------------------------------------------------------------------------------- /src/components/ButtonIconLarge/_ButtonIconLarge.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_buttons"; 2 | 3 | .ButtonIconLarge { 4 | @include button-large-icon; 5 | &__icon { 6 | flex-grow: 1; 7 | width: 20px; 8 | height: 20px; 9 | margin: 1px auto 2px; 10 | align-content: center; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/_scss/w98/var/colors.scss: -------------------------------------------------------------------------------- 1 | $blue: #0000a2; 2 | $grey: #bbc3c4; 3 | $black: #0c0c0c; 4 | $darkgrey: #808088; 5 | 6 | // stripe 7 | $red: #fb0006; 8 | $yellow: #ffff09; 9 | $lightgreen: #22ff04; 10 | $lightblue: #21ffff; 11 | 12 | $folderYellow: #ffff09; 13 | $folderInnerBorder: #a2a44f; 14 | $folderOuterBorder: #000000; 15 | -------------------------------------------------------------------------------- /src/components/StartMenu/StartMenu.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import StartMenu from './StartMenu'; 4 | 5 | describe('StartMenu', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('.StartMenu').length).toBeTruthy(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/IconListIcon/ListIcon.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import ListIcon from './IconListIcon'; 4 | 5 | describe('ListIcon', () => { 6 | it('uses AbstractIcon', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('AbstractIcon').length).toBeTruthy(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /rename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # find all files either in /data or /data/subdir 4 | find .storybook/stories -type f -maxdepth 5 | while read file; do 5 | if [[ "$file" =~ \.js$ ]]; 6 | then 7 | echo $file; 8 | mv $file ${file}x 9 | fi 10 | done 11 | # find /data/ -type f -print0 | while read -d $'\0' file; do 12 | # echo "Processing $file" 13 | # done -------------------------------------------------------------------------------- /src/components/ExplorerView/styles/ExplorerView.scss: -------------------------------------------------------------------------------- 1 | .ExplorerView { 2 | display: flex; 3 | flex-flow: column wrap; 4 | height: 100%; 5 | width: 100%; 6 | align-content: flex-start; 7 | 8 | &--fixed-width { 9 | overflow-y: scroll; 10 | height: initial; 11 | } 12 | &--fixed-height { 13 | overflow-x: scroll; 14 | width: initial; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/IconExplorerIcon/ExplorerIcon.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import ExplorerIcon from './IconExplorerIcon'; 4 | 5 | describe('ExplorerIcon', () => { 6 | it('uses AbstractIcon', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('AbstractIcon').length).toBeTruthy(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/FormSelectDISABLED/README.md: -------------------------------------------------------------------------------- 1 | # Select Dropdown (DISABLED) 2 | 3 | I updated React Select so I could easily update other more important packages. As I don't even use this component in my implementation of this project I'm going to disable it and will look at re-adding it if someone wants it added back or if I need it later. 4 | 5 | It is sort of styled, but there's so much more required 6 | -------------------------------------------------------------------------------- /src/components/StandardMenu/_StandardMenu.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_menu"; 2 | 3 | .StandardMenu { 4 | @include standard-menu(".StandardMenu"); 5 | 6 | @include standard-menu-css(".StandardMenu"); 7 | } 8 | 9 | .StandardMenuItem { 10 | &--empty { 11 | .StandardMenuItem__button { 12 | text-shadow: 1px 1px #ffffff; 13 | text-align: center; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | module.exports = { 3 | verbose: true, 4 | setupFilesAfterEnv: ['./src/setupTests.js'], 5 | collectCoverageFrom: ['src/**/*.js'], 6 | testEnvironment: 'jsdom', 7 | transform: { 8 | '^.+\\.js?$': 'babel-jest', 9 | }, 10 | testURL: 'http://localhost/', 11 | moduleNameMapper: { 12 | '^.+\\.(css|less|scss)$': 'babel-jest', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/_scss/w98/functions/box-shadows.scss: -------------------------------------------------------------------------------- 1 | @import "../var/colors"; 2 | 3 | @function shadow($color, $px1, $px2: "") { 4 | @if ($px2 == "") { 5 | @return inset $px1 * 1px $px1 * 1px 0px $color; 6 | } @else { 7 | @return inset $px1 * 1px $px2 * 1px 0px $color; 8 | } 9 | } 10 | 11 | @function dualShadow($color1, $color2, $px: 1) { 12 | @return shadow($color2, -$px), shadow($color1, $px); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/ExplorerView/styles/ExplorerView.css: -------------------------------------------------------------------------------- 1 | .ExplorerView { 2 | display: flex; 3 | flex-flow: column wrap; 4 | height: 100%; 5 | width: 100%; 6 | align-content: flex-start; 7 | } 8 | .ExplorerView--fixed-width { 9 | overflow-y: scroll; 10 | height: initial; 11 | } 12 | .ExplorerView--fixed-height { 13 | overflow-x: scroll; 14 | width: initial; 15 | } 16 | 17 | /*# sourceMappingURL=ExplorerView.css.map */ 18 | -------------------------------------------------------------------------------- /src/components/FormRadio/Radio.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Radio from './Radio'; 4 | 5 | describe('Radio', () => { 6 | it('uses toggle with radio type', () => { 7 | const wrapper = shallow(); 8 | expect( 9 | wrapper 10 | .find('Toggle') 11 | .at(0) 12 | .props().type 13 | ).toBe('radio'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import Toggle from '../FormToggle/Toggle'; 4 | 5 | import './_Checkbox.scss'; 6 | 7 | const Checkbox = props => ( 8 | 13 | ); 14 | 15 | Checkbox.propTypes = Toggle.propTypes; 16 | 17 | export default Checkbox; 18 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/Checkbox.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Checkbox from './Checkbox'; 4 | 5 | describe('Checkbox', () => { 6 | it('uses toggle with radio type', () => { 7 | const wrapper = shallow(); 8 | expect( 9 | wrapper 10 | .find('Toggle') 11 | .at(0) 12 | .props().type 13 | ).toBe('checkbox'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", {"modules": false}], 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-object-rest-spread", 8 | "@babel/plugin-proposal-class-properties" 9 | ], 10 | "env": { 11 | "test": { 12 | "presets": ["@babel/preset-env", "@babel/preset-react"], 13 | "plugins": [ 14 | "@babel/plugin-proposal-class-properties" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Frame/Frame.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import './_Frame.scss'; 5 | 6 | const WindowFrame = props => ( 7 |
8 | {props.children} 9 |
10 | ); 11 | 12 | WindowFrame.propTypes = { 13 | children: PropTypes.node, 14 | className: PropTypes.string, 15 | }; 16 | 17 | export default WindowFrame; 18 | -------------------------------------------------------------------------------- /src/components/Theme.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import '../_scss/w98/theme.scss'; 5 | 6 | const Theme = props => ( 7 |
8 | {props.children} 9 |
10 | ); 11 | 12 | Theme.propTypes = { 13 | children: PropTypes.node, 14 | className: PropTypes.string, 15 | style: PropTypes.shape(), 16 | }; 17 | 18 | export default Theme; 19 | -------------------------------------------------------------------------------- /.storybook/preview.jsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import React from 'react'; 4 | import Theme from '../src/components/Theme'; 5 | 6 | export const decorators = [ 7 | (Story) => ( 8 | 18 | 19 | 20 | ) 21 | ] -------------------------------------------------------------------------------- /src/components/FormFakeSelect/FakeSelect.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import './_FakeSelect.scss'; 4 | 5 | const FakeSelect = props => ( 6 |
7 | {props.icon && } 8 |
{props.title}
9 |
10 |
11 | ); 12 | 13 | export default FakeSelect; 14 | -------------------------------------------------------------------------------- /src/components/ButtonNav/ButtonNav.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import Button, { commonButtonPropTypes } from '../Button'; 4 | 5 | import './_ButtonNav.scss'; 6 | 7 | const ButtonNav = props => ( 8 | 16 | ); 17 | 18 | Button.propTypes = { 19 | ...commonButtonPropTypes, 20 | }; 21 | 22 | export default ButtonForm; 23 | -------------------------------------------------------------------------------- /src/components/TaskBar/Notifier.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Notifier = props => ( 5 | 17 | ); 18 | 19 | ButtonProgram.propTypes = { 20 | ...commonButtonPropTypes, 21 | icon: PropTypes.any, 22 | }; 23 | 24 | export default ButtonProgram; 25 | -------------------------------------------------------------------------------- /src/components/TaskBar/__tests__/Notifier.spec.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { mount } from 'enzyme'; 4 | import Notifier from '../Notifier'; 5 | 6 | const options = (onClick = jest.fn()) => ([ 7 | { alt: 'open', onClick, title: 'testButton' }, 8 | { alt: 'find', onClick, options: 'testOption' }, 9 | ]); 10 | 11 | describe('Notifier', () => { 12 | const func = jest.fn(); 13 | const wrapper = mount(); 14 | it('renders', () => { 15 | expect(wrapper.find('.Notifier').length).toBeTruthy(); 16 | }); 17 | 18 | it('can be clicked', () => { 19 | wrapper.simulate('click'); 20 | expect(func).toHaveBeenCalled(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/components/Frame/Frame.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import WindowFrame from './Frame'; 4 | 5 | const options = (onClick = jest.fn()) => [ 6 | { alt: 'open', onClick, title: 'testButton' }, 7 | { alt: 'find', onClick, options: 'testOption' }, 8 | ]; 9 | 10 | describe('WindowFrame', () => { 11 | const func = jest.fn(); 12 | const wrapper = shallow( 13 | 14 | ); 15 | 16 | it('renders', () => { 17 | expect(wrapper.find('.Frame').length).toBeTruthy(); 18 | }); 19 | 20 | it('accepts classes and props', () => { 21 | wrapper.setProps({ className: 'test' }); 22 | expect(wrapper.render().hasClass('test')).toBe(true); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | parser: 'babel-eslint', 7 | extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:jest/recommended', 'plugin:storybook/recommended'], 8 | parserOptions: { 9 | ecmaFeatures: { 10 | experimentalObjectRestSpread: true, 11 | jsx: true 12 | }, 13 | sourceType: 'module' 14 | }, 15 | rules: { 16 | indent: ['error', 2], 17 | 'linebreak-style': ['error', 'unix'], 18 | quotes: ['error', 'single'], 19 | semi: ['error', 'always'], 20 | 'comma-dangle': ['error', 'always-multiline'], 21 | 'react/prop-types': [0] // imported props fail 22 | }, 23 | settings: { 24 | react: { 25 | version: "detect" 26 | } 27 | } 28 | }; -------------------------------------------------------------------------------- /src/components/ButtonIconSmall/ButtonIconSmall.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import Button, { commonButtonPropTypes } from '../Button'; 5 | 6 | import './_ButtonIconSmall.scss'; 7 | 8 | const ButtonIconSmall = props => ( 9 | 20 | ); 21 | 22 | ButtonIconSmall.propTypes = { 23 | ...commonButtonPropTypes, 24 | icon: PropTypes.string, 25 | }; 26 | 27 | export default ButtonIconSmall; 28 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const main = { 2 | framework: { 3 | name: '@storybook/react-webpack5', 4 | options: { fastRefresh: true }, 5 | }, 6 | stories: [ 7 | './stories/taskbar.stories.jsx', 8 | './stories/buttons.stories.jsx', 9 | './stories/windows.stories.jsx', 10 | './stories/contextMenu.stories.jsx', 11 | './stories/icons.stories.jsx', 12 | './stories/scrollbar.stories.jsx', 13 | './stories/inputs.stories.jsx', 14 | './stories/start.stories.jsx', 15 | './stories/desktop.stories.jsx', 16 | ], 17 | core: { 18 | builder: { 19 | name: '@storybook/builder-vite', // 👈 The builder enabled here. 20 | options: { 21 | loader: { 22 | '.js': 'jsx', 23 | }, 24 | }, 25 | } 26 | }, 27 | } 28 | 29 | export default main -------------------------------------------------------------------------------- /src/components/ExplorerView/__tests__/ExplorerView.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | shallow, 4 | } from 'enzyme'; 5 | import ExplorerView from '../ExplorerView'; 6 | 7 | describe('ExplorerView', () => { 8 | it('renders', () => { 9 | const wrapper = shallow(); 10 | expect(wrapper.find('.ExplorerView').length).toBeTruthy(); 11 | }); 12 | 13 | it('accepts classes passed in', () => { 14 | const wrapper = shallow( 15 | 20 | ).render(); 21 | expect(wrapper.hasClass('test')).toBe(true); 22 | expect(wrapper.hasClass('ExplorerView--fixed-height')).toBe(true); 23 | expect(wrapper.hasClass('ExplorerView--fixed-width')).toBe(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/ButtonIconLarge/ButtonIconLarge.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import Button, { commonButtonPropTypes } from '../Button/Button'; 5 | 6 | import './_ButtonIconLarge.scss'; 7 | 8 | const ButtonIconLarge = props => ( 9 | 19 | ); 20 | 21 | ButtonIconLarge.propTypes = { 22 | ...commonButtonPropTypes, 23 | icon: PropTypes.string, 24 | title: PropTypes.string, 25 | }; 26 | 27 | export default ButtonIconLarge; 28 | -------------------------------------------------------------------------------- /src/components/FormSelectBoxSimple/SelectMultipleSimple.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import SelectMultipleSimple from './SelectMultipleSimple'; 4 | 5 | describe('SelectMultipleSimple', () => { 6 | it('renders', () => { 7 | const wrapper = shallow( 8 | 9 | ); 10 | expect(wrapper.find('div.SelectMultipleSimple').length).toBeTruthy(); 11 | expect(wrapper.find('option')).toHaveLength(2); 12 | }); 13 | 14 | it('whole box can be disabled', () => { 15 | const wrapper = shallow( 16 | 17 | ); 18 | expect(wrapper.find('select').props().disabled).toBe(true); 19 | }); 20 | 21 | xit('needs more tests, low priority', () => {}); 22 | }); 23 | -------------------------------------------------------------------------------- /src/components/WindowProgram/_WindowProgram.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/var/colors"; 2 | @import "../../_scss/w98/functions/box-shadows"; 3 | @import "../../_scss/w98/mixins/box-shadows"; 4 | @import "../../_scss/w98/var/var"; 5 | 6 | .w98 .WindowProgram { 7 | display: inline-flex; 8 | flex-direction: column; 9 | 10 | > footer { 11 | display: flex; 12 | > div { 13 | white-space: nowrap; 14 | text-overflow: ellipsis; 15 | overflow: hidden; 16 | min-width: 0px; // hack to prevent overflow 17 | flex-grow: 1; 18 | padding: 2px; 19 | height: 12px; 20 | box-shadow: dualShadow($black, #ffffff); 21 | &:not(:last-child) { 22 | margin-right: 2px; 23 | } 24 | &:last-child { 25 | padding-right: 12px; 26 | } 27 | } 28 | } 29 | > div:last-child { 30 | margin-top: 2px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.storybook/stories/scrollbar.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Scrollbar = { 4 | render: () => ( 5 |
6 |
16 |
sdfsadf
17 |
sdfsadf
18 |
19 |
20 | ), 21 | notes: '~Scrollbar only works correctly on Chrome at the moment', 22 | }; 23 | 24 | const meta = { 25 | component: Scrollbar, 26 | } 27 | export default meta; -------------------------------------------------------------------------------- /src/components/MenuBar/MenuBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import Button from '../Button'; 5 | import withMenuWrapper from '../StandardMenuHOC'; 6 | import './_MenuBar.scss'; 7 | 8 | const MenuEntry = withMenuWrapper(Button); 9 | 10 | const MenuBar = props => ( 11 | 12 | {props.options && 13 | props.options.map(section => ( 14 | 19 | {section.title} 20 | 21 | ))} 22 | 23 | ); 24 | 25 | MenuBar.propTypes = { 26 | options: PropTypes.arrayOf(PropTypes.shape()), 27 | className: PropTypes.string, 28 | }; 29 | 30 | export default MenuBar; 31 | -------------------------------------------------------------------------------- /src/components/FormToggle/Toggle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | 5 | const Toggle = props => ( 6 |
7 | 16 | 21 |
22 | ); 23 | 24 | const toggleProps = { 25 | label: PropTypes.string, 26 | type: PropTypes.string, 27 | id: PropTypes.string, 28 | name: PropTypes.string, 29 | checked: PropTypes.bool, 30 | onChange: PropTypes.func, 31 | isDisabled: PropTypes.bool, 32 | }; 33 | 34 | Toggle.propTypes = toggleProps; 35 | 36 | export default Toggle; 37 | -------------------------------------------------------------------------------- /src/components/Window/WindowAbstract.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import WindowAbstract from './Window'; 4 | 5 | describe('WindowAbstract', () => { 6 | const wrapper = shallow( 7 | 8 | ); 9 | it('renders all constants', () => { 10 | expect(wrapper.find('.Window').length).toBeTruthy(); 11 | expect(wrapper.find('.Window__heading').length).toBeTruthy(); 12 | expect(wrapper.find('.Window__title').length).toBeTruthy(); 13 | }); 14 | 15 | it('displays correct heading buttons', () => { 16 | expect(wrapper.find('.Window__close').length).toBeTruthy(); 17 | expect(wrapper.find('.Window__maximize').length).toBeTruthy(); 18 | expect(wrapper.find('.Window__help').length).toBeTruthy(); 19 | wrapper.setState({ maximized: true }); 20 | expect(wrapper.find('.Window__restore').length).toBeTruthy(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/components/WindowProgram/WindowProgram.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | import WindowProgram from '.'; 4 | 5 | const options = (onClick = jest.fn()) => [ 6 | { title: 'testButton', options: [{ title: 'testOption', onClick }] }, 7 | { title: 'test', options: [{ title: 'testOption', onClick }] }, 8 | ]; 9 | 10 | describe('WindowProgram', () => { 11 | const func = jest.fn(); 12 | const wrapper = mount( 13 | 14 |
15 | 16 | ); 17 | it('renders', () => { 18 | expect(wrapper.find('.WindowProgram').length).toBeTruthy(); 19 | }); 20 | it('hasChildren', () => { 21 | expect(wrapper.find('.test').length).toBeTruthy(); 22 | }); 23 | it('has menus', () => { 24 | expect(wrapper.find('.WindowProgram__menu').length).toBeTruthy(); 25 | expect(wrapper.find('.StandardMenuWrapper')).toHaveLength(2); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/components/TaskBar/__tests__/Notifications.spec.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { 4 | mount, 5 | } from 'enzyme'; 6 | import Notifications, { Time } from '../Notifications'; 7 | 8 | const menuOptions = (onClick = jest.fn()) => ([ 9 | { alt: 'open', onClick, title: 'testButton' }, 10 | { alt: 'find', onClick, options: 'testOption' }, 11 | ]); 12 | 13 | describe('Notifications', () => { 14 | const func = jest.fn(); 15 | const options = menuOptions(func); 16 | const wrapper = mount(); 17 | const notifers = wrapper.find('Notifier'); 18 | it('renders', () => { 19 | expect(wrapper.find('.TaskBar__notifications').length).toBeTruthy(); 20 | expect(notifers).toHaveLength(2); 21 | expect(wrapper.find(Time).length).toBeTruthy(); 22 | }); 23 | 24 | it('can be clicked', () => { 25 | notifers.forEach(notifier => notifier.simulate('click')); 26 | expect(func).toHaveBeenCalledTimes(2); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/components/Window/_WindowAbstract.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/_window"; 2 | 3 | .w98 .Window { 4 | &__heading { 5 | @include window-heading(); 6 | } 7 | &__icon { 8 | @include window-icon(); 9 | } 10 | &__title { 11 | @include window-title(); 12 | } 13 | &__close { 14 | margin-left: 2px; 15 | background-image: url($close); 16 | } 17 | &__restore { 18 | background-image: url($restore); 19 | } 20 | &__minimize { 21 | background-image: url($minimize); 22 | } 23 | &__maximize { 24 | background-image: url($maximize); 25 | } 26 | &__help { 27 | background-image: url($whelp); 28 | } 29 | &--resizable { 30 | &:after { 31 | position: absolute; 32 | bottom: 4px; 33 | right: 4px; 34 | height: 12px; 35 | width: 12px; 36 | content: ""; 37 | background-image: url($resize); 38 | } 39 | } 40 | &--maximized { 41 | @include window-maximized(); 42 | } 43 | 44 | &--drag { 45 | @include window-drag(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/ExplorerView/ExplorerView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import './styles/ExplorerView.scss'; 5 | 6 | const ExplorerView = props => ( 7 |
22 | { props.children } 23 |
24 | ); 25 | 26 | ExplorerView.defaultProps = { 27 | style: {}, 28 | }; 29 | 30 | ExplorerView.propTypes = { 31 | children: PropTypes.node, 32 | fixedHeight: PropTypes.bool, 33 | fixedWidth: PropTypes.bool, 34 | className: PropTypes.string, 35 | }; 36 | 37 | export default ExplorerView; 38 | -------------------------------------------------------------------------------- /src/components/FormToggle/Toggle.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import Toggle from './Toggle'; 4 | 5 | describe('Toggle', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('input').length).toBeTruthy(); 9 | }); 10 | 11 | it('renders div when passed class', () => { 12 | const wrapper = shallow(); 13 | expect(wrapper.find('div').length).toBeTruthy(); 14 | }); 15 | 16 | it('checking states with props', () => { 17 | const wrapper = shallow(); 18 | const wrapperProps = wrapper 19 | .find('input') 20 | .at(0) 21 | .props(); 22 | expect(wrapperProps.disabled).toBe(true); 23 | expect(wrapperProps.checked).toBe(true); 24 | }); 25 | 26 | it('onchange fires', () => { 27 | const clickFunc = jest.fn(); 28 | const wrapper = mount(); 29 | wrapper.find('input').simulate('change'); 30 | expect(clickFunc).toHaveBeenCalled(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 @padraigfl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/components/ButtonNav/NavButton.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import ButtonNav from './ButtonNav'; 4 | 5 | describe('ButtonNav', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('.ButtonNav').length).toBeTruthy(); 9 | }); 10 | 11 | it('No children', () => { 12 | const wrapper = shallow( 13 | 14 |
15 | 16 | ); 17 | expect(wrapper.find('#test').length).toBeFalsy(); 18 | }); 19 | 20 | it('accepts classes passed in', () => { 21 | const wrapper = shallow( 22 | 23 | ).render(); 24 | expect(wrapper.hasClass('test')).toBe(true); 25 | expect(wrapper.hasClass('btn--active')).toBe(true); 26 | expect(wrapper.hasClass('btn--disabled')).toBe(true); 27 | }); 28 | 29 | it('onclick fires prop', () => { 30 | const clickFunc = jest.fn(); 31 | const wrapper = mount(); 32 | wrapper.simulate('click'); 33 | expect(clickFunc).toHaveBeenCalled(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/ButtonIconSmall/SmallIconButton.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import ButtonIconSmall from './ButtonIconSmall'; 4 | 5 | describe('ButtonIconSmall', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('.ButtonIconSmall').length).toBeTruthy(); 9 | }); 10 | 11 | it('renders icon', () => { 12 | const wrapper = mount(); 13 | expect(wrapper.find('img').props().src).toBe('TEST'); 14 | }); 15 | 16 | it('accepts classes passed in', () => { 17 | const wrapper = shallow( 18 | 19 | ).render(); 20 | expect(wrapper.hasClass('test')).toBe(true); 21 | expect(wrapper.hasClass('btn--active')).toBe(true); 22 | expect(wrapper.hasClass('btn--disabled')).toBe(true); 23 | }); 24 | 25 | it('onclick focuses and fires prop', () => { 26 | const clickFunc = jest.fn(); 27 | const wrapper = mount(); 28 | wrapper.simulate('click'); 29 | expect(clickFunc).toHaveBeenCalled(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/components/ButtonStart/ButtonStart.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import StartButton from './ButtonStart'; 4 | 5 | describe('StartButton', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('.StartButton').length).toBeTruthy(); 9 | }); 10 | 11 | it('accepts classes passed in', () => { 12 | const wrapper = shallow( 13 | 14 | ).render(); 15 | expect(wrapper.hasClass('test')).toBe(true); 16 | expect(wrapper.hasClass('btn--active')).toBe(true); 17 | expect(wrapper.hasClass('btn--disabled')).toBe(false); 18 | }); 19 | 20 | it('onclick fires prop', () => { 21 | const clickFunc = jest.fn(); 22 | const wrapper = mount(); 23 | wrapper.simulate('click'); 24 | expect(clickFunc).toHaveBeenCalled(); 25 | }); 26 | 27 | it('onblur fires prop', () => { 28 | const clickFunc = jest.fn(); 29 | const wrapper = mount(); 30 | wrapper.simulate('blur'); 31 | expect(clickFunc).toHaveBeenCalled(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/components/FormFakeSelect/_FakeSelect.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/var/colors"; 2 | @import "../../_scss/w98/mixins/box-shadows"; 3 | @import "../../_scss/w98/functions/box-shadows"; 4 | @import "../../_scss/w98/var/var"; 5 | @import "../../_scss/w98/var/uris"; 6 | 7 | $select-height: 22px; 8 | 9 | .FakeSelect { 10 | position: relative; 11 | display: flex; 12 | height: $select-height; 13 | align-self: center; 14 | align-items: center; 15 | background-color: #ffffff; 16 | overflow: hidden; 17 | @include shadow-input; 18 | &__icon { 19 | margin-left: 6px; 20 | height: 16px; 21 | } 22 | &__children { 23 | margin-left: 6px; 24 | margin-right: 28px; 25 | white-space: nowrap; 26 | overflow: hidden; 27 | text-overflow: ellipsis; 28 | } 29 | 30 | &__arrow { 31 | position: absolute; 32 | box-shadow: dualShadow($grey, $black), dualShadow(#ffffff, $darkgrey, 2); 33 | height: $select-height - 4; 34 | width: $select-height - 4; 35 | left: calc(100% - 20px); 36 | top: 2px; 37 | background-color: $grey; 38 | background-repeat: no-repeat; 39 | background-position: center; 40 | background-image: url($arrow-down); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # customs 62 | **/.DS_Store 63 | /.nyc_output/ 64 | /.vscode/ 65 | /.cache/ 66 | /.out/ 67 | .out -------------------------------------------------------------------------------- /src/components/ButtonForm/FormButton.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import ButtonForm from './ButtonForm'; 4 | 5 | describe('ButtonForm', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('.ButtonForm').length).toBeTruthy(); 9 | }); 10 | 11 | it('renders children', () => { 12 | const wrapper = shallow( 13 | 14 |
15 | 16 | ); 17 | expect(wrapper.find('#test').length).toBeTruthy(); 18 | }); 19 | 20 | it('accepts classes passed in', () => { 21 | const wrapper = shallow( 22 | 23 | ).render(); 24 | expect(wrapper.hasClass('test')).toBe(true); 25 | expect(wrapper.hasClass('btn--active')).toBe(true); 26 | expect(wrapper.hasClass('btn--disabled')).toBe(true); 27 | }); 28 | 29 | it('onclick focuses and fires prop', () => { 30 | const clickFunc = jest.fn(); 31 | const testString = 'TestString'; 32 | const wrapper = mount( 33 | {testString} 34 | ); 35 | wrapper.simulate('click'); 36 | expect(document.activeElement.innerHTML).toBe(testString); 37 | expect(clickFunc).toHaveBeenCalled(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/components/TaskBar/__tests__/TaskBar.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | import TaskBar from '../TaskBar'; 4 | 5 | const menuOptions = (onClick = jest.fn()) => [ 6 | { alt: 'open', onClick, title: 'testButton', icon: 'icon1' }, 7 | { alt: 'find', onClick, title: 'testOption', icon: 'icon2' }, 8 | ]; 9 | 10 | describe('TaskBar', () => { 11 | const quickLaunchFunc = jest.fn(); 12 | const notiferFunc = jest.fn(); 13 | const programFunc = jest.fn(); 14 | const wrapper = mount( 15 | 21 | ); 22 | it('renders all as expected', () => { 23 | expect(wrapper.find('.TaskBar').length).toBeTruthy(); 24 | expect(wrapper.find('.TaskBar__quick-launch').length).toBeTruthy(); 25 | expect(wrapper.find('Notifier')).toHaveLength(2); 26 | expect(wrapper.find('ButtonProgram')).toHaveLength(2); 27 | expect(wrapper.find('ButtonIconSmall')).toHaveLength(2); 28 | }); 29 | 30 | it('only renders quicklaunch holder when passed props', () => { 31 | wrapper.setProps({ quickLaunch: null }); 32 | expect(wrapper.find('.TaskBar__quick-launch').length).toBeFalsy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/components/ButtonIconLarge/LargeIconButton.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import ButtonIconLarge from './ButtonIconLarge'; 4 | 5 | describe('ButtonIconLarge', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('.ButtonIconLarge').length).toBeTruthy(); 9 | }); 10 | 11 | it('renders icon', () => { 12 | const wrapper = mount(); 13 | expect(wrapper.find('img').props().src).toBe('TEST'); 14 | expect( 15 | wrapper 16 | .find('AbstractButton') 17 | .at(0) 18 | .text() 19 | .includes('TEST') 20 | ).toBeTruthy(); 21 | }); 22 | 23 | it('accepts classes passed in', () => { 24 | const wrapper = shallow( 25 | 26 | ).render(); 27 | expect(wrapper.hasClass('test')).toBe(true); 28 | expect(wrapper.hasClass('btn--active')).toBe(false); 29 | expect(wrapper.hasClass('btn--disabled')).toBe(true); 30 | }); 31 | 32 | it('onclick focuses and fires prop', () => { 33 | const clickFunc = jest.fn(); 34 | const wrapper = mount(); 35 | wrapper.simulate('click'); 36 | expect(clickFunc).toHaveBeenCalled(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/components/FormInputText/InputText.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import InputText from './InputText'; 4 | 5 | describe('InputText', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect( 9 | wrapper 10 | .find('input') 11 | .at(0) 12 | .props().type 13 | ).toBe('text'); 14 | }); 15 | 16 | it('renders passed class name', () => { 17 | const wrapper = shallow(); 18 | expect(wrapper.find('input').hasClass('InputText')).toBeTruthy(); 19 | }); 20 | 21 | it('disabled field', () => { 22 | const wrapper = shallow(); 23 | const wrapperProps = wrapper 24 | .find('input') 25 | .at(0) 26 | .props(); 27 | expect(wrapperProps.disabled).toBe(true); 28 | }); 29 | 30 | it('onchange fires', () => { 31 | const clickFunc = jest.fn(); 32 | const wrapper = mount(); 33 | wrapper.find('input').simulate('change'); 34 | expect(clickFunc).toHaveBeenCalled(); 35 | }); 36 | it('onblur fires', () => { 37 | const clickFunc = jest.fn(); 38 | const wrapper = mount(); 39 | wrapper.find('input').simulate('blur'); 40 | expect(clickFunc).toHaveBeenCalled(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/components/WindowAlert/WindowAlert.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import WindowAbstract from '../Window/Window'; 5 | import ButtonForm from '../ButtonForm'; 6 | 7 | import './_WindowAlert.scss'; 8 | 9 | const WindowAlert = props => ( 10 | 17 |
21 | {props.children} 22 |
23 |
24 | {props.onOK && ( 25 | 26 | OK 27 | 28 | )} 29 | {props.onCancel && ( 30 | 31 | Cancel 32 | 33 | )} 34 |
35 |
36 | ); 37 | 38 | WindowAlert.propTypes = { 39 | ...WindowAbstract.propTypes, 40 | onOK: PropTypes.func, 41 | onCancel: PropTypes.func, 42 | children: PropTypes.node, 43 | icon: PropTypes.string, 44 | }; 45 | 46 | export default WindowAlert; 47 | -------------------------------------------------------------------------------- /src/components/WindowAction/_styles.scss: -------------------------------------------------------------------------------- 1 | .WindowAction { 2 | width: 80%; 3 | max-width: 350px; 4 | min-width: 280px; 5 | 6 | > :not(:first-child) { 7 | margin: 4px 5px; 8 | width: auto; 9 | } 10 | 11 | .Window__title { 12 | text-transform: capitalize; 13 | } 14 | 15 | &__location { 16 | display: flex; 17 | align-items: center; 18 | margin: 3px; 19 | text-transform: capitalize; 20 | 21 | .btn { 22 | margin-right: 2px; 23 | } 24 | 25 | .FakeSelect { 26 | flex-grow: 1; 27 | margin: auto 4px; 28 | } 29 | } 30 | 31 | &__files { 32 | width: initial; 33 | margin: 5px 3px; 34 | } 35 | 36 | &__footer { 37 | margin: 3px; 38 | display: flex; 39 | } 40 | 41 | &__input { 42 | display: flex; 43 | align-items: center; 44 | .FakeSelect { 45 | margin-left: auto; 46 | width: 75%; 47 | max-width: 180px; 48 | min-width: 140px; 49 | } 50 | .InputText { 51 | margin-left: auto; 52 | width: calc(75% - 6px); 53 | max-width: 174px; 54 | min-width: 136px; 55 | } 56 | } 57 | 58 | &__action-inputs { 59 | flex-grow: 1; 60 | } 61 | 62 | &__input, 63 | .ButtonForm { 64 | margin-top: 4px; 65 | } 66 | 67 | &__action-buttons { 68 | display: inline-flex; 69 | flex-direction: column; 70 | margin-left: 8px; 71 | .btn { 72 | text-transform: capitalize; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.storybook/config.old.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { configure, addDecorator } from '@storybook/react'; 3 | import { withOptions } from '@storybook/addon-options'; 4 | import { withNotes } from '@storybook/addon-notes'; 5 | 6 | import Theme from '../src/components/Theme'; 7 | 8 | addDecorator(story => ( 9 |
10 | 17 | {story()} 18 | 19 |
20 | )); 21 | 22 | addDecorator( 23 | withOptions({ 24 | name: 'Packard-Belle', 25 | url: 'https://github.com/padraigfl/packard-belle?selector', 26 | theme: { 27 | mainBackground: '#bbc3c4', 28 | mainBorder: '2px solid rgba(0,0,0,0)', 29 | mainTextFace: 'monospace', 30 | barFill: 'linear-gradient(to right, #0000a2, #126fc2)', 31 | barTextColor: 'white', 32 | }, 33 | }) 34 | ); 35 | 36 | addDecorator(withNotes); 37 | 38 | function loadStories() { 39 | require('./stories/taskbar.js'); 40 | require('./stories/buttons.js'); 41 | require('./stories/windows.js'); 42 | require('./stories/contextMenu.js'); 43 | require('./stories/icons.js'); 44 | require('./stories/scrollbar.js'); 45 | require('./stories/inputs.js'); 46 | require('./stories/start.js'); 47 | require('./stories/desktop.js'); 48 | } 49 | 50 | configure(loadStories, module); 51 | -------------------------------------------------------------------------------- /src/components/ButtonProgram/ProgramButton.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import ButtonProgram from '../ButtonProgram'; 4 | import AbstractButton from '../Button'; 5 | 6 | describe('ButtonProgram', () => { 7 | it('renders', () => { 8 | const wrapper = shallow(); 9 | expect(wrapper.find('.ButtonProgram').length).toBeTruthy(); 10 | }); 11 | 12 | it('renders children', () => { 13 | const wrapper = shallow( 14 | 15 |
16 | 17 | ); 18 | expect(wrapper.find('#test').length).toBeTruthy(); 19 | }); 20 | 21 | it('accepts classes passed in', () => { 22 | const wrapper = shallow( 23 | 24 | ).render(); 25 | expect(wrapper.hasClass('test')).toBe(true); 26 | expect(wrapper.hasClass('btn--active')).toBe(true); 27 | expect(wrapper.hasClass('btn--disabled')).toBe(false); 28 | }); 29 | 30 | it('onclick focuses and fires prop', () => { 31 | const clickFunc = jest.fn(); 32 | const wrapper = mount(); 33 | wrapper.simulate('click'); 34 | expect(clickFunc).toHaveBeenCalled(); 35 | }); 36 | 37 | it('icon is passed into style', () => { 38 | const wrapper = mount(); 39 | expect(wrapper.find(AbstractButton).props().style.backgroundImage).toBe( 40 | 'url(ICON)' 41 | ); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /.storybook/stories/desktop.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ExplorerView from '../../src/components/ExplorerView'; 3 | import img from './directory_closed.png'; 4 | 5 | const noop = () => { 6 | console.log('run'); 7 | }; 8 | 9 | const options = [ 10 | { 11 | title: 'Test0 With a very very very long name oh wait is this okay', 12 | onClick: noop, 13 | icon: img, 14 | }, 15 | { title: 'Test1', onClick: noop, icon: img }, 16 | { title: 'Test2', onClick: noop, icon: img }, 17 | { title: 'Test3', onClick: noop, icon: img }, 18 | { title: 'Test4', onClick: noop, icon: img }, 19 | { title: 'Test5', onClick: noop, icon: img }, 20 | { title: 'Test6', onClick: noop, icon: img }, 21 | { title: 'Test7', onClick: noop, icon: img }, 22 | { title: 'Test8', onClick: noop, icon: img }, 23 | { title: 'Test9', onClick: noop, icon: img }, 24 | { title: 'TestA', onClick: noop, icon: img }, 25 | { title: 'TestB', onClick: noop, icon: img }, 26 | { title: 'TestC', onClick: noop, icon: img }, 27 | { title: 'TestD', onClick: noop, icon: img }, 28 | { title: 'TestE', onClick: noop, icon: img }, 29 | { title: 'TestF', onClick: noop, icon: img }, 30 | ]; 31 | 32 | const Desktop = { 33 | render: () => ( 34 |
41 | 42 |
43 | ) 44 | } 45 | 46 | const meta = { 47 | component: Desktop, 48 | } 49 | export default meta; -------------------------------------------------------------------------------- /.storybook/stories/start.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import StartMenu from '../../src/components/StartMenu'; 3 | 4 | import img from './directory_closed.png'; 5 | 6 | const noop = () => {}; 7 | 8 | const optionsSample = [ 9 | { 10 | onClick: noop, 11 | title: 'Windows Update', 12 | icon: img, 13 | }, 14 | [ 15 | { 16 | onClick: noop, 17 | title: 'Programs', 18 | icon: img, 19 | options: [ 20 | { 21 | onClick: noop, 22 | title: 'Accessories', 23 | icon: img, 24 | options: [ 25 | { 26 | onClick: noop, 27 | title: 'Notepad?', 28 | icon: img, 29 | }, 30 | { 31 | onClick: noop, 32 | title: 'fly it', 33 | icon: img, 34 | }, 35 | ], 36 | }, 37 | { 38 | onClick: noop, 39 | title: 'open file?', 40 | icon: img, 41 | }, 42 | ] 43 | }, 44 | { 45 | onClick: noop, 46 | title: 'Control Panel', 47 | icon: img, 48 | options: [], 49 | } 50 | ], 51 | { 52 | onClick: noop, 53 | title: 'Shut Down', 54 | icon: img, 55 | }, 56 | ]; 57 | 58 | const Start = { 59 | render: () => ( 60 |
61 | 64 |
65 | ) 66 | }; 67 | 68 | const meta = { 69 | component: Start, 70 | } 71 | export default meta; -------------------------------------------------------------------------------- /src/components/FormSelectDISABLED/Select.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Select from 'react-select'; 3 | import { shallow } from 'enzyme'; 4 | import WSelect from './Select'; 5 | 6 | describe('Select', () => { 7 | it('renders react select', () => { 8 | const wrapper = shallow( 9 | 10 | ); 11 | expect(wrapper.find(Select).length).toBeTruthy(); 12 | }); 13 | 14 | it('renders options', () => { 15 | const options = [{ value: 'abc' }, { value: 'def' }]; 16 | const wrapper = shallow(); 17 | expect( 18 | wrapper 19 | .find(Select) 20 | .at(0) 21 | .props().options 22 | ).toBe(options); 23 | }); 24 | 25 | it('whole box can be disabled', () => { 26 | const wrapper = shallow( 27 | 28 | ); 29 | expect(wrapper.find(Select).props().disabled).toBe(true); 30 | }); 31 | 32 | it('no state value if controlled component', () => { 33 | const wrapper = shallow( 34 | 35 | ); 36 | expect(wrapper.state().value).toBe(null); 37 | }); 38 | 39 | it('state value if controlled component, uses prop value as initial value', () => { 40 | const wrapper = shallow( 41 | 42 | ); 43 | expect(wrapper.state().value).toBe('abc'); 44 | }); 45 | 46 | xit('needs more tests, low priority', () => {}); 47 | }); 48 | -------------------------------------------------------------------------------- /src/components/FormSelectBox/SelectBox.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import SelectBox from './SelectBox'; 4 | 5 | describe('SelectBox', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('div.SelectBox').length).toBeTruthy(); 9 | }); 10 | 11 | it('whole box can be disabled', () => { 12 | const wrapper = shallow(); 13 | expect(wrapper.find('div.SelectBox.disabled').length).toBeTruthy(); 14 | }); 15 | 16 | it('uses passed component for options', () => { 17 | const FakeComp = () =>
; 18 | const wrapper = shallow( 19 | 27 | ); 28 | expect(wrapper.find(FakeComp)).toHaveLength(2); 29 | }); 30 | 31 | it('selected options have is-active class', () => { 32 | const options = [ 33 | { value: 'abc', title: 'def' }, 34 | { value: 'ghi', title: 'jkl' }, 35 | ]; 36 | const wrapper = mount(); 37 | expect(wrapper.find('.is-active')).toHaveLength(0); 38 | 39 | wrapper.setProps({ selected: 'ghi' }); 40 | expect(wrapper.find('.is-active')).toHaveLength(1); 41 | 42 | wrapper.setProps({ selected: ['abc', 'ghi'] }); 43 | expect(wrapper.find('.is-active')).toHaveLength(2); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /rollup.config.babel.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import replace from 'rollup-plugin-replace'; 5 | import postcss from 'rollup-plugin-postcss'; 6 | import image from 'rollup-plugin-img'; 7 | import autoExternal from 'rollup-plugin-auto-external'; 8 | 9 | const moduleOptions = { 10 | name: 'PackardBelle', 11 | globals: { 12 | react: 'React', 13 | 'react-dom': 'ReactDOM', 14 | clone: 'clone', 15 | 'prop-types': 'PropTypes', 16 | classnames: 'classnames', 17 | 'react-select': 'ReactSelect', 18 | }, 19 | sourcemap: true, 20 | }; 21 | 22 | export default { 23 | input: './src/index.js', 24 | 25 | output: [ 26 | { 27 | ...moduleOptions, 28 | file: './build/pb.js', 29 | format: 'umd', 30 | }, 31 | { 32 | ...moduleOptions, 33 | file: 'build/pb.module.js', 34 | format: 'es', 35 | }, 36 | ], 37 | 38 | plugins: [ 39 | image({ 40 | output: './build/images', // default the root 41 | extensions: /\.(png|jpg|jpeg|gif|svg)$/, // support png|jpg|jpeg|gif|svg, and it's alse the default value 42 | limit: 8192, // default 8192(8k) 43 | exclude: 'node_modules/**', 44 | }), 45 | postcss({ 46 | plugins: [], 47 | // modules: true, 48 | }), 49 | resolve({ 50 | extensions: ['.mjs', '.js', '.jsx', '.json', '.node', '.ttf'], 51 | }), 52 | autoExternal(), 53 | babel({ 54 | exclude: 'node_modules/**', 55 | }), 56 | replace({ 57 | 'process.env.NODE_ENV': JSON.stringify('development'), 58 | }), 59 | commonjs(), 60 | ], 61 | 62 | external: ['react', 'react-dom'], 63 | }; 64 | -------------------------------------------------------------------------------- /playroom/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | module.exports = { 3 | Theme: require('../src/components/Theme').default, 4 | ButtonForm: require('../src/components/ButtonForm').default, 5 | ButtonNav: require('../src/components/ButtonNav').default, 6 | ButtonProgram: require('../src/components/ButtonProgram').default, 7 | StartButton: require('../src/components/ButtonStart').default, 8 | ButtonIconLarge: require('../src/components/ButtonIconLarge').default, 9 | ButtonIconSmall: require('../src/components/ButtonIconSmall').default, 10 | StandardMenu: require('../src/components/StandardMenu').default, 11 | ExplorerIcon: require('../src/components/IconExplorerIcon').default, 12 | ListIcon: require('../src/components/IconListIcon').default, 13 | ExplorerView: require('../src/components/ExplorerView').default, 14 | Checkbox: require('../src/components/FormCheckbox').default, 15 | Radio: require('../src/components/FormRadio').default, 16 | InputText: require('../src/components/FormInputText').default, 17 | Select: require('../src/components/FormSelect').default, 18 | FakeSelect: require('../src/components/FormFakeSelect').default, 19 | SelectBox: require('../src/components/FormSelectBox').default, 20 | SelectBoxSimple: require('../src/components/FormSelectBoxSimple').default, 21 | StartMenu: require('../src/components/StartMenu').default, 22 | TaskBar: require('../src/components/TaskBar').default, 23 | Window: require('../src/components/Window').default, 24 | WindowAlert: require('../src/components/WindowAlert').default, 25 | WindowExplorer: require('../src/components/WindowExplorer').default, 26 | WindowProgram: require('../src/components/WindowProgram').default, 27 | DetailsSection: require('../src/components/DetailsSection').default, 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/FormInputText/InputText.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import cx from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import './_InputText.scss'; 6 | 7 | class InputText extends Component { 8 | static defaultProps = { 9 | onChange: () => {}, 10 | onKeyDown: () => {}, 11 | onBlur: () => {}, 12 | onFocus: () => {}, 13 | }; 14 | 15 | state = { 16 | value: this.props.initialValue, 17 | }; 18 | 19 | handleChange = e => { 20 | if (this.props.initialValue) { 21 | this.setState({ 22 | value: e.target.value, 23 | }); 24 | } 25 | 26 | this.props.onChange(e.target.value); 27 | }; 28 | 29 | handleBlur = () => { 30 | this.props.onBlur(this.state.value); 31 | }; 32 | 33 | render() { 34 | return ( 35 | 47 | ); 48 | } 49 | } 50 | 51 | InputText.propTypes = { 52 | className: PropTypes.string, 53 | value: PropTypes.string, 54 | initialValue: PropTypes.string, 55 | isDisabled: PropTypes.bool, 56 | id: PropTypes.string, 57 | name: PropTypes.string, 58 | onBlur: PropTypes.func.isRequired, 59 | onChange: PropTypes.func.isRequired, 60 | onFocus: PropTypes.func.isRequired, 61 | onKeyDown: PropTypes.func.isRequired, 62 | }; 63 | 64 | export default InputText; 65 | -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /src/components/TaskBar/Notifications.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Notifier from './Notifier'; 4 | 5 | const INTERVALS = 20000; 6 | 7 | const formatTime = date => { 8 | let hour = date.getHours(); 9 | let min = date.getMinutes(); 10 | 11 | if (hour < 10) { hour = '0' + hour; } 12 | if (min < 10) { min = '0' + min; } 13 | 14 | return hour+':'+min; 15 | }; 16 | 17 | export class Time extends React.Component { 18 | state = { 19 | time: this.props.time? new Date(this.props.time) : new Date(), 20 | } 21 | 22 | componentDidMount() { 23 | if (!this.props.fixedTime) { 24 | this.timerId = setInterval(() => { 25 | this.getDate(); 26 | }, INTERVALS); 27 | } 28 | } 29 | 30 | componentWillUnmount() { 31 | if(this.timerId) { 32 | clearInterval(this.timerId); 33 | } 34 | } 35 | 36 | getDate() { 37 | this.setState({ time: new Date(this.state.time.getTime() + INTERVALS) }); 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 | { formatTime(this.state.time) } 44 |
45 | ); 46 | } 47 | } 48 | 49 | const Notifications = props => ( 50 |
51 | { 52 | props.notifiers.map( notifier => ( 53 | 59 | )) 60 | } 61 |
63 | ); 64 | 65 | Notifications.propsTypes = { 66 | notifiers: PropTypes.arrayOf(PropTypes.shape(Notifier.propTypes)), 67 | }; 68 | 69 | Notifications.defaultProps = { 70 | notifiers: [], 71 | }; 72 | 73 | export default Notifications; 74 | -------------------------------------------------------------------------------- /src/components/WindowExplorer/WindowExplorer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import WindowProgram from '../WindowProgram'; 4 | import Select from '../FormFakeSelect'; 5 | import OptionsList from '../ResizableIconsList'; 6 | import './_WindowExplorer.scss'; 7 | 8 | class WindowExplorer extends React.Component { 9 | static defaultProps = { 10 | footer: [], 11 | menuOptions: [], 12 | }; 13 | 14 | render() { 15 | const { props } = this; 16 | return ( 17 | 31 | {props.explorerOptions && ( 32 | 36 | )} 37 | 38 |
Address
39 | {props.customSelect ? ( 40 | props.customSelect() 41 | ) : ( 42 | 59 | {props.options.map(option => ( 60 | 70 | ))} 71 | 72 |
73 | ); 74 | } 75 | } 76 | 77 | SelectMultipleSimple.propTypes = { 78 | multiple: PropTypes.bool, 79 | onChange: PropTypes.func, 80 | value: PropTypes.any, 81 | isDisabled: PropTypes.bool, 82 | options: PropTypes.arrayOf( 83 | PropTypes.shape({ 84 | name: PropTypes.string, 85 | value: PropTypes.any, 86 | isDisabled: PropTypes.bool, 87 | }) 88 | ), 89 | }; 90 | 91 | export default SelectMultipleSimple; 92 | -------------------------------------------------------------------------------- /.storybook/stories/taskbar.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Notifications from '../../src/components/TaskBar/Notifications'; 3 | import TaskBar from '../../src/components/TaskBar'; 4 | import img from './directory_closed.png'; 5 | 6 | const noop = () => { 7 | console.log('run'); 8 | }; 9 | 10 | const optionsSample = [ 11 | { 12 | onClick: noop, 13 | title: 'Windows Update', 14 | icon: img, 15 | }, 16 | [ 17 | { 18 | onClick: noop, 19 | title: 'Programs', 20 | icon: img, 21 | options: [ 22 | { 23 | onClick: noop, 24 | title: 'Accessories', 25 | icon: img, 26 | options: [ 27 | { 28 | onClick: noop, 29 | title: 'Notepad?', 30 | icon: img, 31 | }, 32 | { 33 | onClick: noop, 34 | title: 'fly it', 35 | icon: img, 36 | }, 37 | { 38 | onClick: noop, 39 | title: 'Minesweeper', 40 | icon: img, 41 | }, 42 | ], 43 | }, 44 | { 45 | onClick: noop, 46 | title: 'open file?', 47 | icon: img, 48 | }, 49 | ], 50 | }, 51 | { 52 | onClick: noop, 53 | title: 'Control Panel', 54 | icon: img, 55 | }, 56 | ], 57 | [ 58 | { 59 | onClick: noop, 60 | title: 'Shut Down', 61 | icon: img, 62 | }, 63 | ], 64 | ]; 65 | 66 | export const TaskBarStory = { 67 | render: () => ( 68 | 103 | ), 104 | name: 'TaskBar' 105 | } 106 | export const NotificationStory = { 107 | render: () => , 108 | name: 'Notifications', 109 | } 110 | 111 | const meta = { 112 | component: TaskBarStory, 113 | } 114 | export default meta; -------------------------------------------------------------------------------- /.storybook/stories/contextMenu.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContextMenu from '../../src/components/StandardMenu'; 3 | import withMenuWrapper from '../../src/components/StandardMenuHOC'; 4 | //import ContextMenuWrapper from '../../src/components/ContextMenu/ContextMenuWrapper'; 5 | 6 | const noop = () => {}; 7 | 8 | const optionsSample = [ 9 | { 10 | onClick: noop, 11 | title: 'New', 12 | }, 13 | { 14 | onClick: noop, 15 | title: 'Disabled', 16 | isDisabled: true, 17 | }, 18 | [ 19 | { 20 | onClick: noop, 21 | title: 'Open', 22 | options: [ 23 | { 24 | onClick: noop, 25 | title: 'open file?', 26 | }, 27 | { 28 | onClick: noop, 29 | title: 'open drv file?', 30 | }, 31 | { 32 | onClick: noop, 33 | title: 'spin it', 34 | options: [ 35 | { 36 | onClick: noop, 37 | title: 'twist it?', 38 | }, 39 | { 40 | onClick: noop, 41 | title: 'fly it', 42 | }, 43 | ], 44 | }, 45 | ], 46 | }, 47 | ], 48 | { 49 | onClick: noop, 50 | title: 'quit', 51 | }, 52 | ]; 53 | 54 | const MenuWithLogic = withMenuWrapper(); 55 | 56 | const notes = 57 | 'Hover styling currently controlled by javascript (no styling on CSS only version)'; 58 | 59 | export const SingleField = { 60 | render: () => ( 61 | 90 | ), 91 | } 92 | 93 | export const WithChildren = { 94 | render: 95 | () => ( 96 |
97 |

Children display handled via CSS :hover

98 | 99 |

Children display handled via JavaScript wrapper component

100 | 101 |
102 | ), 103 | } 104 | 105 | const meta = { 106 | component: SingleField, 107 | } 108 | export default meta; -------------------------------------------------------------------------------- /src/components/Button/Button.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | 5 | import './_AbstractButton.scss'; 6 | 7 | class AbstractButton extends Component { 8 | state = { 9 | mouseDown: false, 10 | }; 11 | 12 | handleMouse = (func, mouseDown) => { 13 | this.setState({ mouseDown }); 14 | if (func) { 15 | func(); 16 | } 17 | }; 18 | 19 | handleClick = e => { 20 | this.button.focus(); 21 | if (this.props.onClick) { 22 | this.props.onClick(e); 23 | } 24 | }; 25 | 26 | handleBlur = e => { 27 | if (this.props.onBlur) { 28 | this.props.onBlur(e); 29 | } 30 | }; 31 | 32 | handleContextMenu = e => { 33 | e.preventDefault(); 34 | e.stopPropagation(); 35 | this.button.focus(); 36 | if (this.props.onContextMenu) { 37 | this.props.onContextMenu(e); 38 | } 39 | }; 40 | 41 | handleDoubleClick = e => { 42 | if (this.props.onDoubleClick) { 43 | this.props.onDoubleClick(e); 44 | } 45 | }; 46 | 47 | render() { 48 | const { props } = this; 49 | 50 | return ( 51 | 74 | ); 75 | } 76 | } 77 | 78 | export const commonButtonPropTypes = { 79 | children: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), 80 | 81 | title: PropTypes.string, 82 | className: PropTypes.string, 83 | isActive: PropTypes.bool, 84 | isDisabled: PropTypes.bool, 85 | 86 | onBlur: PropTypes.func, 87 | onClick: PropTypes.func, 88 | }; 89 | 90 | AbstractButton.propTypes = { 91 | ...commonButtonPropTypes, 92 | onDoubleClick: PropTypes.func, 93 | onContextMenu: PropTypes.func, 94 | onMouseDown: PropTypes.func, 95 | onMouseUp: PropTypes.func, 96 | style: PropTypes.shape(), // Todo: Needs custom prop 97 | }; 98 | 99 | export default AbstractButton; 100 | -------------------------------------------------------------------------------- /src/components/ResizableIconsList/ResizableIconsList.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import ButtonIconLarge from '../ButtonIconLarge'; 5 | import StandardMenu from '../StandardMenu'; 6 | import './_options-list.scss'; 7 | import { flattenWithDividers } from '../StandardMenu/StandardMenu'; 8 | 9 | class OptionsListDropdown extends Component { 10 | openList = () => { 11 | this.dropdownButton.focus(); 12 | }; 13 | render() { 14 | return ( 15 |
16 |
28 | ); 29 | } 30 | } 31 | 32 | class OptionsList extends Component { 33 | static propTypes = { 34 | options: PropTypes.arrayOf(PropTypes.shape(ButtonIconLarge.propTypes)), 35 | className: PropTypes.string, 36 | }; 37 | state = { 38 | entriesInView: 8, 39 | }; 40 | 41 | ref = React.createRef(); 42 | 43 | checkWidth = () => { 44 | if (!this.ref.current) { 45 | return; 46 | } 47 | const width = this.ref.current.offsetWidth || 200; 48 | const entriesInView = (width - 20) / 50; 49 | if (this.state.entriesInView !== entriesInView) { 50 | this.setState({ entriesInView }); 51 | } 52 | }; 53 | 54 | render() { 55 | const { props, state } = this; 56 | const options = flattenWithDividers(props.options); 57 | return ( 58 | this.checkWidth()} 61 | className={cx(props.className, 'OptionsList')} 62 | > 63 |
64 | {options.slice(0, state.entriesInView).map(option => { 65 | if (option.includes && option.includes('divider')) { 66 | return
; 67 | } 68 | return ( 69 | this.setState({ rand: Math.random() })} 74 | isDisabled={!option.onClick} 75 | /> 76 | ); 77 | })} 78 |
79 | {props.options.slice(state.entriesInView).length > 0 && ( 80 | 83 | )} 84 |
85 | ); 86 | } 87 | } 88 | 89 | export default OptionsList; 90 | -------------------------------------------------------------------------------- /src/components/Button/Button.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import AbstractButton from './Button'; 4 | 5 | describe('AbstractButton', () => { 6 | it('renders', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper.find('button').length).toBeTruthy(); 9 | }); 10 | 11 | it('renders children', () => { 12 | const wrapper = shallow( 13 | 14 |
15 | 16 | ); 17 | expect(wrapper.find('#test').length).toBeTruthy(); 18 | }); 19 | 20 | it('accepts classes passed in', () => { 21 | const wrapper = shallow( 22 | 23 | ).render(); 24 | expect(wrapper.hasClass('test')).toBe(true); 25 | expect(wrapper.hasClass('btn--active')).toBe(true); 26 | expect(wrapper.hasClass('btn--disabled')).toBe(true); 27 | }); 28 | 29 | it('onclick focuses and fires prop', () => { 30 | const clickFunc = jest.fn(); 31 | const testString = 'TestString'; 32 | const wrapper = mount( 33 | {testString} 34 | ); 35 | wrapper.simulate('click'); 36 | expect(document.activeElement.innerHTML).toBe(testString); 37 | expect(clickFunc).toHaveBeenCalled(); 38 | }); 39 | 40 | it('onMouseDown onMouseUp', () => { 41 | const wrapper = shallow(); 42 | wrapper.simulate('mousedown'); 43 | expect(wrapper.state().mouseDown).toBe(true); 44 | wrapper.simulate('mouseup'); 45 | expect(wrapper.state().mouseDown).toBe(false); 46 | }); 47 | 48 | it('onDoubleClick if passed in as prop', () => { 49 | const dblClck = jest.fn(); 50 | const wrapper = shallow(); 51 | wrapper.simulate('doubleclick'); 52 | wrapper.setProps({ onDoubleClick: dblClck }); 53 | wrapper.simulate('doubleclick'); 54 | expect(dblClck).toHaveBeenCalled(); 55 | }); 56 | 57 | it('onBlur if passed in as prop', () => { 58 | const onBlur = jest.fn(); 59 | const wrapper = shallow(); 60 | wrapper.simulate('blur'); 61 | wrapper.setProps({ onBlur }); 62 | wrapper.simulate('blur'); 63 | expect(onBlur).toHaveBeenCalled(); 64 | }); 65 | 66 | it('onContextMenu on works if passed in as prop', () => { 67 | const mockEvent = { 68 | preventDefault: jest.fn(), 69 | stopPropagation: jest.fn(), 70 | }; 71 | const onContextMenu = jest.fn(); 72 | const wrapper = mount(); 73 | wrapper.simulate('contextmenu'); 74 | expect(onContextMenu).not.toHaveBeenCalled(); 75 | 76 | wrapper.setProps({ onContextMenu }); 77 | wrapper.simulate('contextmenu', mockEvent); 78 | expect(onContextMenu).toHaveBeenCalled(); 79 | expect(mockEvent.preventDefault).toHaveBeenCalled(); 80 | expect(mockEvent.stopPropagation).toHaveBeenCalled(); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/components/Icon/Icon.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | 5 | class AbstractIcon extends Component { 6 | state = { 7 | doubleReady: false, 8 | }; 9 | 10 | disableAction = () => { 11 | this.setState({ doubleReady: false }); 12 | }; 13 | 14 | checkDoubleClick = () => { 15 | this.handleClick(); 16 | 17 | if (!this.props.onDoubleClick) { 18 | return; 19 | } 20 | 21 | if (this.state.doubleReady) { 22 | this.props.onDoubleClick(); 23 | this.disableAction(); 24 | } else { 25 | this.setState({ doubleReady: true }); 26 | setTimeout(this.disableAction, 700); 27 | } 28 | }; 29 | 30 | handleClick = e => { 31 | this.icon.focus(); 32 | if (this.props.onClick) { 33 | this.props.onClick(e); 34 | } 35 | }; 36 | 37 | handleContextMenu = e => { 38 | e.preventDefault(); 39 | this.icon.focus(); 40 | if (this.props.onContextMenu) { 41 | this.props.onContextMenu(e); 42 | } 43 | //return false; 44 | }; 45 | 46 | render() { 47 | const { props } = this; 48 | const Comp = props.href ? 'a' : 'button'; 49 | 50 | const iconProps = { 51 | onClick: this.checkDoubleClick, 52 | onContextMenu: this.props.onContextMenu && this.handleContextMenu, 53 | onTouchEnd: this.props.onDoubleClick || this.props.onClick, 54 | className: cx('icon', props.className), 55 | title: props.alt, 56 | value: props.value, 57 | ref: icon => { 58 | this.icon = icon; 59 | }, 60 | href: props.href, 61 | }; 62 | 63 | const contents = ( 64 | <> 65 |
69 |
{props.title}
70 | 71 | ); 72 | 73 | if (this.props.onClick || this.props.onDoubleClick) { 74 | return ( 75 | { 77 | this.icon = icon; 78 | }} 79 | target={props.external && Comp === 'a' && '_blank'} 80 | rel={props.external && Comp === 'a' && 'noopener noreferrer'} 81 | {...iconProps} 82 | > 83 | {contents} 84 | 85 | ); 86 | } 87 | return
{contents}
; 88 | } 89 | } 90 | 91 | export const iconProps = { 92 | title: PropTypes.string, 93 | icon: PropTypes.string, 94 | children: PropTypes.node, 95 | className: PropTypes.string, 96 | alt: PropTypes.string, 97 | value: PropTypes.any, 98 | onClick: PropTypes.func, 99 | onDoubleClick: PropTypes.func, 100 | onContextMenu: PropTypes.func, 101 | href: PropTypes.string, 102 | external: PropTypes.bool, 103 | }; 104 | 105 | AbstractIcon.propTypes = iconProps; 106 | 107 | export default AbstractIcon; 108 | -------------------------------------------------------------------------------- /src/components/WindowExplorer/_WindowExplorer.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/var/colors"; 2 | @import "../../_scss/w98/functions/box-shadows"; 3 | @import "../../_scss/w98/mixins/box-shadows"; 4 | @import "../../_scss/w98/var/var"; 5 | 6 | @function nav-box-shadow() { 7 | @return inset 0px -1px 0px $darkgrey, inset -1px 0px 0px $darkgrey, 8 | inset 0px 0px 0px 1px #ffffff, -1px 0px 0px $darkgrey, 1px 0px 0px #ffffff, 9 | -1px 1px 0px 0px #ffffff, 1px 1px 0px 0px #ffffff; 10 | } 11 | 12 | .w98 .WindowExplorer { 13 | display: inline-flex; 14 | flex-direction: column; 15 | &__view { 16 | min-height: 20px; 17 | margin: 2px 0px; 18 | flex-grow: 1; 19 | background-color: #ffffff; 20 | @include shadow-input; 21 | } 22 | &__details { 23 | display: flex; 24 | &__section { 25 | box-shadow: inset -1px -1px 0px #ffffff, inset 1px 1px 0px $darkgrey; 26 | flex-grow: 1; 27 | margin-top: 2px; 28 | height: 16px; 29 | 30 | &:not(:last-child) { 31 | margin: 2px; 32 | } 33 | } 34 | } 35 | 36 | .window__menu { 37 | padding: 2px 2px 2px 12px; 38 | } 39 | > div + menu { 40 | margin-top: 2px; 41 | box-shadow: 0px 2px 0px -1px #ffffff, -1px 2px 0px -1px #ffffff, 42 | -1px 1px 0px $darkgrey, 0px 1px 0px $darkgrey, nav-box-shadow(), 43 | -1px -1px 0px $darkgrey, 0px -1px 0px $darkgrey, inset 0px 1px 0px #ffffff, 44 | 1px -1px 0px #ffffff; 45 | } 46 | > menu { 47 | position: relative; 48 | min-height: 22px; 49 | padding-left: 12px; 50 | margin: 0px 1px; 51 | box-shadow: nav-box-shadow(); 52 | &:before { 53 | position: absolute; 54 | top: 3px; 55 | left: 5px; 56 | height: calc(100% - 6px); 57 | width: 3px; 58 | background-color: $grey; 59 | content: ""; 60 | box-shadow: dualShadow(#ffffff, $darkgrey); 61 | } 62 | &.OptionsList { 63 | min-height: 40px; 64 | display: block; 65 | } 66 | } 67 | > footer { 68 | display: flex; 69 | > div { 70 | white-space: nowrap; 71 | text-overflow: ellipsis; 72 | overflow: hidden; 73 | min-width: 0px; // hack to prevent overflow 74 | flex-grow: 1; 75 | padding: 2px; 76 | height: 12px; 77 | box-shadow: dualShadow($black, #ffffff); 78 | &:not(:last-child) { 79 | margin-right: 2px; 80 | } 81 | &:last-child { 82 | padding-right: 12px; 83 | } 84 | } 85 | } 86 | &__address { 87 | display: flex; 88 | height: 26px; 89 | overflow-y: visible; 90 | user-select: none; 91 | &__title { 92 | align-self: center; 93 | margin-right: 4px; 94 | } 95 | .FakeSelect { 96 | flex-grow: 1; 97 | z-index: 5; 98 | margin-right: 4px; 99 | } 100 | } 101 | &__options { 102 | display: flex; 103 | padding: 2px 8px 2px 12px; 104 | } 105 | > div:last-child { 106 | margin-top: 2px; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Todo Lists 2 | 3 | # Critical 4 | 5 | - [x] get rid of node-sass 6 | - [x] fix storybook build issues 7 | - [x] fix css file build issues 8 | - [x] mass upgrade of key dependencies 9 | - [ ] either fix tests or delete them 10 | - [ ] remove unused things such as playroom and arguably stylelint 11 | 12 | # Initial UI Todo List 13 | 14 | Abstract 15 | 16 | - [x] Standard colors 17 | - [x] Borders 18 | - [x] Button 19 | - [x] Actions list 20 | - [x] Nested actions list 21 | - [x] Icons in action list 22 | - [x] Cursor 23 | 24 | Forms/etc 25 | 26 | - [x] Form button 27 | - [x] Section dividers 28 | - [x] Radio buttons 29 | - [x] Checkbox 30 | - [x] Text input 31 | - [x] Select (using react-select) 32 | - [x] Select multiple 33 | - [x] Disabled states for inputs 34 | - [ ] Tabs 35 | - [ ] Alert/dialog text handling 36 | 37 | W98 Toolbar 38 | 39 | - [x] Start Button 40 | - [x] Notifications + time 41 | - [x] Bar and dividers 42 | - [x] Quick Launch 43 | - [x] Active windows 44 | - [x] Start menu 45 | 46 | Explorer/Program 47 | 48 | - [x] Heading 49 | - [x] Standard view icons 50 | - [x] close/minimize/restore/help 51 | - [x] File/Edit/etc toolbar 52 | - [x] Options (needs sections, resize handling and see more icons) 53 | - [x] Explorer input (cosmetic only) 54 | - [ ] Explorer input (working) 55 | - [ ] Status footer (partially done, logic not figured out) 56 | - [ ] Explorer views (?) 57 | 58 | ## Interactive 59 | 60 | General 61 | 62 | - [ ] Right click actions 63 | - [ ] State sharing 64 | - [ ] Loading 65 | - [x] ~Shut down~ 66 | - [x] ~Font substitution~ 67 | - [x] ~Scaling display size~ 68 | - [ ] Make CSS pseudoclass driven design choices optional 69 | 70 | W98 Toolbar 71 | 72 | - [x] Interactive start menu, validated inputs 73 | - [x] ~Only one active window~ 74 | - [ ] Network notifications icon 75 | 76 | ## Performance 77 | 78 | - [ ] Tests 79 | - [ ] CI 80 | - [ ] Coverage 81 | - [x] Linting 82 | - [x] PropTypes 83 | 84 | # Necessary Refactorings 85 | 86 | - [ ] Decouple CSS build from React build (i.e. style everything within mixins, include where appropriate) 87 | - [x] ~Reimplement CSS Modules~ 88 | - [x] Make dummy Select component for places where aesthetically needed 89 | - [x] Group options collections in explorer 90 | - [ ] Pixel near perfect (leave to last) 91 | - [ ] Switch to react-testing-library 92 | - [ ] Sematic HTML improvements 93 | 94 | ## To fix later 95 | 96 | - [ ] Use svg filter for icon highlighting (checkered blue pixel) 97 | - [ ] Refactor various list option groups 98 | - [ ] Blue selected areas grey when parent not active 99 | - [x] Radio and checkbox for Menus (via classes) 100 | - [x] Custom font 101 | - [ ] Start menu animation on iOS (absolute transitions issue, considering disabling on mobile) 102 | - [ ] Selected sections grey when not focus 103 | - [ ] Filter overhauls 104 | - [ ] Redundant CSS clearout 105 | - [x] StandardMenuWrapper as HOC? 106 | - [ ] Gradient improvements 107 | - [ ] Add empty taskbar to core css 108 | - [ ] Disabled buttons 109 | - [x] ~Single width font~ 110 | - [ ] Firefox outlines issue 111 | - [ ] PERFORMANCE IMPROVMENTS 112 | -------------------------------------------------------------------------------- /src/components/Window/Window.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import cx from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import WindowFrame from '../Frame'; 5 | import Button from '../ButtonNav'; 6 | 7 | import './_WindowAbstract.scss'; 8 | 9 | class WindowAbstract extends Component { 10 | static defaultProps = { 11 | title: '...', 12 | resizable: true, 13 | }; 14 | 15 | state = { 16 | maximized: this.props.maximizeOnOpen, 17 | }; 18 | 19 | handleMaximize = e => { 20 | this.setState({ maximized: true }); 21 | 22 | if (this.props.onMaximize) { 23 | this.props.onMaximize(e); 24 | } 25 | }; 26 | 27 | handleRestore = e => { 28 | this.setState({ maximized: false }); 29 | if (this.props.onRestore) { 30 | this.props.onRestore(e); 31 | } 32 | }; 33 | 34 | render() { 35 | const { props } = this; 36 | return ( 37 | 45 |
46 | {props.icon && ( 47 |
51 | )} 52 |
{props.title}
53 | {props.onHelp && ( 54 |
80 | {props.children} 81 | 82 | ); 83 | } 84 | } 85 | 86 | export const windowProps = { 87 | children: PropTypes.node, 88 | title: PropTypes.string, 89 | className: PropTypes.string, 90 | isActive: PropTypes.bool, 91 | icon: PropTypes.string, 92 | 93 | onClose: PropTypes.func, 94 | onMinimize: PropTypes.func, 95 | onMaximize: PropTypes.func, 96 | onRestore: PropTypes.func, 97 | maximizeOnOpen: PropTypes.bool, 98 | 99 | changingState: PropTypes.bool, 100 | }; 101 | 102 | WindowAbstract.propTypes = windowProps; 103 | 104 | export default WindowAbstract; 105 | -------------------------------------------------------------------------------- /src/components/FormSelectDISABLED/_Select.scss: -------------------------------------------------------------------------------- 1 | @import "../../_scss/w98/var/colors"; 2 | @import "../../_scss/w98/mixins/box-shadows"; 3 | @import "../../_scss/w98/functions/box-shadows"; 4 | @import "../../_scss/w98/var/var"; 5 | @import "../../_scss/w98/var/uris"; 6 | 7 | @mixin select-hover { 8 | outline: 1px dotted #ffffff; 9 | outline-offset: -1px; 10 | background-color: $blue; 11 | color: #ffffff; 12 | } 13 | 14 | .w98 { 15 | // .SelectMultiple { 16 | // display: inline-block; 17 | // padding: 2px; 18 | // @include shadow-input; 19 | // >select[multiple] { 20 | // padding: 0px; 21 | // box-shadow: none; 22 | // } 23 | // ::-webkit-scrollbar { 24 | // display: none; 25 | // } 26 | // } 27 | 28 | $select-height: 16px; 29 | 30 | .Select-arrow-zone { 31 | position: absolute; 32 | box-shadow: dualShadow($grey, $black), dualShadow(#ffffff, $darkgrey, 2); 33 | height: $select-height; 34 | width: $select-height; 35 | left: calc(100% - 18px); 36 | top: 2px; 37 | background-color: $grey; 38 | background-repeat: no-repeat; 39 | background-position: center; 40 | background-image: url($arrow-down); 41 | } 42 | /* stylelint-disable */ 43 | .Select { 44 | position: relative; 45 | .Select-control { 46 | width: 100%; 47 | .Select-multi-value-wrapper { 48 | .Select-input, 49 | .Select-placeholder, 50 | .Select-value { 51 | width: calc(100% - 4px); 52 | } 53 | .Select-input { 54 | display: none !important; 55 | } 56 | .Select-value, 57 | .Select-placeholder { 58 | height: $select-height; 59 | background-color: #ffffff; 60 | border: none; 61 | @include shadow-input; 62 | padding: 2px; 63 | .Select-value-label > div { 64 | margin: 1px; 65 | margin-right: $select-height + 1px; 66 | padding-left: 1px; 67 | outline: 1px dotted rgba(0, 0, 0, 0); 68 | } 69 | &:active, 70 | &:focus { 71 | .Select-value-label > div { 72 | @include select-hover(); 73 | } 74 | } 75 | } 76 | .Select-placeholder { 77 | display: flex; 78 | align-items: center; 79 | padding: 2px 0px 2px 4px; 80 | } 81 | } 82 | .Select-clear-zone { 83 | display: none; 84 | } 85 | } 86 | .Select-menu-outer { 87 | border: 1px solid $black; 88 | background-color: #ffffff; 89 | .Select-menu { 90 | .Select-option { 91 | padding: 1px; 92 | &:hover { 93 | @include select-hover(); 94 | } 95 | } 96 | } 97 | } 98 | &.is-disabled { 99 | pointer-events: none; 100 | .Select-control { 101 | .Select-multi-value-wrapper { 102 | .Select-value, 103 | .Select-placeholder { 104 | background-color: $grey; 105 | } 106 | } 107 | .Select-arrow-zone { 108 | &:after { 109 | background-image: url($arrow-down-disabled); 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/_scss/w98/_inputs.scss: -------------------------------------------------------------------------------- 1 | @import "./var/colors"; 2 | @import "./mixins/box-shadows"; 3 | @import "./var/var"; 4 | @import "./var/uris"; 5 | 6 | @mixin input-text { 7 | position: relative; 8 | padding: 3px 3px 6px; 9 | font-size: $base-font-size; 10 | border: none; 11 | @include shadow-input; 12 | 13 | &:active, 14 | &:focus, 15 | &:active:focus, 16 | &.clicked { 17 | outline: none; 18 | } 19 | &:disabled, 20 | &.disabled { 21 | background-color: $grey; 22 | } 23 | } 24 | 25 | @mixin select-multiple-simple { 26 | position: relative; 27 | border: none; 28 | background-color: #ffffff; 29 | border-radius: 0px; 30 | outline: none; 31 | padding: 2px; 32 | @include shadow-input; 33 | 34 | &:active, 35 | &:focus, 36 | &:active:focus, 37 | &.active, 38 | &.clicked { 39 | outline: none; 40 | } 41 | option { 42 | &:active, 43 | &:focus, 44 | &:checked, 45 | &.checked { 46 | outline: 1px dotted #ffffff; 47 | outline-offset: -1px; 48 | background-color: $blue; 49 | color: #ffffff; 50 | } 51 | } 52 | } 53 | 54 | @mixin select-hover { 55 | outline: 1px dotted #ffffff; 56 | outline-offset: -1px; 57 | background-color: $blue; 58 | color: #ffffff; 59 | } 60 | 61 | @mixin toggle { 62 | opacity: 0; 63 | display: none; 64 | cursor: pointer; 65 | + label { 66 | position: relative; 67 | padding: 1px 0px; 68 | padding-left: 16px; 69 | > span, 70 | > div { 71 | display: inline-block; 72 | border: 1px solid rgba(0, 0, 0, 0); 73 | } 74 | &:before { 75 | content: ""; 76 | position: absolute; 77 | left: 0px; 78 | top: 1px; 79 | width: 20px; 80 | height: 12px; 81 | background-repeat: no-repeat; 82 | } 83 | } 84 | &:checked { 85 | + label { 86 | border-bottom-left-radius: 2px; 87 | border-bottom-right-radius: 2px; 88 | } 89 | &:active, 90 | &:focus, 91 | &:active:focus, 92 | &.active, 93 | &.clicked { 94 | + label { 95 | > span, 96 | > div { 97 | border: 1px dotted $black; 98 | } 99 | } 100 | } 101 | } 102 | &:disabled, 103 | &.disabled { 104 | + label { 105 | color: $darkgrey; 106 | } 107 | } 108 | } 109 | 110 | @mixin checkbox { 111 | @include toggle(); 112 | + label:before { 113 | width: 13px; 114 | height: 13px; 115 | background-color: #ffffff; 116 | @include shadow-input; 117 | } 118 | &:checked + label:before { 119 | background-image: url($menu-checked); 120 | background-position: center; 121 | background-size: 8px; 122 | } 123 | &:disabled, 124 | &.disabled { 125 | + label:before { 126 | background-color: $grey; 127 | } 128 | &:checked + label:before { 129 | background-image: url($menu-checked-disabled); 130 | } 131 | } 132 | } 133 | 134 | @mixin radio { 135 | @include toggle(); 136 | + label:before { 137 | background-image: url($radio-off); 138 | } 139 | &:checked { 140 | + label { 141 | &:before { 142 | background-image: url($radio-on); 143 | } 144 | } 145 | } 146 | &:disabled, 147 | &.disabled { 148 | + label:before { 149 | background-image: url($radio-off-disabled); 150 | } 151 | &:checked { 152 | + label { 153 | &:before { 154 | background-image: url($radio-on-disabled); 155 | } 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "packard-belle", 3 | "version": "0.4.0", 4 | "description": "Library of components to mimic old desktop", 5 | "main": "build/pb.js", 6 | "module": "build/pb.module.js", 7 | "repository": "https://github.com/padraigfl/packard-belle", 8 | "scripts": { 9 | "build:module": "rollup -c rollup.config.babel.js", 10 | "watch": "rollup -c rollup.config.babel.js -w", 11 | "storybook": "storybook dev -p 9001 -c .storybook", 12 | "build": "storybook build -c .storybook -o .out", 13 | "build:css": "sass src/_scss/w98_simple.scss:css/w98_simple.css", 14 | "test": "jest --env=jsdom", 15 | "test:watch": "jest --env=jsdom --no-cache --watch", 16 | "coverage": "jest --env=jsdom --coverage --coverageReporters=text-lcov ", 17 | "coveralls": "npm run coverage | coveralls", 18 | "lint": "eslint -c .eslintrc.js --quiet --ext .js './src/**/*.js'", 19 | "lint:css": "stylelint 'src/**/*.scss'", 20 | "precommit-build": "npm run build:module && git add build && git add css", 21 | "index": "node ./.scripts/autoIndex/", 22 | "playroom:start": "playroom start", 23 | "playroom:build": "playroom build" 24 | }, 25 | "keywords": [ 26 | "react", 27 | "component", 28 | "library", 29 | "windows", 30 | "desktop", 31 | "98" 32 | ], 33 | "author": "padraigf (https://github.com/padraigfl)", 34 | "license": "ISC", 35 | "dependencies": { 36 | "classnames": "^2.2.6", 37 | "clone": "^2.1.2", 38 | "prop-types": "^15.6.2" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "^7.1.5", 42 | "@babel/plugin-proposal-class-properties": "^7.1.0", 43 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", 44 | "@babel/preset-env": "^7.1.5", 45 | "@babel/preset-react": "^7.0.0", 46 | "@storybook/addon-storysource": "^7.0.21", 47 | "@storybook/builder-vite": "^7.0.21", 48 | "@storybook/builder-webpack5": "^7.0.21", 49 | "@storybook/react": "^7.0.21", 50 | "@storybook/react-webpack5": "^7.0.21", 51 | "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", 52 | "ajv": "^6.12.6", 53 | "babel-cli": "^6.26.0", 54 | "babel-core": "^7.0.0-bridge.0", 55 | "babel-eslint": "^8.2.6", 56 | "babel-jest": "^29.5.0", 57 | "babel-loader": "^8.0.4", 58 | "coveralls": "^3.0.2", 59 | "css-loader": "^5.2.7", 60 | "enzyme": "^3.3.0", 61 | "eslint": "^8.42.0", 62 | "eslint-plugin-jest": "^21.18.0", 63 | "eslint-plugin-react": "^7.33.2", 64 | "eslint-plugin-storybook": "^0.6.15", 65 | "jest": "^29.5.0", 66 | "playroom": "^0.31.0", 67 | "pre-commit": "^1.2.2", 68 | "raf": "^3.4.0", 69 | "react": "^17.0.0", 70 | "react-dom": "^17.0.0", 71 | "react-select": "^5.0.0", 72 | "react-test-renderer": "~17.0.0", 73 | "regenerator-runtime": "^0.12.1", 74 | "rollup": "^1.32.1", 75 | "rollup-plugin-auto-external": "^2.0.0", 76 | "rollup-plugin-babel": "^4.0.3", 77 | "rollup-plugin-commonjs": "^9.1.4", 78 | "rollup-plugin-img": "^1.1.0", 79 | "rollup-plugin-node-resolve": "^3.3.0", 80 | "rollup-plugin-postcss": "^4.0.2", 81 | "rollup-plugin-replace": "^2.0.0", 82 | "sass": "^1.63.4", 83 | "sass-loader": "^10.4.1", 84 | "storybook": "^7.0.21", 85 | "style-loader": "^2.0.0", 86 | "stylelint": "^15.7.0", 87 | "stylelint-scss": "^5.0.0" 88 | }, 89 | "peerDependencies": { 90 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0", 91 | "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" 92 | }, 93 | "pre-commit": [ 94 | "lint", 95 | "build:css", 96 | "precommit-build" 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /src/_scss/w98/_window.scss: -------------------------------------------------------------------------------- 1 | @import "./var/colors"; 2 | @import "./functions/box-shadows"; 3 | @import "./var/var"; 4 | @import "./var/uris"; 5 | @import "./_menu"; 6 | 7 | @mixin window-basic { 8 | position: relative; 9 | background-color: $grey; 10 | padding: 3px; 11 | box-shadow: dualShadow($grey, $black), dualShadow(#ffffff, $darkgrey, 2); 12 | display: inline-block; 13 | } 14 | 15 | @mixin window-drag { 16 | background-color: rgba(0, 0, 0, 0); 17 | box-shadow: dualShadow($darkgrey, $darkgrey, 3); 18 | > *, 19 | &:after { 20 | filter: opacity(0.1%); 21 | } 22 | } 23 | 24 | @mixin window-maximized { 25 | width: 100%; 26 | height: 100%; 27 | } 28 | 29 | @mixin window-heading { 30 | display: flex; 31 | background: linear-gradient(to right, $blue, #126fc2); 32 | font-weight: bold; 33 | color: #ffffff; 34 | margin-bottom: 1px; 35 | padding: 0px 1px 0px 3px; 36 | align-items: center; 37 | letter-spacing: 1px; 38 | 39 | button { 40 | padding: 0px; 41 | min-width: initial; 42 | width: 16px; 43 | height: 14px; 44 | margin-left: 1px; 45 | image-rendering: pixelated; 46 | display: flex; 47 | align-items: center; 48 | flex-shrink: 0; 49 | background-repeat: no-repeat; 50 | background-position: 1px 1px; 51 | 52 | &:focus, 53 | &.clicked { 54 | outline: none; 55 | border: none; 56 | } 57 | 58 | &:active:focus, 59 | &.clicked { 60 | padding: 2px 8px 1px 4px; 61 | background-position: 2px 2px; 62 | } 63 | } 64 | } 65 | 66 | @mixin window-icon { 67 | padding: 8px; 68 | display: flex; 69 | background-size: 14px; 70 | background-repeat: no-repeat; 71 | background-position: center; 72 | margin-right: 4px; 73 | } 74 | 75 | @mixin window-title { 76 | white-space: nowrap; 77 | overflow: hidden; 78 | text-overflow: ellipsis; 79 | flex-grow: 1; 80 | min-width: 0px; 81 | user-select: none; 82 | } 83 | 84 | @mixin window-menu($classname: "div") { 85 | display: flex; 86 | padding: 0px; 87 | font-size: 1rem; 88 | position: relative; 89 | overflow-y: visible; 90 | z-index: 20; 91 | 92 | > div { 93 | position: relative; 94 | 95 | > button { 96 | padding: 0px 4px; 97 | outline: none; 98 | border: none; 99 | user-select: none; 100 | color: $black; 101 | display: inline-block; 102 | background-color: rgba(0, 0, 0, 0); 103 | width: 100%; 104 | overflow: hidden; 105 | white-space: nowrap; 106 | text-overflow: ellipsis; 107 | text-align: left; 108 | padding: 3px 6px; 109 | text-transform: capitalize; 110 | 111 | + div, 112 | + #{$classname} { 113 | z-index: 20; 114 | visibility: hidden; 115 | position: absolute; 116 | max-height: 0px; 117 | top: 100%; 118 | left: 0px; 119 | @media (min-height: 720px) and (min-width: 960px) { 120 | transition: max-height linear 750ms; 121 | } 122 | } 123 | 124 | &:hover { 125 | box-shadow: dualShadow(#ffffff, $darkgrey); 126 | } 127 | 128 | &:active, 129 | &:focus, 130 | &:active:focus, 131 | &.active, 132 | &.clicked { 133 | box-shadow: dualShadow($darkgrey, #ffffff); 134 | padding: 4px 5px 2px 7px; 135 | 136 | + div, 137 | + #{$classname} { 138 | visibility: visible; 139 | max-height: 480px; 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | @mixin window-settings-section() { 147 | position: relative; 148 | border: 1px solid #ffffff; 149 | outline: 1px solid $darkgrey; 150 | padding: 5px; 151 | margin: 16px 8px 8px; 152 | 153 | &__title { 154 | position: absolute; 155 | top: -10px; 156 | padding: 2px 4px; 157 | background-color: $grey; 158 | } 159 | } 160 | 161 | @mixin window-alert() { 162 | display: inline-flex; 163 | flex-direction: column; 164 | max-width: 250px; 165 | &__message { 166 | display: flex; 167 | align-items: center; 168 | user-select: none; 169 | &.has-icon { 170 | background-size: 28px 28px; 171 | background-repeat: no-repeat; 172 | background-position: 6px 6px; 173 | padding: 6px 4px 8px 40px; 174 | } 175 | min-height: 28px; 176 | padding: 10px 2px 6px; 177 | } 178 | &__actions { 179 | width: 100%; 180 | display: flex; 181 | justify-content: center; 182 | .btn { 183 | margin: 0px 4px 8px; 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/_scss/w98/var/uris.scss: -------------------------------------------------------------------------------- 1 | $arrow-up: "data:image/gif;base64,R0lGODlhBwAEAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAHAAQAAAIHlGEJq8sOCwA7"; 2 | $arrow-down: "data:image/gif;base64,R0lGODlhBwAEAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAHAAQAAAIIhA+CKWoNmSgAOw=="; 3 | $arrow-left: "data:image/gif;base64,R0lGODlhBAAHAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAEAAcAAAIIlHEIy7ppBCgAOw=="; 4 | $arrow-right: "data:image/gif;base64,R0lGODlhBAAHAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAEAAcAAAIIhA4maeyrlCgAOw=="; 5 | $arrow-right-inverted: "data:image/gif;base64,R0lGODlhBAAHAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAEAAcAAAIIjB4maeyrlCgAOw=="; 6 | $arrow-down-disabled: "data:image/gif;base64,R0lGODlhCAAFAJEAAAAAAP///5mZmf///yH5BAEAAAMALAAAAAAIAAUAAAIMlC8zKBF6nIJyqqcKADs="; 7 | $menu-checked: "data:image/gif;base64,R0lGODlhBwAHAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAHAAcAAAIMlA9nwMj9xGuLIlUAADs="; 8 | $menu-checked-disabled: "data:image/gif;base64,R0lGODlhBwAHAJEAAAAAAP///5mZmf///yH5BAEAAAMALAAAAAAHAAcAAAIMnC9nwsj9xmuLIlUAADs="; 9 | $menu-radio: "data:image/gif;base64,R0lGODlhBgAGAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAGAAYAAAIIFA6Gy816RAEAOw=="; 10 | $radio-off: "data:image/gif;base64,R0lGODlhDAAMAKIAAAAAAP///8zMzJmZmf///wAAAAAAAAAAACH5BAEAAAQALAAAAAAMAAwAAAMqSErTs6uBCVqcIQesBtCaEDAfGAaeeaZqILKqyLQyI4hhTWT3nUEKECQBADs="; 11 | $radio-on: "data:image/gif;base64,R0lGODlhDAAMAKIAAAAAAP///8zMzJmZmf///wAAAAAAAAAAACH5BAEAAAQALAAAAAAMAAwAAAMtSErTs6uBCVqcIQesBtCaEDBfhmWiZ1JooG5skJZwOA6g3QliKC4oXg+iAEESADs="; 12 | $radio-off-disabled: "data:image/gif;base64,R0lGODlhDAAMAKIAAAAAAP///8zMzJmZmf///wAAAAAAAAAAACH5BAEAAAQALAAAAAAMAAwAAAMpSErTs6uBCVqccAY1AFTC1n1LOJKE6aEqmorsxggCRMtEENA3vug6SAIAOw=="; 13 | $radio-on-disabled: "data:image/gif;base64,R0lGODlhDAAMAKIAAAAAAP///8zMzJmZmf///wAAAAAAAAAAACH5BAEAAAQALAAAAAAMAAwAAAMtSErTs6uBCVqccAY1AFTC1i0YGIwE5REhqppourLiZ3KCAOWbEgQ5Xg/y+0ESADs="; 14 | 15 | $more-options: "data:image/gif;base64,R0lGODlhCAAFAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAIAAUAAAIKBCSGebzqoJKtAAA7"; 16 | 17 | $grey-checkered: "data:image/gif;base64,R0lGODlhAgACAJEAAAAAAP///8zMzP///yH5BAEAAAMALAAAAAACAAIAAAID1CYFADs="; 18 | $blue-checkered: "data:image/gif;base64,R0lGODlhAgACAJEAAAAAAP///wAAt////yH5BAEAAAMALAAAAAACAAIAAAID1CYFADs="; 19 | $blue-checkered-lighter: "data:image/gif;base64,R0lGODlhAgACAJEAAAAAAP///0ZGtP///yH5BAEAAAMALAAAAAACAAIAAAID1CYFADs="; 20 | 21 | $cursor: "data:image/gif;base64,R0lGODlhCwATAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAALABMAAAIrhI4JlhrcBAgvSlrbxPBs7mAU9IlMaV7mwo6jY2zk+Xphh8iWint1tDgUAAA7"; 22 | $cursorX2: "data:image/gif;base64,R0lGODlhFgAmAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAAWACYAAAJzBISpu8b/jINUHgpNCBMrzV1eAm6dV4YjkppiBWyyisazfDIt/ur2zcv8gDQf8ZYT7IDJJfHkZL6izwtVyhpKLVwtssudpZJZ8ZCstE3GvbSrHGxIPue2hW72CfOkNvy9wrbiFjcoGFhnmIjIp4iGcZdQAAA7"; 23 | 24 | $close: "data:image/gif;base64,R0lGODlhDQALAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAANAAsAAAIUlI+pKwDoVGxvucmwvblqo33MqBQAOw=="; 25 | $restore: "data:image/gif;base64,R0lGODlhDQALAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAANAAsAAAIZlI9pwK3SnAKI1kjtwTlpyHjV830b9qRHAQA7"; 26 | $minimize: "data:image/gif;base64,R0lGODlhDQALAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAANAAsAAAIOlI+py+0PozSg2mXvFAUAOw=="; 27 | $maximize: "data:image/gif;base64,R0lGODlhDQALAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAANAAsAAAIXlI8Jy4wNXzJAznqwsjtPoYFfCDXfWQAAOw=="; 28 | $whelp: "data:image/gif;base64,R0lGODlhDQALAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAANAAsAAAIUlI9pwKDrBHtTxmcxvJTrn30VqBQAOw=="; 29 | $resize: "data:image/gif;base64,R0lGODlhDAAMAJEAAAAAAP///5mZmf///yH5BAEAAAMALAAAAAAMAAwAAAIbnI8TmSF83IMSKvFWw3dnHnFV+GVGhZZXmaoFADs="; 30 | 31 | $action-menu5: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOAgMAAABbQXQZAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJUExURQAAq////wAAADZqBOUAAAArSURBVAjXY1gFBAwNDAxMDGDW1NDQMIapUbOmQVhTGBjEoCyEGJwFJkDaAGAFGP4kmmZ0AAAAAElFTkSuQmCC"; 32 | $action-menu4: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOAgMAAABbQXQZAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURQAAq////wAAAIeIj/NEB9oAAAAoSURBVAjXY1gFBAwNDAxMDGDW1NDQMIY5mYXLGCaEOobh4AYic0HaAHnVGXeUN3O0AAAAAElFTkSuQmCC"; 33 | $action-menu3: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAkUExURUdwTAAA/1Ooqf///wAAAIeIj6ipUf//AKoAVwAAqwD///8AABIbKHYAAAABdFJOUwBA5thmAAAAY0lEQVQY012PURKAIAgFHwimdf/7xkMza/92hxkA2GgNH+rFoMRJPaq5QU8VlIAuEiEd6R0M6WMenWFzoBucXupwOAObFdgKxiXYJ6JMZ1Da43MtXldeKusZyYk41QeiPN1+3GTUApb2Wc7EAAAAAElFTkSuQmCC"; 34 | $action-menu2: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAPAgMAAACQHae8AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJUExURUdwTAAAAP//ADvXwAwAAAABdFJOUwBA5thmAAAAOUlEQVQI12NgYGBkgAEwy4GFgTVAUIBBooXFgSE0NESQwaOj04EhSUlRAMRiAbEYQCwGJFZoaCgDADXDC1L2Qw0aAAAAAElFTkSuQmCC"; 35 | $action-menu1: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAANCAMAAABBwMRzAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJUExURUdwTP///4eIj4zp4tUAAAABdFJOUwBA5thmAAAAOElEQVQI112OQQ4AIAzCuv7/0WpMcBNO0CwM8IhIa9sXu8AaRmmW283KFOHVOYnh44Q+VmPw+88FZDYBB0ED8bYAAAAASUVORK5CYII="; 36 | -------------------------------------------------------------------------------- /src/_scss/w98/_task-bar.scss: -------------------------------------------------------------------------------- 1 | @import "./var/colors"; 2 | @import "./functions/box-shadows"; 3 | 4 | $w98-text: "data:image/gif;base64,R0lGODlhDgBkALMAAAAAAP///wIAsZKSmZKTmpGSmZKTmcjOz8fNzsfOz8fOzv///wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAOAGQAAAT/cMk5SUo06CO179wSGEowgEOQBcRUEuqkUaIRd/cCwyvFzyJNS3JQ2Tyt0QLBklgwEqZGQasShr4DQhuilDxgRCWAINgIAkIxFoB2DDJWbmGb2Oq0nJx2dqoCXUEuKl8GMCZRSjpgWAdYEydVkhMJQlVkQR8UTFRgQDhiHkc9QRyfRwRSV5+ZH1KbnodzjEGPCAYFcBIJj5mOk61IkgZSnpKVxpSeYCuegTjCw8Uev1bLPkfXccuY29SSGgmRky2p4b2Jnm5+3LrQ3CsY5Wuk9ZlwcJrv2uzLvWthJgH0cWVAKkMGBjhKws1YQ4cPP1wxUETclUPuBOXRY4mOvmDJafaFFMmKwoEDCspIgnGSC0pYDZvB88YvE7Bd3YABrBlRJs+HN73MiPgq4heQYJAhlYiOhqyUwLhVo7TTWcYlyEZOmAbEYM+I4hape4b0Cg0tDXlVyapVR9UY5h7KaogAg9R1c82ubEohAgA7"; 5 | 6 | @mixin task-bar($classname: "task-bar") { 7 | position: fixed; 8 | background-color: $grey; 9 | bottom: 0px; 10 | left: 0px; 11 | width: 100%; 12 | max-width: 100%; 13 | z-index: 10; 14 | box-shadow: 0px -1px 0px #ffffff; 15 | padding: 2px 0px; 16 | display: flex; 17 | 18 | > div, 19 | > button { 20 | position: relative; 21 | height: 22px; 22 | margin: 0px 2px; 23 | } 24 | 25 | > div:not(:last-child) { 26 | padding: 0px 6px; 27 | &:first-child { 28 | padding: 0px 3px 0px 0px; 29 | } 30 | &:after { 31 | position: absolute; 32 | top: 1px; 33 | right: 0px; 34 | height: calc(100% - 2px); 35 | width: 1px; 36 | background-color: $darkgrey; 37 | content: ""; 38 | box-shadow: 1px 0px 0px #ffffff; 39 | } 40 | &:before { 41 | position: absolute; 42 | top: 3px; 43 | right: -6px; 44 | height: calc(100% - 6px); 45 | width: 3px; 46 | background-color: $grey; 47 | content: ""; 48 | box-shadow: dualShadow(#ffffff, $darkgrey); 49 | } 50 | } 51 | 52 | &__programs { 53 | display: flex; 54 | flex-grow: 1; 55 | flex-shrink: 1; 56 | flex-wrap: nowrap; 57 | margin-right: 4px; 58 | min-width: 42px; 59 | 60 | &:before { 61 | display: none; 62 | } 63 | } 64 | &__start { 65 | position: relative; 66 | > button { 67 | + div { 68 | @media (min-height: 720px) and (min-width: 960px) { 69 | transition: max-height linear 200ms; 70 | } 71 | position: fixed; 72 | bottom: 25px; 73 | left: 2px; 74 | visibility: hidden; 75 | max-height: 0px; 76 | padding-left: 22px; 77 | > .divider:empty, 78 | > div:empty { 79 | margin-left: 24px; 80 | width: calc(100% - 26px); 81 | } 82 | &:after { 83 | content: ""; 84 | display: block; 85 | position: absolute; 86 | left: 3px; 87 | top: 3px; 88 | height: calc(100% - 6px); 89 | width: 20px; 90 | background: $blue; 91 | background: linear-gradient($blue, #126fc2); 92 | background: url($w98-text) no-repeat bottom 3px center, 93 | linear-gradient($blue, #126fc2); 94 | } 95 | > div { 96 | display: flex; 97 | align-items: center; 98 | margin-left: 20px; 99 | > button { 100 | height: 32px; 101 | padding-left: 32px; 102 | background-size: 22px; 103 | background-position: 4px center; 104 | } 105 | } 106 | } 107 | 108 | &.active, 109 | &.clicked { 110 | background-position: 3px 2px; 111 | outline: 1px dotted $black; 112 | outline-offset: -4px; 113 | 114 | > div { 115 | visibility: visible; 116 | max-height: 100vh; 117 | padding: 3px; 118 | div { 119 | display: flex; 120 | } 121 | } 122 | } 123 | } 124 | 125 | &.active { 126 | > div { 127 | visibility: visible; 128 | max-height: 100vh; 129 | padding: 3px; 130 | div { 131 | display: flex; 132 | } 133 | } 134 | } 135 | } 136 | 137 | &__notifications { 138 | background-color: $grey; 139 | display: flex; 140 | flex: none; 141 | margin-left: auto; 142 | align-items: center; 143 | height: 22px; 144 | padding: 0px 8px 0px 4px; 145 | box-shadow: dualShadow($darkgrey, #ffffff); 146 | 147 | &__time { 148 | margin-left: 4px; 149 | } 150 | 151 | &__notifier { 152 | height: 16px; 153 | width: 16px; 154 | background-color: $grey; 155 | background-size: contain; 156 | background-position: center; 157 | background-repeat: no-repeat; 158 | border: none; 159 | &:active, 160 | &:focus, 161 | &:active:focus, 162 | &.active, 163 | &.clicked { 164 | outline: none; 165 | border: none; 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /.storybook/stories/windows.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import WindowFrame from '../../src/components/Frame'; 3 | import AbstractWindow from '../../src/components/Window'; 4 | import ExplorerWindow from '../../src/components/WindowExplorer'; 5 | import AlertWindow from '../../src/components/WindowAlert'; 6 | import WindowProgram from '../../src/components/WindowProgram'; 7 | import DetailsSection from '../../src/components/DetailsSection'; 8 | 9 | import img from './directory_closed.png'; 10 | import WindowAction from '../../src/components/WindowAction'; 11 | 12 | const noop = () => {}; 13 | 14 | const optionsSample = [ 15 | { 16 | onClick: noop, 17 | title: 'New', 18 | }, 19 | { 20 | onClick: noop, 21 | title: 'Disabled', 22 | disabled: true, 23 | }, 24 | [ 25 | { 26 | onClick: noop, 27 | title: 'Open', 28 | options: [ 29 | { 30 | onClick: noop, 31 | title: 'open file?', 32 | }, 33 | { 34 | onClick: noop, 35 | title: 'open drv file?', 36 | }, 37 | { 38 | onClick: noop, 39 | title: 'spin it', 40 | options: [ 41 | { 42 | onClick: noop, 43 | title: 'twist it?', 44 | }, 45 | { 46 | onClick: noop, 47 | title: 'fly it', 48 | }, 49 | ], 50 | }, 51 | ], 52 | }, 53 | ], 54 | { 55 | onClick: noop, 56 | title: 'quit', 57 | }, 58 | ]; 59 | 60 | export const GenericWindow = () => Window 61 | export const StaticWindow = () => 68 | Windows 69 | 70 | export const WindowWithDetailsSection = () => ( 71 | 78 | 79 |

Here's a load of stuff

80 |
81 | 82 |

Here's a load of stuff

83 |
84 |
85 | ) 86 | export const WindowForProgram = () => ( 87 | 105 | Windows 106 | 107 | ) 108 | export const ExplorerWindowStory = { 109 | render: () => ( 110 | Test
,
Oh
]} 161 | resizable 162 | /> 163 | ), 164 | notes: 'WIP' 165 | } 166 | export const AlertWindowStory = { 167 | render: () => ( 168 | 175 | This is an error message. 176 | 177 | ) 178 | } 179 | export const WindowActionStory = () => ( 180 | 188 | ); 189 | 190 | const meta = { 191 | component: GenericWindow, 192 | } 193 | export default meta; -------------------------------------------------------------------------------- /src/components/StandardMenuHOC/StandardMenuHOC.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import clone from 'clone'; 5 | import StandardMenu, { standardMenuProps } from '../StandardMenu'; 6 | 7 | const withContextLogic = ContextButton => { 8 | return class StandardMenuSimple extends Component { 9 | static defaultProps = { 10 | value: [], 11 | }; 12 | static propTypes = { 13 | ...standardMenuProps, 14 | onClick: PropTypes.func, 15 | onBlur: PropTypes.func, 16 | onContextMenu: PropTypes.func, 17 | }; 18 | 19 | static getDerivedStateFromProps(nextProps, prevState) { 20 | if (nextProps.isActive !== prevState.isActive) { 21 | return { 22 | options: nextProps.options, 23 | isActive: nextProps.isActive, 24 | }; 25 | } else return null; 26 | } 27 | 28 | state = { 29 | options: this.props.options, 30 | isActive: this.props.isActive, 31 | isOpen: false, 32 | }; 33 | 34 | updateActive(activeFields, newOptions, idx = 0) { 35 | if (activeFields.length <= idx) { 36 | return newOptions; 37 | } 38 | const changeIdx = newOptions.findIndex((option, optIdx) => { 39 | if (Array.isArray(option)) { 40 | const subIdx = option.findIndex( 41 | opt => opt.title === activeFields[idx] 42 | ); 43 | if (subIdx !== -1) { 44 | newOptions[optIdx][subIdx].isActive = true; 45 | if (newOptions[optIdx][subIdx].options) { 46 | newOptions[optIdx][subIdx].options = this.updateActive( 47 | activeFields, 48 | newOptions[optIdx][subIdx].options, 49 | idx + 1 50 | ); 51 | } 52 | return; 53 | } 54 | } 55 | return option.title === activeFields[idx]; 56 | }); 57 | if (changeIdx !== -1) { 58 | newOptions[changeIdx].isActive = true; 59 | newOptions[changeIdx].options = this.updateActive( 60 | activeFields, 61 | newOptions[changeIdx].options, 62 | idx + 1 63 | ); 64 | } 65 | return newOptions; 66 | } 67 | 68 | mouseEnterItem = e => { 69 | if (e.target.value) { 70 | const newOptions = this.updateActive( 71 | e.target.value.split(','), 72 | clone(this.props.options), 73 | 0 74 | ); 75 | this.setState({ options: newOptions }); 76 | } 77 | }; 78 | addBlurListener = () => { 79 | document.body.addEventListener('click', this.handleBlur); 80 | document.body.addEventListener('mousedown', this.handleBlur); 81 | }; 82 | removeBlurListener = () => { 83 | document.body.removeEventListener('click', this.handleBlur); 84 | document.body.removeEventListener('mousedown', this.handleBlur); 85 | }; 86 | 87 | buttonClick = () => { 88 | if (this.state.isOpen) { 89 | this.removeBlurListener(); 90 | this.setState({ isOpen: false, options: this.props.options }); 91 | } else { 92 | this.addBlurListener(); 93 | this.setState({ isOpen: true, options: this.props.options }); 94 | } 95 | }; 96 | handleEvent = newState => onEvent => e => { 97 | if (onEvent) { 98 | onEvent(e); 99 | } 100 | if (newState) { 101 | this.setState(newState); 102 | } 103 | }; 104 | handleContextMenu = e => 105 | this.handleEvent({ isOpen: true })(this.props.onContextMenu)(e); 106 | handleBlur = e => { 107 | if (this.el && !this.el.contains(e.target)) { 108 | this.handleEvent({ isOpen: false, options: this.props.options })( 109 | this.props.onBlur 110 | )(e); 111 | } 112 | }; 113 | handleSelectionClose = this.handleEvent({ 114 | isOpen: false, 115 | options: this.props.options, 116 | }); 117 | 118 | render() { 119 | const renderedMenu = ( 120 | this.mouseEnterItem(e)} 124 | closeOnClick={this.handleSelectionClose} 125 | /> 126 | ); 127 | 128 | if (ContextButton) { 129 | const { className, ...props } = this.props; 130 | return ( 131 |
{ 133 | this.el = el; 134 | }} 135 | className={cx('StandardMenuWrapper', className, { 136 | active: this.state.isOpen, 137 | })} 138 | > 139 | this.handleContextMenu(e)) 145 | } 146 | > 147 | {props.children} 148 | 149 | {renderedMenu} 150 |
151 | ); 152 | } 153 | return renderedMenu; 154 | } 155 | }; 156 | }; 157 | 158 | export default withContextLogic; 159 | --------------------------------------------------------------------------------